From cd9c6a40d94fd4e373623fb9ac9b072e9a8aaa5c Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 4 May 2022 22:44:28 +0800 Subject: [PATCH 01/31] Tune the update speed --- packages/db/migrate.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/db/migrate.ts b/packages/db/migrate.ts index 9d5b2bfd6..42f4682d8 100755 --- a/packages/db/migrate.ts +++ b/packages/db/migrate.ts @@ -133,6 +133,9 @@ log('Starting adding default state to pages in elasticsearch...') esClient .update_by_query({ index: INDEX_ALIAS, + requests_per_second: 10, + scroll: '30s', + scroll_size: 500, body: { script: { source: 'ctx._source.state = params.state', From 8386aebaf8cb8c6e59da045e969ede57e9f7eea9 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 4 May 2022 14:22:08 -0700 Subject: [PATCH 02/31] Remove ads from fiercepharma page --- packages/readabilityjs/Readability.js | 2 +- .../fiercepharma/expected-metadata.json | 11 + .../test-pages/fiercepharma/expected.html | 68 + .../test/test-pages/fiercepharma/source.html | 1575 +++++++++++++++++ .../test/test-pages/fiercepharma/url.txt | 1 + 5 files changed, 1656 insertions(+), 1 deletion(-) create mode 100644 packages/readabilityjs/test/test-pages/fiercepharma/expected-metadata.json create mode 100644 packages/readabilityjs/test/test-pages/fiercepharma/expected.html create mode 100644 packages/readabilityjs/test/test-pages/fiercepharma/source.html create mode 100644 packages/readabilityjs/test/test-pages/fiercepharma/url.txt diff --git a/packages/readabilityjs/Readability.js b/packages/readabilityjs/Readability.js index 8380b9313..683f500d4 100644 --- a/packages/readabilityjs/Readability.js +++ b/packages/readabilityjs/Readability.js @@ -167,7 +167,7 @@ Readability.prototype = { // NOTE: These two regular expressions are duplicated in // Readability-readerable.js. Please keep both copies in sync. articleNegativeLookBehindCandidates: /breadcrumbs|breadcrumb|utils|trilist/i, - articleNegativeLookAheadCandidates: /outstream(.?)_|sub(.?)_|m_/i, + articleNegativeLookAheadCandidates: /outstream(.?)_|sub(.?)_|m_|omeda-promo-/i, unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|breadcrumb|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager(?!ow)|popup|yom-remote|copyright|keywords|outline|infinite-list|beta|recirculation|site-index|hide-for-print|post-end-share-cta|post-end-cta-full|post-footer|main-navigation|programtic-ads|outstream_article|hfeed|comment-holder|back-to-top|show-up-next/i, // okMaybeItsACandidate: /and|article(?!-breadcrumb)|body|column|content|main|shadow|post-header/i, get okMaybeItsACandidate() { diff --git a/packages/readabilityjs/test/test-pages/fiercepharma/expected-metadata.json b/packages/readabilityjs/test/test-pages/fiercepharma/expected-metadata.json new file mode 100644 index 000000000..60b1d61ef --- /dev/null +++ b/packages/readabilityjs/test/test-pages/fiercepharma/expected-metadata.json @@ -0,0 +1,11 @@ +{ + "title": "Novavax, eyeing the COVID 'vaccine hesitant' and kids, unveils new education campaigns as Nuvaxovid nears US finish line", + "byline": " 03:23pm", + "dir": null, + "excerpt": "Pfizer, Moderna and Johnson & Johnson were quickest off the mark in getting COVID vaccines into American arms, but Novavax is hoping to add another pandemic vaccine to the U.S. | Pfizer, Moderna and Johnson & Johnson were quickest off the mark in getting COVID vaccines into American arms, but Novavax is hoping to add another pandemic vaccine to the U.S. mix soon—and it's pushing new campaigns to get the word out.", + "siteName": "Fierce Pharma", + "siteIcon": "https://qtxasset.com/quartz/qcloud5/fiercefavicon.ico", + "previewImage": "https://qtxasset.com/quartz/qcloud5/media/image/Novavaximage.png?VersionId=f9UDAFMgagg4Y_igFXWmnc3TJKOoTl_k", + "publishedDate": "2022-03-10T13:30:00.000Z", + "readerable": false +} diff --git a/packages/readabilityjs/test/test-pages/fiercepharma/expected.html b/packages/readabilityjs/test/test-pages/fiercepharma/expected.html new file mode 100644 index 000000000..35833138b --- /dev/null +++ b/packages/readabilityjs/test/test-pages/fiercepharma/expected.html @@ -0,0 +1,68 @@ +
+
+
+
+
+ Novavax +
+
Novavax is betting its reliance on traditional vaccine technology can bring several missed population groups back on board for their COVID shots. (Novavax )
+
+ + +
+
+

Pfizer, Moderna and Johnson & Johnson were quickest off the mark in getting COVID vaccines into American arms, but Novavax is hoping to add another pandemic vaccine to the U.S. mix soon—and it's pushing new campaigns to get the word out.

+

The biopharma, which has approvals and authorizations in Europe and around the world, is now on the cusp of a potential green light in the U.S. And with a market comes the need for marketing.

+

But because it still has no U.S. approval—and it cannot under law advertise to consumers in Europe—Novavax is launching two new global, unbranded vaccine education programs: "We Do Vaccines" and "Know Our Vax." They're designed to offer up vaccine information and "explain Novavax’ commitment to vaccine development and innovation,” the company told Fierce Pharma Marketing.

+

The main message of the campaign is that “people have options when it comes to their vaccine,” Silvia Taylor, senior vice president of global corporate affairs at Novavax, said in an interview. “We want people to understand that we have this vaccine, and that this vaccine is different.”

+
+

+

Related

+

+
+

Novavax knows it has some tough competition—Pfizer and Moderna's vaccines dominate the U.S. market—but the small biotech is eyeing certain market niches: The "vaccine hesitant" who might be leery of the brand-new mRNA tech in Pfizer and Moderna's shots, and children. And it does have a strong, vocal following online that's eagerly awaiting a U.S. decision.

+

Nuvaxovid taps older tech that's been used in influenza shots and others for decades. The vaccine contains a version of the SARS-CoV-2 spike protein made in the lab as well as an adjuvant, which is a booster ingredient designed to strengthen immune responses to the vaccine.

+

A new option

+

Pfizer and Moderna's shots obviously weren't the only two vaccines in the U.S.: Johnson & Johnson’s single-dose vaccine alternative also uses older vaccine technology. But it fell out of favor amid weakening efficacy and major manufacturing issues. Then, late last year, a Centers for Disease Control and Prevention panel recommended it should be sidelined because of serious safety concerns.

+

AstraZeneca's COVID vaccine—which itself uses more traditional vaccine technology—hasn’t been approved in the U.S. 

+

Enter Novavax, now looking to position its vaccine as an mRNA alternative.

+

People hesitant about vaccinations may not want an mRNA vaccine because it's new technology, without years of proven safety behind it. But they might use an older, “tried and tested” tech, as Novavax puts it.  

+

“There’s a recognition; a familiarity with this type of [protein-based] vaccine technology that many are comfortable with, and would have had with HPV, shingles and flu shots,” Taylor said.

+

“There is a segment of the population that are the so-called vaccine hesitant; they are the people we know are waiting for our vaccine. Never before have I seen a company and a product so closely followed, and that’s a big opportunity," Taylor said.

+

"There are people who want to know they can have a new option; they want to know who is making that option," she added. "So, these two education campaigns are set up to really help people understand that.”

+
+

+

Related

+

+
+

She added that people are telling the company directly that access to Nuvaxovid “will convince them to get their vaccine. So that’s the first target audience for us.”

+

That group not only includes people getting their first shots but also those who may need boosters but put them off because of concerns about mRNA.

+

And the choice goes both ways: Not only does Novavax want consumers to have a choice, they want to arm doctors with a different shot for their vaccine arsenal.

+

Novavax is also targeting the pediatric population. There are questions about how well mRNA vaccines work in younger children. There are also safety concerns, notably the rates of myocarditis in young and adolescent boys, who appear to be more at risk from this condition, which can cause dangerous inflammation of the heart.

+

Taylor believes Nuvaxovid can be a safe and efficacious second choice for children and adolescents outside of mRNA. “When you are talking to caregivers, there are certain considerations that are going to be front and center for them: So, tolerability and efficacy and the big question, how will it be tolerated by my child? That becomes very important, and that’s also the market we are starting to make inroads in," Taylor said.

+

The education program route is one well-traveled by pharmas. In this case, it allows Novavax to talk up vaccines—and itself—without running afoul of rules against branded advertising. And awareness campaigns help prime the pump ahead of what could be branded DTC campaigns if and when the shot wins full FDA approval.

+
+

+

Related

+

+
+

The "We Do Vaccines" program offers up educational information about common vaccine types and how they work, how vaccines are made and tested, and how Novavax believes its approach to technology makes its vaccines different.

+

It has an accompanying website that's a straightforward look at the different types of vaccine technologies and how they can help stop the spread of certain infectious diseases, from COVID to influenza. This particular campaign is aimed at consumers, Taylor said.

+

Novavax’s name is on the website, though not prominently, and it doesn’t directly talk about the COVID shot. But the site does link to a second site that dives much more deeply into the protein technology Novavax uses for the COVID vaccine, approved with the brand name Nuvaxovid in Europe. (The name hasn’t been confirmed in the U.S. yet.)

+

The "Know Our Vax" program, meanwhile, targets doctors and other healthcare professionals with educational information about Novavax, its global approach and technology. This campaign's website talks a little about Novavax itself and its history—and more about its vaccine tech and its pipeline, which includes work on other respiratory diseases. 

+

Both sites invite visitors to sign up for “vaccine updates” from the company. Novavax said it used an agency to create the campaigns, though it did not name which one.

+

Pfizer and Moderna have both been relatively quiet on the marketing front. Neither want to talk to journalists about their marketing or education plans (at least this one). Marketing wasn't allowed while they were sold under emergency authorization, but now that they have full FDA approval, they can. Still, Pfizer has over the past four months been releasing a series of new DTC ads similar in tone to what Novavax is doing.

+

In its first series of ads, which first aired late last year, Pfizer doesn’t mention the words “COVID-19” or “vaccine,” but rather takes the viewer to “the pursuit of normal” and features the deliciously mundane aspects of everyday life that vaccines have allowed to return.

+
+

+

Related

+

+
+

While nearing the finish line in the U.S., Novavax still has some way to go to actually cross it. First, the biopharma has been around 34 years now, but until last year, never saw a drug authorized or approved. As it nears a possible green light in its native U.S., that’s a lot of pressure for management to deliver.

+

And it’s struggled to get here: Manufacturing issues have hampered delivery of its vaccine, with the company reportedly struggling to meet quality standards. It has since said it has cleared up any remaining issues with the FDA in a recent interview with The Wall Street Journal.

+

Novavax is also preparing for a full BLA filing in the second half of the year; should it win that approval, it can really hit the gas on its DTC plans. Taylor said Novavax isn’t thinking too far ahead in terms of marketing after an approval, saying they “are solely focused on delivering these new campaigns and our vaccine around the world.”

+
+ + +
+
\ No newline at end of file diff --git a/packages/readabilityjs/test/test-pages/fiercepharma/source.html b/packages/readabilityjs/test/test-pages/fiercepharma/source.html new file mode 100644 index 000000000..1e740e124 --- /dev/null +++ b/packages/readabilityjs/test/test-pages/fiercepharma/source.html @@ -0,0 +1,1575 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Novavax, eyeing the COVID 'vaccine hesitant' and kids, unveils new education campaigns as Nuvaxovid nears US finish line | Fierce Pharma + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+ Marketing +
+
+
+
+
+
+
+

+ Novavax, eyeing the COVID 'vaccine hesitant' and kids, unveils new education campaigns as Nuvaxovid nears US finish line +

+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ Novavax +
+
+ Novavax is betting its reliance on traditional vaccine technology can bring several missed population groups back on board for their COVID shots. (Novavax ) +
+
+
+
+
+
+
+ +
+

+ Pfizer, Moderna and Johnson & Johnson were quickest off the mark in getting COVID vaccines into American arms, but Novavax is hoping to add another pandemic vaccine to the U.S. mix soon—and it's pushing new campaigns to get the word out. +

+
+
+
+
+ +
+
+
+
+

+ The biopharma, which has approvals and authorizations in Europe and around the world, is now on the cusp of a potential green light in the U.S. And with a market comes the need for marketing. +

+

+ But because it still has no U.S. approval—and it cannot under law advertise to consumers in Europe—Novavax is launching two new global, unbranded vaccine education programs: "We Do Vaccines" and "Know Our Vax." They're designed to offer up vaccine information and "explain Novavax’ commitment to vaccine development and innovation,” the company told Fierce Pharma Marketing. +

+
+
+
+

+ Webinar: Tuesday, may 10, 2022 | 2Pm ET / 11am PT +

+

+ Improving Commercial Effectiveness at Novartis +

+

+ Sponsored by Snowflake +

+

+ Join this session to learn how Novartis is using Snowflake's Data Cloud to achieve interoperability across multitudes of vendors, systems, and data sources, empower innovation and improve effectiveness for analytics and data science teams by removing data bottlenecks and barriers to insight, and enable a global data strategy by leveraging multi-cloud capabilities. +

Register Now +
+
+

+ The main message of the campaign is that “people have options when it comes to their vaccine,” Silvia Taylor, senior vice president of global corporate affairs at Novavax, said in an interview. “We want people to understand that we have this vaccine, and that this vaccine is different.” +

+ +

+ Novavax knows it has some tough competition—Pfizer and Moderna's vaccines dominate the U.S. market—but the small biotech is eyeing certain market niches: The "vaccine hesitant" who might be leery of the brand-new mRNA tech in Pfizer and Moderna's shots, and children. And it does have a strong, vocal following online that's eagerly awaiting a U.S. decision. +

+

+ Nuvaxovid taps older tech that's been used in influenza shots and others for decades. The vaccine contains a version of the SARS-CoV-2 spike protein made in the lab as well as an adjuvant, which is a booster ingredient designed to strengthen immune responses to the vaccine. +

+
+

+ A new option +

+

+ Pfizer and Moderna's shots obviously weren't the only two vaccines in the U.S.: Johnson & Johnson’s single-dose vaccine alternative also uses older vaccine technology. But it fell out of favor amid weakening efficacy and major manufacturing issues. Then, late last year, a Centers for Disease Control and Prevention panel recommended it should be sidelined because of serious safety concerns. +

+

+ AstraZeneca's COVID vaccine—which itself uses more traditional vaccine technology—hasn’t been approved in the U.S.  +

+

+ Enter Novavax, now looking to position its vaccine as an mRNA alternative. +

+

+ People hesitant about vaccinations may not want an mRNA vaccine because it's new technology, without years of proven safety behind it. But they might use an older, “tried and tested” tech, as Novavax puts it.   +

+
+
+
+
+ digital pharma east logo +
+
+
+
+

+ Fierce Event +

+

+ Register now for early-bird savings! +

+
+
+ + +

+ October 18-20, 2022 +

+
+
+ + +

+ Philadelphia, PA +

+
+
+
+
+ Register +
+
+
+
+

+ “There’s a recognition; a familiarity with this type of [protein-based] vaccine technology that many are comfortable with, and would have had with HPV, shingles and flu shots,” Taylor said. +

+

+ “There is a segment of the population that are the so-called vaccine hesitant; they are the people we know are waiting for our vaccine. Never before have I seen a company and a product so closely followed, and that’s a big opportunity," Taylor said. +

+

+ "There are people who want to know they can have a new option; they want to know who is making that option," she added. "So, these two education campaigns are set up to really help people understand that.” +

+ +

+ She added that people are telling the company directly that access to Nuvaxovid “will convince them to get their vaccine. So that’s the first target audience for us.” +

+

+ That group not only includes people getting their first shots but also those who may need boosters but put them off because of concerns about mRNA. +

+

+ And the choice goes both ways: Not only does Novavax want consumers to have a choice, they want to arm doctors with a different shot for their vaccine arsenal. +

+

+ Novavax is also targeting the pediatric population. There are questions about how well mRNA vaccines work in younger children. There are also safety concerns, notably the rates of myocarditis in young and adolescent boys, who appear to be more at risk from this condition, which can cause dangerous inflammation of the heart. +

+

+ Taylor believes Nuvaxovid can be a safe and efficacious second choice for children and adolescents outside of mRNA. “When you are talking to caregivers, there are certain considerations that are going to be front and center for them: So, tolerability and efficacy and the big question, how will it be tolerated by my child? That becomes very important, and that’s also the market we are starting to make inroads in," Taylor said. +

+

+ The education program route is one well-traveled by pharmas. In this case, it allows Novavax to talk up vaccines—and itself—without running afoul of rules against branded advertising. And awareness campaigns help prime the pump ahead of what could be branded DTC campaigns if and when the shot wins full FDA approval. +

+ +

+ The "We Do Vaccines" program offers up educational information about common vaccine types and how they work, how vaccines are made and tested, and how Novavax believes its approach to technology makes its vaccines different. +

+

+ It has an accompanying website that's a straightforward look at the different types of vaccine technologies and how they can help stop the spread of certain infectious diseases, from COVID to influenza. This particular campaign is aimed at consumers, Taylor said. +

+

+ Novavax’s name is on the website, though not prominently, and it doesn’t directly talk about the COVID shot. But the site does link to a second site that dives much more deeply into the protein technology Novavax uses for the COVID vaccine, approved with the brand name Nuvaxovid in Europe. (The name hasn’t been confirmed in the U.S. yet.) +

+

+ The "Know Our Vax" program, meanwhile, targets doctors and other healthcare professionals with educational information about Novavax, its global approach and technology. This campaign's website talks a little about Novavax itself and its history—and more about its vaccine tech and its pipeline, which includes work on other respiratory diseases.  +

+

+ Both sites invite visitors to sign up for “vaccine updates” from the company. Novavax said it used an agency to create the campaigns, though it did not name which one. +

+

+ Pfizer and Moderna have both been relatively quiet on the marketing front. Neither want to talk to journalists about their marketing or education plans (at least this one). Marketing wasn't allowed while they were sold under emergency authorization, but now that they have full FDA approval, they can. Still, Pfizer has over the past four months been releasing a series of new DTC ads similar in tone to what Novavax is doing. +

+

+ In its first series of ads, which first aired late last year, Pfizer doesn’t mention the words “COVID-19” or “vaccine,” but rather takes the viewer to “the pursuit of normal” and features the deliciously mundane aspects of everyday life that vaccines have allowed to return. +

+ +

+ While nearing the finish line in the U.S., Novavax still has some way to go to actually cross it. First, the biopharma has been around 34 years now, but until last year, never saw a drug authorized or approved. As it nears a possible green light in its native U.S., that’s a lot of pressure for management to deliver. +

+

+ And it’s struggled to get here: Manufacturing issues have hampered delivery of its vaccine, with the company reportedly struggling to meet quality standards. It has since said it has cleared up any remaining issues with the FDA in a recent interview with The Wall Street Journal. +

+

+ Novavax is also preparing for a full BLA filing in the second half of the year; should it win that approval, it can really hit the gas on its DTC plans. Taylor said Novavax isn’t thinking too far ahead in terms of marketing after an approval, saying they “are solely focused on delivering these new campaigns and our vaccine around the world.” +

+
+
+ +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+
+ + +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+
+ + +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+
+ + +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_~ +
+ + + + diff --git a/packages/readabilityjs/test/test-pages/fiercepharma/url.txt b/packages/readabilityjs/test/test-pages/fiercepharma/url.txt new file mode 100644 index 000000000..6dad2fe5a --- /dev/null +++ b/packages/readabilityjs/test/test-pages/fiercepharma/url.txt @@ -0,0 +1 @@ +https://www.fiercepharma.com/marketing/novavax-eyeing-covid-vaccine-hesitant-and-kids-unveils-new-education-campaigns-nuvaxovid \ No newline at end of file From 4279bed74df5e6eb965e5cbd8f923def15078580 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 4 May 2022 14:48:12 -0700 Subject: [PATCH 03/31] show progress view on web reader if content doesn't load in one second --- .../App/Views/WebReader/WebReaderContainer.swift | 12 ++++++++++-- .../App/Views/WebReader/WebReaderViewModel.swift | 7 ++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index c8e26c311..182f3aca2 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -15,6 +15,7 @@ import WebKit @State private var navBarVisibilityRatio = 1.0 @State private var showDeleteConfirmation = false @State private var showOverlay = true + @State private var progressViewOpacity = 0.0 @State var increaseFontActionID: UUID? @State var decreaseFontActionID: UUID? @State var annotationSaveTransactionID: UUID? @@ -186,9 +187,16 @@ import WebKit } ) } + } else if let errorMessage = viewModel.errorMessage { + Text(errorMessage).padding() } else { - Text(viewModel.contentFetchFailed ? "Unable to fetch content." : "Processing...") - .padding() + ProgressView() + .opacity(progressViewOpacity) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) { + progressViewOpacity = 1 + } + } .task { await viewModel.loadContent(dataService: dataService, itemID: item.unwrappedID) } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift index 97017cfd8..2daef7731 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift @@ -10,15 +10,16 @@ struct SafariWebLink: Identifiable { @MainActor final class WebReaderViewModel: ObservableObject { @Published var articleContent: ArticleContent? - @Published var contentFetchFailed = false + @Published var errorMessage: String? func loadContent(dataService: DataService, itemID: String) async { - contentFetchFailed = false + errorMessage = nil do { articleContent = try await dataService.fetchArticleContent(itemID: itemID) } catch { - contentFetchFailed = true + // TODO: check if this is a network or parsing error + errorMessage = "Unable to extract your content." } } From 74693d40c06098f7a90cfbe13dd3d055effb3ad2 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 4 May 2022 15:01:35 -0700 Subject: [PATCH 04/31] Mark page as readerable --- .../test/test-pages/fiercepharma/expected-metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/readabilityjs/test/test-pages/fiercepharma/expected-metadata.json b/packages/readabilityjs/test/test-pages/fiercepharma/expected-metadata.json index 60b1d61ef..44d47041b 100644 --- a/packages/readabilityjs/test/test-pages/fiercepharma/expected-metadata.json +++ b/packages/readabilityjs/test/test-pages/fiercepharma/expected-metadata.json @@ -7,5 +7,5 @@ "siteIcon": "https://qtxasset.com/quartz/qcloud5/fiercefavicon.ico", "previewImage": "https://qtxasset.com/quartz/qcloud5/media/image/Novavaximage.png?VersionId=f9UDAFMgagg4Y_igFXWmnc3TJKOoTl_k", "publishedDate": "2022-03-10T13:30:00.000Z", - "readerable": false + "readerable": true } From a2a7a2a232507488a43090d183a08f236826f4a4 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 4 May 2022 15:20:44 -0700 Subject: [PATCH 05/31] handle content fetch errors in web reader --- .../Views/WebReader/WebReaderViewModel.swift | 12 +++++-- .../Models/ErrorModels/SaveArticleError.swift | 2 ++ .../Queries/ArticleContentQuery.swift | 34 ++++++++++--------- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift index 2daef7731..1a03c96dd 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift @@ -18,8 +18,16 @@ struct SafariWebLink: Identifiable { do { articleContent = try await dataService.fetchArticleContent(itemID: itemID) } catch { - // TODO: check if this is a network or parsing error - errorMessage = "Unable to extract your content." + if let fetchError = error as? ContentFetchError { + switch fetchError { + case .network: + errorMessage = "We were unable to retrieve your content. Please ccheck network connectivity and try again." + default: + errorMessage = "We were unable to parse your content." + } + } else { + errorMessage = "We were unable to retrieve your content." + } } } diff --git a/apple/OmnivoreKit/Sources/Models/ErrorModels/SaveArticleError.swift b/apple/OmnivoreKit/Sources/Models/ErrorModels/SaveArticleError.swift index adbb15246..3c806bb3c 100644 --- a/apple/OmnivoreKit/Sources/Models/ErrorModels/SaveArticleError.swift +++ b/apple/OmnivoreKit/Sources/Models/ErrorModels/SaveArticleError.swift @@ -1,5 +1,7 @@ import Foundation +public typealias ContentFetchError = SaveArticleError + public enum SaveArticleError: Error { case unauthorized case network diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift index f2beed079..a4dd8fec5 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift @@ -20,7 +20,7 @@ extension DataService { func prefetchPage(pendingLink: PendingLink, username: String) async { let content = try? await articleContent(username: username, itemID: pendingLink.itemID, useCache: false) - if content?.contentStatus == .processing, pendingLink.retryCount < 6 { + if content?.contentStatus == .processing, pendingLink.retryCount < 7 { let retryDelayInNanoSeconds = UInt64(pendingLink.retryCount * 2 * 1_000_000_000) do { @@ -45,26 +45,24 @@ extension DataService { username: String? = nil, requestCount: Int = 1 ) async throws -> ArticleContent { - guard let username = username ?? currentViewer?.username else { - throw BasicError.message(messageText: "username could not be fetched from core data") + guard requestCount < 7 else { + throw ContentFetchError.badData } - guard let fetchedContent = try? await articleContent(username: username, itemID: itemID, useCache: true) else { - throw BasicError.message(messageText: "networking error") + guard let username = username ?? currentViewer?.username else { + throw ContentFetchError.unauthorized } + let fetchedContent = try await articleContent(username: username, itemID: itemID, useCache: true) + switch fetchedContent.contentStatus { case .failed: - throw BasicError.message(messageText: "content processing failed") + throw ContentFetchError.badData case .processing: - do { - let retryDelayInNanoSeconds = UInt64(requestCount * 2 * 1_000_000_000) - try await Task.sleep(nanoseconds: retryDelayInNanoSeconds) - logger.debug("fetching content for \(itemID). request count: \(requestCount)") - return try await fetchArticleContent(itemID: itemID, username: username, requestCount: requestCount + 1) - } catch { - throw BasicError.message(messageText: "content fetch failed") - } + let retryDelayInNanoSeconds = UInt64(requestCount * 2 * 1_000_000_000) + try await Task.sleep(nanoseconds: retryDelayInNanoSeconds) + logger.debug("fetching content for \(itemID). request count: \(requestCount)") + return try await fetchArticleContent(itemID: itemID, username: username, requestCount: requestCount + 1) case .succeeded, .unknown: return fetchedContent } @@ -120,7 +118,7 @@ extension DataService { return try await withCheckedThrowingContinuation { continuation in send(query, to: path, headers: headers) { [weak self] queryResult in guard let payload = try? queryResult.get() else { - continuation.resume(throwing: BasicError.message(messageText: "network error")) + continuation.resume(throwing: ContentFetchError.network) return } @@ -129,6 +127,10 @@ extension DataService { // Default to suceeded since older links will return a nil status // (but the content is almost always there) let status = result.contentStatus ?? .succeeded + if status == .failed { + continuation.resume(throwing: ContentFetchError.badData) + return + } if status == .succeeded { self?.persistArticleContent( @@ -146,7 +148,7 @@ extension DataService { continuation.resume(returning: articleContent) case .error: - continuation.resume(throwing: BasicError.message(messageText: "LinkedItem fetch error")) + continuation.resume(throwing: ContentFetchError.badData) } } } From 4e5377d7f45f9e15fa03c468949cde43281a239b Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 4 May 2022 15:22:21 -0700 Subject: [PATCH 06/31] remove web reader loading overlay --- .../App/Views/WebReader/WebReaderContainer.swift | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index 182f3aca2..2a102607a 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -14,7 +14,6 @@ import WebKit @State var safariWebLink: SafariWebLink? @State private var navBarVisibilityRatio = 1.0 @State private var showDeleteConfirmation = false - @State private var showOverlay = true @State private var progressViewOpacity = 0.0 @State var increaseFontActionID: UUID? @State var decreaseFontActionID: UUID? @@ -157,21 +156,6 @@ import WebKit annotationSaveTransactionID: $annotationSaveTransactionID, annotation: $annotation ) - .overlay( - Group { - if showOverlay { - Color.systemBackground - .transition(.opacity) - .onAppear { - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { - withAnimation(.linear(duration: 0.2)) { - showOverlay = false - } - } - } - } - } - ) .sheet(item: $safariWebLink) { SafariView(url: $0.url) } From 96fbd84bbb3e7e9d2264b26ef150977be462f350 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 5 May 2022 09:02:56 +0800 Subject: [PATCH 07/31] Extend timeout --- packages/db/migrate.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/db/migrate.ts b/packages/db/migrate.ts index 42f4682d8..f1134d400 100755 --- a/packages/db/migrate.ts +++ b/packages/db/migrate.ts @@ -135,7 +135,8 @@ esClient index: INDEX_ALIAS, requests_per_second: 10, scroll: '30s', - scroll_size: 500, + scroll_size: 1000, + timeout: '30m', body: { script: { source: 'ctx._source.state = params.state', From f47e2754c2c04a9b11e7f3f7976c780ae9ee7b97 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 5 May 2022 09:54:52 +0800 Subject: [PATCH 08/31] Add state in search result graphql --- .../web/lib/networking/queries/useGetLibraryItemsQuery.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx index 3cc484de8..9d016290a 100644 --- a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx +++ b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx @@ -1,12 +1,12 @@ import { gql } from 'graphql-request' import useSWRInfinite from 'swr/infinite' import { gqlFetcher } from '../networkHelpers' -import type { ArticleFragmentData, PageType, State } from '../fragments/articleFragment' +import type { PageType, State } from '../fragments/articleFragment' import { ContentReader } from '../fragments/articleFragment' import { setLinkArchivedMutation } from '../mutations/setLinkArchivedMutation' import { deleteLinkMutation } from '../mutations/deleteLinkMutation' import { articleReadingProgressMutation } from '../mutations/articleReadingProgressMutation' -import { Label, labelFragment } from './../fragments/labelFragment' +import { Label } from './../fragments/labelFragment' import { showErrorToast, showSuccessToast } from '../../toastHelpers' export type LibraryItemsQueryInput = { @@ -124,6 +124,7 @@ export function useGetLibraryItemsQuery({ shortId quote annotation + state } } pageInfo { From 6f29f1874311bd68eaa2120cb3ef75c618b38248 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 5 May 2022 12:13:08 +0800 Subject: [PATCH 09/31] Parse image and save it in a element --- packages/puppeteer-parse/image-handler.js | 35 +++++++++++++++++++++++ packages/puppeteer-parse/index.js | 2 ++ 2 files changed, 37 insertions(+) create mode 100644 packages/puppeteer-parse/image-handler.js diff --git a/packages/puppeteer-parse/image-handler.js b/packages/puppeteer-parse/image-handler.js new file mode 100644 index 000000000..df0a2e6e1 --- /dev/null +++ b/packages/puppeteer-parse/image-handler.js @@ -0,0 +1,35 @@ +/* eslint-disable no-undef */ +/* eslint-disable no-empty */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ +require('dotenv').config(); + + +exports.imageHandler = { + shouldPrehandle: (url, env) => { + const IMAGE_URL_PATTERN = + /(https?:\/\/.*\.(?:jpg|jpeg|png|webp))/i + return IMAGE_URL_PATTERN.test(url.toString()) + }, + + prehandle: async (url, env) => { + const title = url.toString().split('/').pop(); + const content = ` + + ${title} + + + + +
+ Image: +
+ ${title} +
+ + ` + + return { title, content }; + } +} diff --git a/packages/puppeteer-parse/index.js b/packages/puppeteer-parse/index.js index fc8db66f6..c88e9f954 100644 --- a/packages/puppeteer-parse/index.js +++ b/packages/puppeteer-parse/index.js @@ -24,6 +24,7 @@ const { tDotCoHandler } = require('./t-dot-co-handler'); const { pdfHandler } = require('./pdf-handler'); const { mediumHandler } = require('./medium-handler'); const { derstandardHandler } = require('./derstandard-handler'); +const { imageHandler } = require('./image-handler'); const storage = new Storage(); const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : []; @@ -228,6 +229,7 @@ const handlers = { 't-dot-co': tDotCoHandler, 'medium': mediumHandler, 'derstandard': derstandardHandler, + 'image': imageHandler, }; /** From c4ea185e17231ef07a1f5bf6d78b311b19b7bf2e Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 5 May 2022 12:16:53 +0800 Subject: [PATCH 10/31] Add comment --- packages/puppeteer-parse/image-handler.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/puppeteer-parse/image-handler.js b/packages/puppeteer-parse/image-handler.js index df0a2e6e1..e9d3cdb1f 100644 --- a/packages/puppeteer-parse/image-handler.js +++ b/packages/puppeteer-parse/image-handler.js @@ -17,12 +17,14 @@ exports.imageHandler = { const title = url.toString().split('/').pop(); const content = ` - ${title} - - + + ${title} + +
+ Image:
${title} From 911afd77e567e6655c94b51c0a866fc1fa017295 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 5 May 2022 12:40:44 +0800 Subject: [PATCH 11/31] Reduce requests per second and scroll_size --- packages/db/migrate.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/db/migrate.ts b/packages/db/migrate.ts index f1134d400..4d739189c 100755 --- a/packages/db/migrate.ts +++ b/packages/db/migrate.ts @@ -133,9 +133,9 @@ log('Starting adding default state to pages in elasticsearch...') esClient .update_by_query({ index: INDEX_ALIAS, - requests_per_second: 10, + requests_per_second: 5, scroll: '30s', - scroll_size: 1000, + scroll_size: 500, timeout: '30m', body: { script: { From 7183ca2e896792743d6e23d76ec9f889a55b313e Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 5 May 2022 13:28:01 +0800 Subject: [PATCH 12/31] Add request timeout = 30m in elastic client --- packages/db/migrate.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/db/migrate.ts b/packages/db/migrate.ts index 4d739189c..461f3399f 100755 --- a/packages/db/migrate.ts +++ b/packages/db/migrate.ts @@ -89,6 +89,7 @@ const logAppliedMigrations = ( export const INDEX_ALIAS = 'pages_alias' export const esClient = new Client({ node: process.env.ELASTIC_URL || 'http://localhost:9200', + requestTimeout: 60000 * 30, // 30 minutes auth: { username: process.env.ELASTIC_USERNAME || '', password: process.env.ELASTIC_PASSWORD || '', From 35e3e347d6695f6e98242ead503ed15b691649cb Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 5 May 2022 19:25:09 +0800 Subject: [PATCH 13/31] Remove image text --- packages/puppeteer-parse/image-handler.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/puppeteer-parse/image-handler.js b/packages/puppeteer-parse/image-handler.js index e9d3cdb1f..59f132afc 100644 --- a/packages/puppeteer-parse/image-handler.js +++ b/packages/puppeteer-parse/image-handler.js @@ -24,9 +24,6 @@ exports.imageHandler = {
- - Image: -
${title}
From 24373018af3abef3e2da58e458921ecd2b1d6ae9 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 5 May 2022 19:26:39 +0800 Subject: [PATCH 14/31] Return non-text elements if no text content found after parsing in readability --- packages/readabilityjs/Readability.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/readabilityjs/Readability.js b/packages/readabilityjs/Readability.js index 683f500d4..714f44fa4 100644 --- a/packages/readabilityjs/Readability.js +++ b/packages/readabilityjs/Readability.js @@ -1579,7 +1579,7 @@ Readability.prototype = { }); // But first check if we actually have something - if (!this._attempts[0].textLength) { + if (!this._attempts[0].textLength && !this._attempts[0].articleContent) { return null; } From b9c2c1186b1385f5cc3f39e58f76d8f214e8c440 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 5 May 2022 21:08:27 +0800 Subject: [PATCH 15/31] Fix batch_size=500 to avoid exceeding max memory limit and requests_per_second=250 to throttle update requests Fix batch_size=500 to avoid exceeding max memory limit and requests_per_second=250 to throttle update requests --- packages/db/migrate.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/db/migrate.ts b/packages/db/migrate.ts index 461f3399f..4359959b6 100755 --- a/packages/db/migrate.ts +++ b/packages/db/migrate.ts @@ -134,8 +134,7 @@ log('Starting adding default state to pages in elasticsearch...') esClient .update_by_query({ index: INDEX_ALIAS, - requests_per_second: 5, - scroll: '30s', + requests_per_second: 250, scroll_size: 500, timeout: '30m', body: { From 384c5dbf9fa74356c7d63d7d56dfe368b8812b93 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 5 May 2022 09:14:51 -0700 Subject: [PATCH 16/31] Improve rendering of the Financial Times --- packages/readabilityjs/Readability.js | 4 +-- .../test-pages/ft.com/expected-metadata.json | 11 ++++++++ .../test/test-pages/ft.com/expected.html | 27 +++++++++++++++++++ .../test/test-pages/ft.com/source.html | 1 + .../test/test-pages/ft.com/url.txt | 1 + 5 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 packages/readabilityjs/test/test-pages/ft.com/expected-metadata.json create mode 100644 packages/readabilityjs/test/test-pages/ft.com/expected.html create mode 100644 packages/readabilityjs/test/test-pages/ft.com/source.html create mode 100644 packages/readabilityjs/test/test-pages/ft.com/url.txt diff --git a/packages/readabilityjs/Readability.js b/packages/readabilityjs/Readability.js index 714f44fa4..bdaa3f029 100644 --- a/packages/readabilityjs/Readability.js +++ b/packages/readabilityjs/Readability.js @@ -167,8 +167,8 @@ Readability.prototype = { // NOTE: These two regular expressions are duplicated in // Readability-readerable.js. Please keep both copies in sync. articleNegativeLookBehindCandidates: /breadcrumbs|breadcrumb|utils|trilist/i, - articleNegativeLookAheadCandidates: /outstream(.?)_|sub(.?)_|m_|omeda-promo-/i, - unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|breadcrumb|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager(?!ow)|popup|yom-remote|copyright|keywords|outline|infinite-list|beta|recirculation|site-index|hide-for-print|post-end-share-cta|post-end-cta-full|post-footer|main-navigation|programtic-ads|outstream_article|hfeed|comment-holder|back-to-top|show-up-next/i, + articleNegativeLookAheadCandidates: /outstream(.?)_|sub(.?)_|m_|omeda-promo-|in-article-advert/i, + unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|breadcrumb|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager(?!ow)|popup|yom-remote|copyright|keywords|outline|infinite-list|beta|recirculation|site-index|hide-for-print|post-end-share-cta|post-end-cta-full|post-footer|main-navigation|programtic-ads|outstream_article|hfeed|comment-holder|back-to-top|show-up-next|onward-journey|topic-tracker/i, // okMaybeItsACandidate: /and|article(?!-breadcrumb)|body|column|content|main|shadow|post-header/i, get okMaybeItsACandidate() { return new RegExp(`and|(? +
+

Tumble in tech stocks deals fresh blow to Chase Coleman’s flagship hedge fund

+
+
+
+
Tiger Global logo on a smartphone +
Tiger Global’s hedge fund lost 15.2% in April, according to a letter to investors, taking it down 43.7% in the first four months of 2022 © Timon Schneider/Alamy
+
+
+
+

Tiger Global’s flagship hedge fund was dealt a fresh blow in April and is down more than 40 per cent this year, in the latest sign of how star investors who rode the big rally in tech stocks have been wrongfooted by a sharp pullback.

+

The losses marked a dramatic fall from grace for Tiger Global’s founder Chase Coleman, who has emerged as one of the world’s most prominent growth investors after founding the firm in 2001.

+

Tiger Global’s hedge fund lost 15.2 per cent in April, according to a person familiar with the matter, taking it down 43.7 per cent in the first four months of 2022. This year’s losses and a 7 per cent reversal in 2021 mean that the Tiger Global hedge fund’s gain of 48 per cent in 2020 has been completely erased.

+

The group’s long-only fund lost 24.9 per cent in April and is down 51.7 per cent in 2022, the person said. Across the two funds, the firm managed about $35bn in public equities at the end of 2021. Tiger Global declined to comment on the performance numbers, which were first reported by Bloomberg.

+

Last month was a miserable one for many hedge funds, with both global bond and stock markets losing money as investors fretted about high inflation pushing central banks into an aggressive interest rate hiking cycle.

+

The so-called Tiger Cub hedge funds, spawned from Julian Robertson’s Tiger Management and big investors in tech stocks, have been hit particularly hard in recent months as a boom in high-growth technology stocks that was accelerated by the pandemic has turned into a bear market. This has put the brakes on one of the most lucrative trades in recent years.

+

The Nasdaq Composite lost 13.3 per cent in April, its worst monthly performance since 2008. Despite a bounce in recent days, the tech-heavy benchmark has fallen almost 22 per cent since its November peak.

+

In a brief letter to investors, the Tiger Global investment team said: “April added to a very disappointing start to 2022 for our public funds. Markets have not been co-operative given the macroeconomic backdrop, but we do not believe in excuses and so will not offer any.”

+

The letter added that the team was managing the portfolio in the ways it described in its first-quarter letter. “[We] know we will look back on this as one point in time on a long journey,” it said.

+

By the start of last year, Coleman was ranked the 14th best-performing hedge fund manager ever after a bumper year in 2020 in which he made $10.4bn of gains for investors, according to research by LCH Investments. But a bruising few months meant he lost $1.5bn for investors last year, pushing him down the rankings even before this year’s fall.

+

This year’s losses come as expectations of a sharp rise in interest rates have pushed investors out of stocks with high rates of growth but little in the way of earnings. Higher interest rates make such companies’ future cash flows look relatively less attractive.

+

Other high-profile casualties among growth investors include Baillie Gifford’s Scottish Mortgage Investment Trust and Cathie Wood’s flagship Ark Innovation ETF, both of which have nursed big losses in the past 12 months.

+

Tiger Global is also a prolific investor in private markets and holds stakes in more billion-dollar private start-ups than any other firm, according to CB Insights. It has recently gained notoriety for something else: a fast-paced style of investing that has unsettled the clubby ranks of Silicon Valley venture capitalists.

+
+
+
\ No newline at end of file diff --git a/packages/readabilityjs/test/test-pages/ft.com/source.html b/packages/readabilityjs/test/test-pages/ft.com/source.html new file mode 100644 index 000000000..3cb0a6ca5 --- /dev/null +++ b/packages/readabilityjs/test/test-pages/ft.com/source.html @@ -0,0 +1 @@ +Tiger Global slumps more than 40% in first four months of 2022 | Financial Times
Accessibility helpSkip to navigationSkip to contentSkip to footer

Save 33% on our annual subscription

Special offer on our Standard Digital access, including:

  • Opinion & Analysis from leading business experts
  • Market-moving news, politics, tech, the arts and more
  • Disrupted Times: our three-times a week newsletter focused on business and the economy between Covid and conflict

Save 33% on our annual subscription

Special offer on our Standard Digital access.

Your guide to a disrupted world

Tiger Global slumps more than 40% in first four months of 2022

Tumble in tech stocks deals fresh blow to Chase Coleman’s flagship hedge fund
Tiger Global logo on a smartphone
Tiger Global’s hedge fund lost 15.2% in April, according to a letter to investors, taking it down 43.7% in the first four months of 2022 © Timon Schneider/Alamy

Tiger Global’s flagship hedge fund was dealt a fresh blow in April and is down more than 40 per cent this year, in the latest sign of how star investors who rode the big rally in tech stocks have been wrongfooted by a sharp pullback.

The losses marked a dramatic fall from grace for Tiger Global’s founder Chase Coleman, who has emerged as one of the world’s most prominent growth investors after founding the firm in 2001.

Tiger Global’s hedge fund lost 15.2 per cent in April, according to a person familiar with the matter, taking it down 43.7 per cent in the first four months of 2022. This year’s losses and a 7 per cent reversal in 2021 mean that the Tiger Global hedge fund’s gain of 48 per cent in 2020 has been completely erased.

The group’s long-only fund lost 24.9 per cent in April and is down 51.7 per cent in 2022, the person said. Across the two funds, the firm managed about $35bn in public equities at the end of 2021. Tiger Global declined to comment on the performance numbers, which were first reported by Bloomberg.

Last month was a miserable one for many hedge funds, with both global bond and stock markets losing money as investors fretted about high inflation pushing central banks into an aggressive interest rate hiking cycle.

The so-called Tiger Cub hedge funds, spawned from Julian Robertson’s Tiger Management and big investors in tech stocks, have been hit particularly hard in recent months as a boom in high-growth technology stocks that was accelerated by the pandemic has turned into a bear market. This has put the brakes on one of the most lucrative trades in recent years.

The Nasdaq Composite lost 13.3 per cent in April, its worst monthly performance since 2008. Despite a bounce in recent days, the tech-heavy benchmark has fallen almost 22 per cent since its November peak.

In a brief letter to investors, the Tiger Global investment team said: “April added to a very disappointing start to 2022 for our public funds. Markets have not been co-operative given the macroeconomic backdrop, but we do not believe in excuses and so will not offer any.”

The letter added that the team was managing the portfolio in the ways it described in its first-quarter letter. “[We] know we will look back on this as one point in time on a long journey,” it said.

By the start of last year, Coleman was ranked the 14th best-performing hedge fund manager ever after a bumper year in 2020 in which he made $10.4bn of gains for investors, according to research by LCH Investments. But a bruising few months meant he lost $1.5bn for investors last year, pushing him down the rankings even before this year’s fall.

This year’s losses come as expectations of a sharp rise in interest rates have pushed investors out of stocks with high rates of growth but little in the way of earnings. Higher interest rates make such companies’ future cash flows look relatively less attractive.

Other high-profile casualties among growth investors include Baillie Gifford’s Scottish Mortgage Investment Trust and Cathie Wood’s flagship Ark Innovation ETF, both of which have nursed big losses in the past 12 months.

Tiger Global is also a prolific investor in private markets and holds stakes in more billion-dollar private start-ups than any other firm, according to CB Insights. It has recently gained notoriety for something else: a fast-paced style of investing that has unsettled the clubby ranks of Silicon Valley venture capitalists.

Event details and information

Investing in Space

Pan Pacific London

08 June - 09 June 2022

New horizons for sustainable growth

Copyright The Financial Times Limited 2022. All rights reserved.
Reuse this content (opens in new window) CommentsJump to comments section

Follow the topics in this article

Saving...
\ No newline at end of file diff --git a/packages/readabilityjs/test/test-pages/ft.com/url.txt b/packages/readabilityjs/test/test-pages/ft.com/url.txt new file mode 100644 index 000000000..49859a508 --- /dev/null +++ b/packages/readabilityjs/test/test-pages/ft.com/url.txt @@ -0,0 +1 @@ +https://on.ft.com/3LSG1iw \ No newline at end of file From 6ef14e1f9121ed3a8a686343ff7208449a5c6d5d Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 5 May 2022 09:28:22 -0700 Subject: [PATCH 17/31] Mark ft.com page a readerable --- .../readabilityjs/test/test-pages/ft.com/expected-metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/readabilityjs/test/test-pages/ft.com/expected-metadata.json b/packages/readabilityjs/test/test-pages/ft.com/expected-metadata.json index a0091f52b..a4f1da875 100644 --- a/packages/readabilityjs/test/test-pages/ft.com/expected-metadata.json +++ b/packages/readabilityjs/test/test-pages/ft.com/expected-metadata.json @@ -7,5 +7,5 @@ "siteIcon": "https://www.ft.com/__origami/service/image/v2/images/raw/ftlogo-v1%3Abrand-ft-logo-square-coloured?source=update-logos&format=svg", "previewImage": "https://d1e00ek4ebabms.cloudfront.net/production/fcdb88dd-1d9b-4b1a-b6ea-590293ae813f.jpg", "publishedDate": "2022-05-03T17:53:22.094Z", - "readerable": false + "readerable": true } From f50357d385134d1587fe114275d12ec2587b9b5c Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 5 May 2022 10:59:53 -0700 Subject: [PATCH 18/31] Add some padding and min cell height on the iOS list view --- .../Views/FeedItem/HomeFeedCardView.swift | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift index a38bebf6e..e7b90337f 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift @@ -12,12 +12,11 @@ public struct FeedCard: View { public var body: some View { VStack { HStack(alignment: .top, spacing: 6) { - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: 2) { Text(item.unwrappedTitle) - .font(.appSubheadline) + .font(.appCallout) .foregroundColor(.appGrayTextContrast) - .lineLimit(2) - .frame(maxWidth: .infinity, alignment: .leading) + .fixedSize(horizontal: false, vertical: true) if let author = item.author { Text("By \(author)") @@ -34,7 +33,13 @@ public struct FeedCard: View { .lineLimit(1) } } - .frame(maxWidth: .infinity) + .frame( + minWidth: 0, + maxWidth: .infinity, + minHeight: 0, + maxHeight: .infinity, + alignment: .topLeading + ) .multilineTextAlignment(.leading) .padding(0) @@ -52,7 +57,7 @@ public struct FeedCard: View { .frame(width: 80, height: 80) .cornerRadius(6) } else { - EmptyView() + EmptyView().frame(width: 80, height: 80, alignment: .top) } } } @@ -68,8 +73,19 @@ public struct FeedCard: View { Spacer() } } - .padding(.bottom, 5) + .padding(.top, 2) + .padding(.bottom, 2) } - .padding(.top, 5) + .padding(.top, 16) + .padding(.bottom, 8) + .frame( + minWidth: nil, + idealWidth: nil, + maxWidth: nil, + minHeight: 70, + idealHeight: nil, + maxHeight: nil, + alignment: .topLeading + ) } } From b53dae36a07f77499e2146652964ed5df1478751 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 5 May 2022 11:17:00 -0700 Subject: [PATCH 19/31] Enable saved search buttons for all users --- .../web/components/templates/homeFeed/HomeFeedContainer.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index 37c97210f..7d193e1a4 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -37,7 +37,6 @@ import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers' import { ConfirmationModal } from '../../patterns/ConfirmationModal' import { SetLabelsModal } from '../article/SetLabelsModal' import { Label } from '../../../lib/networking/fragments/labelFragment' -import { isVipUser } from '../../../lib/featureFlag' import { EmptyLibrary } from './EmptyLibrary' import TopBarProgress from 'react-topbar-progress-indicator' import { State, PageType } from '../../../lib/networking/fragments/articleFragment' @@ -601,7 +600,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { searchTerm={props.searchTerm} applySearchQuery={props.applySearchQuery} /> - {viewerData?.me && isVipUser(viewerData?.me) && ( + {viewerData?.me && ( Date: Thu, 5 May 2022 11:18:57 -0700 Subject: [PATCH 20/31] set isLoading to false after create label request completes --- .../OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift index 7870449e2..92aa9df2a 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift @@ -43,6 +43,7 @@ import Views color: color.hex ?? "", description: description ) else { + isLoading = false return } @@ -51,6 +52,7 @@ import Views unselectedLabels.insert(label, at: 0) } + isLoading = false showCreateEmailModal = false } From 6995cc29dab513ce220108a70ca73204b9ae6c29 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 5 May 2022 11:45:39 -0700 Subject: [PATCH 21/31] Add a little more spacing between lines and before labels --- .../OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift index e7b90337f..9bf95af7d 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift @@ -12,7 +12,7 @@ public struct FeedCard: View { public var body: some View { VStack { HStack(alignment: .top, spacing: 6) { - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: 4) { Text(item.unwrappedTitle) .font(.appCallout) .foregroundColor(.appGrayTextContrast) @@ -73,7 +73,7 @@ public struct FeedCard: View { Spacer() } } - .padding(.top, 2) + .padding(.top, 8) .padding(.bottom, 2) } .padding(.top, 16) From 1063e9e12b4cba5467276ed5cc0f857f6bf68c1b Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 5 May 2022 14:32:30 -0700 Subject: [PATCH 22/31] support grid style cards on iphone --- .../App/Views/Home/HomeFeedViewIOS.swift | 69 ++++++++++--------- .../Sources/App/Views/Home/HomeView.swift | 13 ---- 2 files changed, 35 insertions(+), 47 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 05c8e64bc..dffa4dd11 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -8,7 +8,7 @@ import Views #if os(iOS) struct HomeFeedContainerView: View { @EnvironmentObject var dataService: DataService - @AppStorage(UserDefaultKey.homeFeedlayoutPreference.rawValue) var prefersListLayout = UIDevice.isIPhone + @AppStorage(UserDefaultKey.homeFeedlayoutPreference.rawValue) var prefersListLayout = false @ObservedObject var viewModel: HomeFeedViewModel func loadItems(isRefresh: Bool) { @@ -49,6 +49,40 @@ import Views .sheet(item: $viewModel.itemUnderLabelEdit) { item in ApplyLabelsView(mode: .item(item), onSave: nil) } + .toolbar { + ToolbarItem(placement: .barTrailing) { + Button("", action: {}) + .disabled(true) + .overlay { + if viewModel.isLoading, !prefersListLayout { + ProgressView() + } + } + } + ToolbarItem(placement: UIDevice.isIPhone ? .barLeading : .barTrailing) { + Button( + action: { prefersListLayout.toggle() }, + label: { + Label("Toggle Feed Layout", systemImage: prefersListLayout ? "square.grid.2x2" : "list.bullet") + } + ) + } + ToolbarItem(placement: .barTrailing) { + if UIDevice.isIPhone { + NavigationLink( + destination: { ProfileView() }, + label: { + Image.profile + .resizable() + .frame(width: 26, height: 26) + .padding(.vertical) + } + ) + } else { + EmptyView() + } + } + } } .navigationTitle("Home") .navigationBarTitleDisplayMode(.inline) @@ -119,27 +153,6 @@ import Views HomeFeedListView(prefersListLayout: $prefersListLayout, viewModel: viewModel) } else { HomeFeedGridView(viewModel: viewModel) - .toolbar { - ToolbarItem { - Button("", action: {}) - .disabled(true) - .overlay { - if viewModel.isLoading { - ProgressView() - } - } - } - ToolbarItem { - if UIDevice.isIPad { - Button( - action: { prefersListLayout.toggle() }, - label: { - Label("Toggle Feed Layout", systemImage: prefersListLayout ? "square.grid.2x2" : "list.bullet") - } - ) - } - } - } } } } @@ -256,18 +269,6 @@ import Views } } .listStyle(PlainListStyle()) - .toolbar { - ToolbarItem { - if UIDevice.isIPad { - Button( - action: { prefersListLayout.toggle() }, - label: { - Label("Toggle Feed Layout", systemImage: prefersListLayout ? "square.grid.2x2" : "list.bullet") - } - ) - } - } - } } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift index 5c016d814..dcd22386a 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift @@ -8,19 +8,6 @@ struct HomeView: View { if UIDevice.isIPhone { NavigationView { HomeFeedContainerView(viewModel: viewModel) - .toolbar { - ToolbarItem { - NavigationLink( - destination: { ProfileView() }, - label: { - Image.profile - .resizable() - .frame(width: 26, height: 26) - .padding() - } - ) - } - } } .accentColor(.appGrayTextContrast) } else { From 1bda035890108f51ae4c553f32b62ad5db4307c2 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 5 May 2022 14:44:45 -0700 Subject: [PATCH 23/31] Make the highlight cards work a little better in list mode --- .../patterns/LibraryCards/HighlightItemCard.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/web/components/patterns/LibraryCards/HighlightItemCard.tsx b/packages/web/components/patterns/LibraryCards/HighlightItemCard.tsx index f3f685ab7..d493d8fd6 100644 --- a/packages/web/components/patterns/LibraryCards/HighlightItemCard.tsx +++ b/packages/web/components/patterns/LibraryCards/HighlightItemCard.tsx @@ -19,12 +19,13 @@ export function HighlightItemCard(props: HighlightItemCardProps): JSX.Element { css={{ p: '$2', height: '100%', - maxWidth: '498px', - borderRadius: '6px', + width: '100%', + borderRadius: '0px', cursor: 'pointer', wordBreak: 'break-word', overflow: 'clip', border: '1px solid $grayBorder', + borderBottom: 'none', boxShadow: '0px 3px 11px rgba(32, 31, 29, 0.04)', bg: '$grayBg', '&:focus': { @@ -49,6 +50,7 @@ export function HighlightItemCard(props: HighlightItemCardProps): JSX.Element { css={{ background: '$highlightBackground', color: '$highlightText', + fontSize: '14px', }} > {props.item.quote} @@ -75,13 +77,10 @@ export function HighlightItemCard(props: HighlightItemCardProps): JSX.Element { )} - {props.item.title - .substring(0, 50) - .concat(props.item.title.length > 50 ? '...' : '')} + {props.item.title} From 7c6b810522991ea470283063134c09f322328e8b Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 6 May 2022 10:53:36 +0800 Subject: [PATCH 24/31] Remove redundant JSDOM --- packages/api/src/resolvers/article/index.ts | 4 +--- packages/api/src/services/save_email.ts | 9 ++------- packages/api/src/services/save_page.ts | 6 ++---- packages/api/src/utils/parser.ts | 8 +++++--- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index b7865a5c6..1eee8e0ae 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -54,7 +54,6 @@ import { } from '../../utils/helpers' import { ParsedContentPuppeteer, - parseOriginalContent, parsePreparedContent, } from '../../utils/parser' import { isSiteBlockedForParse } from '../../utils/blocked' @@ -236,8 +235,7 @@ export const createArticleResolver = authorized< parsedContent = parseResults.parsedContent canonicalUrl = parseResults.canonicalUrl domContent = parseResults.domContent - - pageType = parseOriginalContent(url, domContent) + pageType = parseResults.pageType } else if (!preparedDocument?.document) { // We have a URL but no document, so we try to send this to puppeteer // and return a dummy response. diff --git a/packages/api/src/services/save_email.ts b/packages/api/src/services/save_email.ts index e62175c47..95ee5c576 100644 --- a/packages/api/src/services/save_email.ts +++ b/packages/api/src/services/save_email.ts @@ -1,9 +1,5 @@ import { generateSlug, stringToHash, validatedDate } from '../utils/helpers' -import { - parseOriginalContent, - parsePreparedContent, - parseUrlMetadata, -} from '../utils/parser' +import { parsePreparedContent, parseUrlMetadata } from '../utils/parser' import normalizeUrl from 'normalize-url' import { PubsubClient } from '../datalayer/pubsub' import { ArticleSavingRequestStatus, Page } from '../elastic/types' @@ -44,7 +40,6 @@ export const saveEmail = async ( const content = parseResult.parsedContent?.content || input.originalContent const slug = generateSlug(title) - const pageType = parseOriginalContent(url, input.originalContent) const metadata = await parseUrlMetadata(url) const articleToSave: Page = { @@ -60,7 +55,7 @@ export const saveEmail = async ( stripHash: true, stripWWW: false, }), - pageType: pageType, + pageType: parseResult.pageType, hash: stringToHash(content), image: metadata?.previewImage || parseResult.parsedContent?.previewImage, publishedAt: validatedDate(parseResult.parsedContent?.publishedDate), diff --git a/packages/api/src/services/save_page.ts b/packages/api/src/services/save_page.ts index e6fd79110..f56f95884 100644 --- a/packages/api/src/services/save_page.ts +++ b/packages/api/src/services/save_page.ts @@ -3,7 +3,7 @@ import { homePageURL } from '../env' import { Maybe, SavePageInput, SaveResult } from '../generated/graphql' import { DataModels } from '../resolvers/types' import { generateSlug, stringToHash, validatedDate } from '../utils/helpers' -import { parseOriginalContent, parsePreparedContent } from '../utils/parser' +import { parsePreparedContent } from '../utils/parser' import normalizeUrl from 'normalize-url' import { createPageSaveRequest } from './create_page_save_request' @@ -72,8 +72,6 @@ export const savePage = async ( }, }) - const pageType = parseOriginalContent(input.url, input.originalContent) - const articleToSave: Page = { id: input.clientRequestId, slug, @@ -87,7 +85,7 @@ export const savePage = async ( stripHash: true, stripWWW: false, }), - pageType: pageType, + pageType: parseResult.pageType, hash: stringToHash(parseResult.parsedContent?.content || input.url), image: parseResult.parsedContent?.previewImage, publishedAt: validatedDate(parseResult.parsedContent?.publishedDate), diff --git a/packages/api/src/utils/parser.ts b/packages/api/src/utils/parser.ts index 3e7de931f..94e8502cb 100644 --- a/packages/api/src/utils/parser.ts +++ b/packages/api/src/utils/parser.ts @@ -80,6 +80,7 @@ export type ParsedContentPuppeteer = { domContent: string parsedContent: Readability.ParseResult | null canonicalUrl?: string | null + pageType: PageType } /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -101,9 +102,8 @@ type ArticleParseLogRecord = LogRecord & { const DEBUG_MODE = process.env.DEBUG === 'true' || false -export const parseOriginalContent = (url: string, html: string): PageType => { +const parseOriginalContent = (window: DOMWindow): PageType => { try { - const { window } = new JSDOM(html, { url }) const e = window.document.querySelector("head meta[property='og:type']") const content = e?.getAttribute('content') if (!content) { @@ -121,7 +121,7 @@ export const parseOriginalContent = (url: string, html: string): PageType => { return PageType.Website } } catch (error) { - logger.error('Error extracting og:type from content for url', url, error) + logger.error('Error extracting og:type from content', error) } return PageType.Unknown @@ -232,6 +232,7 @@ export const parsePreparedContent = async ( canonicalUrl: url, parsedContent: null, domContent: preparedDocument.document, + pageType: PageType.Unknown, } } @@ -310,6 +311,7 @@ export const parsePreparedContent = async ( domContent: preparedDocument.document, parsedContent: article, canonicalUrl, + pageType: parseOriginalContent(window), } } From 2f4f24dd6be016d207a70030330dc97200b35d8d Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 5 May 2022 20:32:50 -0700 Subject: [PATCH 25/31] add a sortedLabels computed property on LinkedItem --- .../Sources/App/Views/Labels/LabelsViewModel.swift | 2 +- apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift | 6 ++++++ apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift | 2 +- .../Sources/Views/FeedItem/HomeFeedCardView.swift | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift index 92aa9df2a..e0a7aa062 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift @@ -22,7 +22,7 @@ import Views dataService.viewContext.performAndWait { self.labels = labelIDs.compactMap { dataService.viewContext.object(with: $0) as? LinkedItemLabel } } - let selLabels = initiallySelectedLabels ?? item?.labels.asArray(of: LinkedItemLabel.self) ?? [] + let selLabels = initiallySelectedLabels ?? item?.sortedLabels ?? [] for label in labels { if selLabels.contains(label) { selectedLabels.append(label) diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift index 1e1fd0363..c6b8421a3 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift @@ -58,6 +58,12 @@ public extension LinkedItem { return URL(string: pageURLString ?? "") } + var sortedLabels: [LinkedItemLabel] { + labels.asArray(of: LinkedItemLabel.self).sorted { + ($0.name ?? "").lowercased() < ($1.name ?? "").lowercased() + } + } + var labelsJSONString: String { let labels = self.labels.asArray(of: LinkedItemLabel.self).map { label in [ diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift index f9e5c49ba..bb2dc5c33 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift @@ -151,7 +151,7 @@ public struct GridCard: View { // Category Labels ScrollView(.horizontal, showsIndicators: false) { HStack { - ForEach(item.labels.asArray(of: LinkedItemLabel.self), id: \.self) { + ForEach(item.sortedLabels, id: \.self) { TextChip(feedItemLabel: $0) } Spacer() diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift index 9bf95af7d..afb3f2916 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift @@ -67,7 +67,7 @@ public struct FeedCard: View { // Category Labels ScrollView(.horizontal, showsIndicators: false) { HStack { - ForEach(item.labels.asArray(of: LinkedItemLabel.self), id: \.self) { + ForEach(item.sortedLabels, id: \.self) { TextChip(feedItemLabel: $0) } Spacer() From fd1e80b01db70100235fadd3c86fa7515309ee6f Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 5 May 2022 20:46:17 -0700 Subject: [PATCH 26/31] use Capsule rather than cornerRadius to create text chip --- apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift | 1 - apple/OmnivoreKit/Sources/Views/TextChip.swift | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift index bb2dc5c33..bd1f03cd1 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift @@ -156,7 +156,6 @@ public struct GridCard: View { } Spacer() } - .frame(height: 30) .padding(.horizontal) .padding(.bottom, 8) } diff --git a/apple/OmnivoreKit/Sources/Views/TextChip.swift b/apple/OmnivoreKit/Sources/Views/TextChip.swift index f318d7902..76b8f30a4 100644 --- a/apple/OmnivoreKit/Sources/Views/TextChip.swift +++ b/apple/OmnivoreKit/Sources/Views/TextChip.swift @@ -26,8 +26,7 @@ public struct TextChip: View { .font(.appFootnote) .foregroundColor(color.isDark ? .white : .black) .lineLimit(1) - .background(color) - .cornerRadius(cornerRadius) + .background(Capsule().fill(color)) } } From d74342950e92c14337a27c2cf905251ae63a2f20 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 5 May 2022 20:54:52 -0700 Subject: [PATCH 27/31] remove unused corner radius property --- apple/OmnivoreKit/Sources/Views/TextChip.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Views/TextChip.swift b/apple/OmnivoreKit/Sources/Views/TextChip.swift index 76b8f30a4..4e0ada9c4 100644 --- a/apple/OmnivoreKit/Sources/Views/TextChip.swift +++ b/apple/OmnivoreKit/Sources/Views/TextChip.swift @@ -17,7 +17,6 @@ public struct TextChip: View { let text: String let color: Color - let cornerRadius = 20.0 public var body: some View { Text(text) @@ -85,7 +84,6 @@ public struct TextChipButton: View { let color: Color let onTap: () -> Void let actionType: ActionType - let cornerRadius = 20.0 let foregroundColor: Color public var body: some View { @@ -101,8 +99,7 @@ public struct TextChipButton: View { .font(.appFootnote) .foregroundColor(foregroundColor) .lineLimit(1) - .background(color) - .cornerRadius(cornerRadius) + .background(Capsule().fill(color)) Color.clear.contentShape(Rectangle()).frame(height: 15) } From 5f5076e86410c853e35036fe828fbad0ab21a91a Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 6 May 2022 12:20:54 +0800 Subject: [PATCH 28/31] Highlight code element without reinitialize jsdom --- packages/api/src/readability.d.ts | 3 +++ packages/api/src/resolvers/article/index.ts | 2 +- packages/api/src/utils/parser.ts | 8 ++++---- packages/readabilityjs/Readability.js | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/api/src/readability.d.ts b/packages/api/src/readability.d.ts index b18e6d4ba..d6a0128ee 100644 --- a/packages/api/src/readability.d.ts +++ b/packages/api/src/readability.d.ts @@ -5,6 +5,8 @@ // TypeScript Version: 2.2 declare module '@omnivore/readability' { + import { DOMWindow } from 'jsdom' + /** * A standalone version of the readability library used for Firefox Reader View. * @@ -163,6 +165,7 @@ declare module '@omnivore/readability' { previewImage?: string /** Article published date */ publishedDate?: Date + window?: DOMWindow } } diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index 1eee8e0ae..7494c32ab 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -229,7 +229,7 @@ export const createArticleResolver = authorized< const parseResults = await traceAs>( { spanName: 'article.parse' }, async (): Promise => { - return await parsePreparedContent(url, preparedDocument) + return parsePreparedContent(url, preparedDocument) } ) parsedContent = parseResults.parsedContent diff --git a/packages/api/src/utils/parser.ts b/packages/api/src/utils/parser.ts index 94e8502cb..6680e2dd8 100644 --- a/packages/api/src/utils/parser.ts +++ b/packages/api/src/utils/parser.ts @@ -254,9 +254,9 @@ export const parsePreparedContent = async ( // Format code blocks // TODO: we probably want to move this type of thing // to the handlers, and have some concept of postHandle - if (article?.content) { - const cWindow = new JSDOM(article?.content).window - cWindow.document.querySelectorAll('code').forEach((e) => { + if (article?.window) { + // const cWindow = new JSDOM(article?.content).window + article.window.document.querySelectorAll('code').forEach((e) => { console.log(e.textContent) if (e.textContent) { const att = hljs.highlightAuto(e.textContent) @@ -271,7 +271,7 @@ export const parsePreparedContent = async ( e.replaceWith(code) } }) - article.content = cWindow.document.body.outerHTML + article.content = article.window.document.body.outerHTML } const newWindow = new JSDOM('').window diff --git a/packages/readabilityjs/Readability.js b/packages/readabilityjs/Readability.js index bdaa3f029..999f85cec 100644 --- a/packages/readabilityjs/Readability.js +++ b/packages/readabilityjs/Readability.js @@ -2899,6 +2899,7 @@ Readability.prototype = { siteIcon: metadata.siteIcon, previewImage: metadata.previewImage, publishedDate: metadata.publishedDate || publishedAt || this._articlePublishedDate, + window: articleContent, }; } }; From eaad96acddb903cebc6b09b24368dfeb3b01d6a8 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 6 May 2022 12:29:08 +0800 Subject: [PATCH 29/31] Return parsed dom back to backend --- packages/api/src/readability.d.ts | 4 +--- packages/api/src/utils/parser.ts | 7 +++---- packages/readabilityjs/Readability.js | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/api/src/readability.d.ts b/packages/api/src/readability.d.ts index d6a0128ee..54f8846ec 100644 --- a/packages/api/src/readability.d.ts +++ b/packages/api/src/readability.d.ts @@ -5,8 +5,6 @@ // TypeScript Version: 2.2 declare module '@omnivore/readability' { - import { DOMWindow } from 'jsdom' - /** * A standalone version of the readability library used for Firefox Reader View. * @@ -165,7 +163,7 @@ declare module '@omnivore/readability' { previewImage?: string /** Article published date */ publishedDate?: Date - window?: DOMWindow + dom?: Element } } diff --git a/packages/api/src/utils/parser.ts b/packages/api/src/utils/parser.ts index 6680e2dd8..2472988b2 100644 --- a/packages/api/src/utils/parser.ts +++ b/packages/api/src/utils/parser.ts @@ -254,9 +254,8 @@ export const parsePreparedContent = async ( // Format code blocks // TODO: we probably want to move this type of thing // to the handlers, and have some concept of postHandle - if (article?.window) { - // const cWindow = new JSDOM(article?.content).window - article.window.document.querySelectorAll('code').forEach((e) => { + if (article?.dom) { + article.dom.querySelectorAll('code').forEach((e) => { console.log(e.textContent) if (e.textContent) { const att = hljs.highlightAuto(e.textContent) @@ -271,7 +270,7 @@ export const parsePreparedContent = async ( e.replaceWith(code) } }) - article.content = article.window.document.body.outerHTML + article.content = article.dom.outerHTML } const newWindow = new JSDOM('').window diff --git a/packages/readabilityjs/Readability.js b/packages/readabilityjs/Readability.js index 999f85cec..d1bce8bdb 100644 --- a/packages/readabilityjs/Readability.js +++ b/packages/readabilityjs/Readability.js @@ -2899,7 +2899,7 @@ Readability.prototype = { siteIcon: metadata.siteIcon, previewImage: metadata.previewImage, publishedDate: metadata.publishedDate || publishedAt || this._articlePublishedDate, - window: articleContent, + dom: articleContent, }; } }; From 109d721ae33777f09c613a4de8b0488bda5ae6c3 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 6 May 2022 09:34:45 -0700 Subject: [PATCH 30/31] add feature falg to enable grid on iphone --- .../App/Views/Home/HomeFeedViewIOS.swift | 22 ++++++++++++------- .../Sources/Utils/FeatureFlags.swift | 1 + 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index dffa4dd11..9c1b17f0e 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -5,6 +5,8 @@ import UserNotifications import Utils import Views +private let enableGrid = UIDevice.isIPad || FeatureFlag.enableGridCardsOnPhone + #if os(iOS) struct HomeFeedContainerView: View { @EnvironmentObject var dataService: DataService @@ -54,18 +56,22 @@ import Views Button("", action: {}) .disabled(true) .overlay { - if viewModel.isLoading, !prefersListLayout { + if viewModel.isLoading, !prefersListLayout, enableGrid { ProgressView() } } } ToolbarItem(placement: UIDevice.isIPhone ? .barLeading : .barTrailing) { - Button( - action: { prefersListLayout.toggle() }, - label: { - Label("Toggle Feed Layout", systemImage: prefersListLayout ? "square.grid.2x2" : "list.bullet") - } - ) + if enableGrid { + Button( + action: { prefersListLayout.toggle() }, + label: { + Label("Toggle Feed Layout", systemImage: prefersListLayout ? "square.grid.2x2" : "list.bullet") + } + ) + } else { + EmptyView() + } } ToolbarItem(placement: .barTrailing) { if UIDevice.isIPhone { @@ -149,7 +155,7 @@ import Views } } } - if prefersListLayout { + if prefersListLayout || !enableGrid { HomeFeedListView(prefersListLayout: $prefersListLayout, viewModel: viewModel) } else { HomeFeedGridView(viewModel: viewModel) diff --git a/apple/OmnivoreKit/Sources/Utils/FeatureFlags.swift b/apple/OmnivoreKit/Sources/Utils/FeatureFlags.swift index 43385fcc4..9ce3b9eb1 100644 --- a/apple/OmnivoreKit/Sources/Utils/FeatureFlags.swift +++ b/apple/OmnivoreKit/Sources/Utils/FeatureFlags.swift @@ -14,4 +14,5 @@ public enum FeatureFlag { public static let enablePushNotifications = false public static let enableShareButton = false public static let enableSnooze = false + public static let enableGridCardsOnPhone = false } From c3dc5d6bc8c2bd8cef137a227d210d595121193d Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Sun, 8 May 2022 07:49:27 -0700 Subject: [PATCH 31/31] use stack navigation style for iphone --- apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift index dcd22386a..1fbbe840b 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift @@ -9,6 +9,7 @@ struct HomeView: View { NavigationView { HomeFeedContainerView(viewModel: viewModel) } + .navigationViewStyle(StackNavigationViewStyle()) .accentColor(.appGrayTextContrast) } else { HomeFeedContainerView(viewModel: viewModel)