diff --git a/packages/content-handler/src/index.ts b/packages/content-handler/src/index.ts index 69ce3f362..38d5608af 100644 --- a/packages/content-handler/src/index.ts +++ b/packages/content-handler/src/index.ts @@ -22,6 +22,7 @@ import { MorningBrewHandler } from './newsletters/morning-brew-handler' import { RevueHandler } from './newsletters/revue-handler' import { SubstackHandler } from './newsletters/substack-handler' import { AppleNewsHandler } from './websites/apple-news-handler' +import { ArsTechnicaHandler } from './websites/ars-technica-handler' import { BloombergHandler } from './websites/bloomberg-handler' import { DerstandardHandler } from './websites/derstandard-handler' import { GitHubHandler } from './websites/github-handler' @@ -33,11 +34,11 @@ import { PipedVideoHandler } from './websites/piped-video-handler' import { ScrapingBeeHandler } from './websites/scrapingBee-handler' import { StackOverflowHandler } from './websites/stack-overflow-handler' import { TDotCoHandler } from './websites/t-dot-co-handler' +import { TheAtlanticHandler } from './websites/the-atlantic-handler' import { WeixinQqHandler } from './websites/weixin-qq-handler' import { WikipediaHandler } from './websites/wikipedia-handler' import { YoutubeHandler } from './websites/youtube-handler' -import { TheAtlanticHandler } from './websites/the-atlantic-handler' -import { ArsTechnicaHandler } from './websites/ars-technica-handler' +import { ZhihuHandler } from './websites/zhihu-handler' const validateUrlString = (url: string): boolean => { const u = new URL(url) @@ -81,6 +82,7 @@ const contentHandlers: ContentHandler[] = [ new PipedVideoHandler(), new WeixinQqHandler(), new NitterHandler(), + new ZhihuHandler(), ] const newsletterHandlers: ContentHandler[] = [ diff --git a/packages/content-handler/src/websites/zhihu-handler.ts b/packages/content-handler/src/websites/zhihu-handler.ts new file mode 100644 index 000000000..a53573bf8 --- /dev/null +++ b/packages/content-handler/src/websites/zhihu-handler.ts @@ -0,0 +1,117 @@ +import { ContentHandler } from '../content-handler' + +export class ZhihuHandler extends ContentHandler { + constructor() { + super() + this.name = 'zhihu' + } + + parseQuestion(element: Element) { + const newQuestion = element.ownerDocument.createElement('div') + const question = element.querySelector(`.QuestionHeader-main`) + if (question) { + const votes = element + .querySelector(`div[itemprop='upvoteCount']`) + ?.getAttribute('data-value') + + if (votes) { + newQuestion.innerHTML = `

问题: ${votes} vote${ + votes === '1' ? '' : 's' + }

${question.innerHTML}` + } + } + return newQuestion + } + + parseComments(element: Element) { + const dom = element.ownerDocument + const newComments = dom.createElement('div') + + // comments + const commentsDiv = element.querySelector(`.comments`) + if (commentsDiv) { + const comments = commentsDiv.querySelectorAll(`.comment`) + if (comments.length > 0) { + newComments.innerHTML = `

Comments

` + + comments.forEach((comment) => { + const author = comment.querySelector(`.comment-user`) + const text = comment.querySelector(`.comment-copy`)?.textContent + const authorHref = author?.getAttribute('href') + const date = comment.querySelector(`.relativetime-clean`)?.textContent + if (author && text && authorHref && date) { + const newComment = dom.createElement('p') + newComment.innerHTML = `${author.innerHTML}: ${text} - ${date}` + newComments.appendChild(newComment) + } + }) + } + } + + return newComments + } + + parseAuthors(element: Element) { + const dom = element.ownerDocument + const newAuthors = dom.createElement('div') + + const authors = element.querySelectorAll(`.post-signature`) + authors.forEach((author) => { + const isOwner = author.classList.contains('owner') + const name = author.querySelector(`.user-details a`)?.textContent + const link = author.querySelector(`.user-details a`)?.getAttribute('href') + const reputation = author.querySelector(`.reputation-score`)?.textContent + const badges = Array.from( + author.querySelectorAll(`span[title*='badges']`) + ) + .map((badge) => badge.getAttribute('title')) + .join(', ') + const date = author.querySelector(`.user-action-time`)?.textContent + if (name && link && reputation && date) { + const newAuthor = dom.createElement('p') + newAuthor.innerHTML = `${name} - ${reputation} reputation - ${ + badges || 'no badge' + } - ${date}` + if (isOwner) { + const author = dom.createElement('span') + author.setAttribute('rel', 'author') + author.innerHTML = name + newAuthor.appendChild(author) + } + newAuthors.appendChild(newAuthor) + } + }) + + return newAuthors + } + + shouldPreParse(url: string, dom: Document): boolean { + return new URL(url).hostname.endsWith('zhihu.com') + } + + async preParse(url: string, dom: Document): Promise { + const mainEntity = dom.querySelector(`div[itemprop='mainEntity']`) + if (mainEntity) { + const newMainEntity = dom.createElement('div') + const question = mainEntity.querySelector('.QuestionHeader') + if (question) { + question.className = '_omnivore_zhihu_question' + newMainEntity.appendChild(question) + } + + const answers = mainEntity.querySelectorAll('.ContentItem.AnswerItem') + answers.forEach((answer) => { + answer + .querySelector('.AuthorInfo') + ?.setAttribute('class', '_omnivore_zhihu_author') + + answer.className = '_omnivore_zhihu_answer' + newMainEntity.appendChild(answer) + }) + + dom.body.replaceChildren(newMainEntity) + } + + return Promise.resolve(dom) + } +} diff --git a/packages/readabilityjs/Readability-readerable.js b/packages/readabilityjs/Readability-readerable.js index ee92e21d4..09da68908 100644 --- a/packages/readabilityjs/Readability-readerable.js +++ b/packages/readabilityjs/Readability-readerable.js @@ -23,12 +23,12 @@ var REGEXPS = { // NOTE: These two regular expressions are duplicated in // Readability.js. Please keep both copies in sync. - articleNegativeLookBehindCandidates: /breadcrumbs|breadcrumb|utils|trilist/i, - articleNegativeLookAheadCandidates: /outstream(.?)_|sub(.?)_|m_|omeda-promo-|in-article-advert|block-ad-.*/i, - unlikelyCandidates: /\bad\b|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|list-nav|block-ad-entity|adSpecs/i, + articleNegativeLookBehindCandidates: /breadcrumbs|breadcrumb|utils|trilist|_header/i, + articleNegativeLookAheadCandidates: /outstream(.?)_|sub(.?)_|m_|omeda-promo-|in-article-advert|block-ad-.*|tl_/i, + unlikelyCandidates: /\bad\b|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|post-head|post-tag|li-date|main-navigation|programtic-ads|outstream_article|hfeed|comment-holder|back-to-top|show-up-next|onward-journey|topic-tracker|list-nav|block-ad-entity|adSpecs|gift-article-button|modal-title|in-story-masthead|share-tools|standard-dock|expanded-dock|margins-h|subscribe-dialog|icon|bumped|dvz-social-media-buttons|post-toc|mobile-menu|mobile-navbar|tl_article_header|mvp(-post)*-(add-story|soc(-mob)*-wrap)|w-condition-invisible|rich-text-block main w-richtext|rich-text-block_ataglance at-a-glance test w-richtext|PostsPage-commentsSection/i, // okMaybeItsACandidate: /and|article(?!-breadcrumb)|body|column|content|main|shadow|post-header/i, get okMaybeItsACandidate() { - return new RegExp(`and|(? nodes which have
node(s) and append them into the `nodes` variable. // Some articles' DOM structures might look like diff --git a/packages/readabilityjs/Readability.js b/packages/readabilityjs/Readability.js index 316f1a0ef..2727d3ea1 100644 --- a/packages/readabilityjs/Readability.js +++ b/packages/readabilityjs/Readability.js @@ -120,20 +120,21 @@ function Readability(doc, options) { return `<${node.localName} ${attrPairs}>`; }; this.log = function () { - if (typeof dump !== "undefined") { - var msg = Array.prototype.map.call(arguments, function(x) { - return (x && x.nodeName) ? logNode(x) : x; - }).join(" "); - dump("Reader: (Readability) " + msg + "\n"); - } else if (typeof console !== "undefined") { + if (typeof console !== "undefined") { let args = Array.from(arguments, arg => { - if (arg && arg.nodeType === this.ELEMENT_NODE) { + if (arg && arg.nodeType == this.ELEMENT_NODE) { return logNode(arg); } return arg; }); args.unshift("Reader: (Readability)"); console.log.apply(console, args); + } else if (typeof dump !== "undefined") { + /* global dump */ + var msg = Array.prototype.map.call(arguments, function(x) { + return (x && x.nodeName) ? logNode(x) : x; + }).join(" "); + dump("Reader: (Readability) " + msg + "\n"); } }; } else { @@ -194,6 +195,9 @@ Readability.prototype = { hashUrl: /^#.+/, srcsetUrl: /(\S+)(\s+[\d.]+[xw])?(\s*(?:,|$))/g, b64DataUrl: /^data:\s*([^\s;,]+)\s*;\s*base64\s*,/i, + // Commas as used in Latin, Sindhi, Chinese and various other scripts. + // see: https://en.wikipedia.org/wiki/Comma#Comma_variants + commas: /\u002C|\u060C|\uFE50|\uFE10|\uFE11|\u2E41|\u2E34|\u2E32|\uFF0C/g, // See: https://schema.org/Article jsonLdArticleTypes: /^Article|AdvertiserContentArticle|NewsArticle|AnalysisNewsArticle|AskPublicNewsArticle|BackgroundNewsArticle|OpinionNewsArticle|ReportageNewsArticle|ReviewNewsArticle|Report|SatiricalArticle|ScholarlyArticle|MedicalScholarlyArticle|SocialMediaPosting|BlogPosting|LiveBlogPosting|DiscussionForumPosting|TechArticle|APIReference$/, DATES_REGEXPS: [ @@ -1173,6 +1177,12 @@ Readability.prototype = { continue; } + // User is not able to see elements applied with both "aria-modal = true" and "role = dialog" + if (node.getAttribute("aria-modal") == "true" && node.getAttribute("role") == "dialog") { + node = this._removeAndGetNext(node); + continue; + } + // Check to see if this node is a byline or published, and remove it if it is. if (this._checkByline(node, matchString) || this._checkPublishedDate(node, matchString)) { node = this._removeAndGetNext(node); @@ -1326,8 +1336,8 @@ Readability.prototype = { // Add a point for the paragraph itself as a base. contentScore += 1; - // Add points for any commas (including those in CJK language) within this paragraph. - contentScore += innerText.split(/[,,、]/g).length; + // Add points for any commas within this paragraph. + contentScore += innerText.split(this.REGEXPS.commas).length; // For every 100 characters in this paragraph, add another point. Up to 3 points. contentScore += Math.min(Math.floor(innerText.length / 100), 3); @@ -1487,7 +1497,7 @@ Readability.prototype = { if (isPaging) articleContent.id = "readability-content"; - var siblingScoreThreshold = Math.max(10, (topCandidate.readability && topCandidate.readability.contentScore || 0) * 0.2); + var siblingScoreThreshold = Math.max(10, (topCandidate.readability?.contentScore || 0) * 0.2); // Keep potential top candidate's parent node to try to get text direction of it later. parentOfTopCandidate = topCandidate.parentNode; var siblings = parentOfTopCandidate.children; @@ -1770,7 +1780,22 @@ Readability.prototype = { this.log(`Parsed after: `, {parsed: parsedArticleInfo}) } - if (typeof parsedArticleInfo.name === "string") { + if (typeof parsedArticleInfo.name === "string" && typeof parsedArticleInfo.headline === "string" && parsedArticleInfo.name !== parsedArticleInfo.headline) { + // we have both name and headline element in the JSON-LD. They should both be the same but some websites like aktualne.cz + // put their own name into "name" and the article title to "headline" which confuses Readability. So we try to check if either + // "name" or "headline" closely matches the html title, and if so, use that one. If not, then we use "name" by default. + + var title = this._getArticleTitle(); + var nameMatches = this._textSimilarity(parsedArticleInfo.name, title) > 0.75; + var headlineMatches = this._textSimilarity(parsedArticleInfo.headline, title) > 0.75; + + if (headlineMatches && !nameMatches) { + metadata.title = parsedArticleInfo.headline; + } else { + metadata.title = parsedArticleInfo.name; + } + + } else if (typeof parsedArticleInfo.name === "string") { metadata.title = parsedArticleInfo.name.trim(); } else if (typeof parsedArticleInfo.headline === "string") { metadata.title = parsedArticleInfo.headline.trim(); @@ -2114,12 +2139,7 @@ Readability.prototype = { * @param Element **/ _removeScripts: function (doc) { - this._removeNodes(this._getAllNodesWithTag(doc, ["script"]), function (scriptNode) { - scriptNode.nodeValue = ""; - scriptNode.removeAttribute("src"); - return true; - }); - this._removeNodes(this._getAllNodesWithTag(doc, ["noscript"])); + this._removeNodes(this._getAllNodesWithTag(doc, ["script", "noscript"])); }, /** diff --git a/packages/readabilityjs/test/generate-testcase.js b/packages/readabilityjs/test/generate-testcase.js index 86adaea96..f9eab147b 100644 --- a/packages/readabilityjs/test/generate-testcase.js +++ b/packages/readabilityjs/test/generate-testcase.js @@ -56,7 +56,9 @@ const enableJavascriptForUrl = (url) => { }; function generateTestcase(slug) { - const options = {}; + const options = { + debug, + }; if (slug.startsWith("newsletters/")) { // keep the newsletter content in tables options.keepTables = true; @@ -289,7 +291,6 @@ async function runReadability(source, destPath, metadataDestPath, options) { try { // Use linkedom for isProbablyReaderable because it supports querySelectorAll var dom = parseHTML(source).document; - console.log('dom', parseHTML(source).innerHTML) readerable = isProbablyReaderable(dom); // We pass `caption` as a class to check that passing in extra classes works, // given that it appears in some of the test documents. diff --git a/packages/readabilityjs/test/index.html b/packages/readabilityjs/test/index.html index c3b890947..3c68fee3d 100644 --- a/packages/readabilityjs/test/index.html +++ b/packages/readabilityjs/test/index.html @@ -14,250 +14,28 @@ diff --git a/packages/readabilityjs/test/test-pages/aboveavalon/expected-metadata.json b/packages/readabilityjs/test/test-pages/aboveavalon/expected-metadata.json index 30892a306..012b0ffb9 100644 --- a/packages/readabilityjs/test/test-pages/aboveavalon/expected-metadata.json +++ b/packages/readabilityjs/test/test-pages/aboveavalon/expected-metadata.json @@ -4,7 +4,9 @@ "dir": null, "excerpt": "In a few months, the number of people wearing an Apple Watch will surpass 100 million. While the tech press spent years infatuated with stationary smart speakers and the idea of voice-only interfaces, it was the Apple Watch and utility on the wrist that ushered in a new paradigm shift in computing.", "siteName": "Above Avalon", + "siteIcon": "https://images.squarespace-cdn.com/content/v1/5446f93de4b0a3452dfaf5b0/1497467684469-0OFYG1H8WD6WHL09UZOY/ke17ZwdGBToddI8pDm48kMpagLdZPgiW6yD5i4KsS9VZw-zPPgdn4jUwVcJE1ZvWhcwhEtWJXoshNdA9f1qD7UnCxNA8dHvmd7460Z7fbKFSZnIIAPuX1C4iUTyn4Xd4-76gsgk4xgxAaWTBSRHt9w/favicon.ico?format=100w", "previewImage": "http://static1.squarespace.com/static/5446f93de4b0a3452dfaf5b0/5457bb30e4b087fea5cb1c54/5f74e65f21abae74c7a3d26f/1602274812639/Screen+Shot+2020-09-30+at+5.23.23+PM.png?format=1500w", "publishedDate": "2020-09-30T21:44:39.000Z", + "language": "English", "readerable": true } diff --git a/packages/readabilityjs/test/test-pages/aboveavalon/expected.html b/packages/readabilityjs/test/test-pages/aboveavalon/expected.html index ee7928348..607c52710 100644 --- a/packages/readabilityjs/test/test-pages/aboveavalon/expected.html +++ b/packages/readabilityjs/test/test-pages/aboveavalon/expected.html @@ -1,79 +1,79 @@ -
+
-
+

In a few months, the number of people wearing an Apple Watch will surpass 100 million. While the tech press spent years infatuated with stationary smart speakers and the idea of voice-only interfaces, it was the Apple Watch and utility on the wrist that ushered in a new paradigm shift in computing. We are now seeing Apple leverage the growing number of Apple Watch wearers to build a formidable health platform. The Apple Watch is a runaway train with no company in a position to slow it down.

Mirages and Head Fakes

-

We are coming off of a weird stretch for the tech industry. As smartphone sales growth slowed in the mid-2010s, companies, analysts, and pundits began to search for the next big thing. The search landed on stationary smart speakers and voice interfaces.

-

Companies who weren’t able to leverage the smartphone revolution with their own hardware placed massive bets on digital voice assistants that would supposedly usher in the end of the smartphone era. These digital voice assistants would be delivered to consumers via cheap stationary speakers placed in the home. Massive PR campaigns were launched that attempted to convince people about this post-smartphone future. Unfortunately for these companies, glowing press coverage cannot hide a product category’s fundamental design shortcomings. 

+

We are coming off of a weird stretch for the tech industry. As smartphone sales growth slowed in the mid-2010s, companies, analysts, and pundits began to search for the next big thing. The search landed on stationary smart speakers and voice interfaces.

+

Companies who weren’t able to leverage the smartphone revolution with their own hardware placed massive bets on digital voice assistants that would supposedly usher in the end of the smartphone era. These digital voice assistants would be delivered to consumers via cheap stationary speakers placed in the home. Massive PR campaigns were launched that attempted to convince people about this post-smartphone future. Unfortunately for these companies, glowing press coverage cannot hide a product category’s fundamental design shortcomings. 

At nearly every turn, Apple was said to be missing the voice train because of a dependency on iPhone revenue. Management was said to suffer from tunnel vision while the company’s approach to privacy was positioned as a long-term headwind that would lead to inferior results in AI relative to the competition. Simply put, Apple was viewed as losing control of where technology was headed following the mobile revolution.

There were glaring signs that narratives surrounding smart speakers and Apple lacking a coherent strategy for the future were off the mark. In November 2017, I wrote the following in an article titled, “A Stationary Smart Speaker Mirage”:

- “On the surface, Amazon Echo sales point to a burgeoning product category. A 15M+ annual sales pace for a product category that is only three years old is quite the accomplishment. This has led to prognostications of stationary smart speakers representing a new paradigm in technology. However, relying too much on Echo sales will lead to incomplete or faulty conclusions. The image portrayed by Echo sales isn't what it seems. In fact, it is only a matter of time before it becomes clear the stationary home speaker is shaping up to be one of the largest head fakes in tech. We are already starting to see early signs of disappointment begin to appear… + “On the surface, Amazon Echo sales point to a burgeoning product category. A 15M+ annual sales pace for a product category that is only three years old is quite the accomplishment. This has led to prognostications of stationary smart speakers representing a new paradigm in technology. However, relying too much on Echo sales will lead to incomplete or faulty conclusions. The image portrayed by Echo sales isn't what it seems. In fact, it is only a matter of time before it becomes clear the stationary home speaker is shaping up to be one of the largest head fakes in tech. We are already starting to see early signs of disappointment begin to appear…

- I don’t think stationary smart speakers represent the future of computing. Instead, companies are using smart speakers to take advantage of an awkward phase of technology in which there doesn’t seem to be any clear direction as to where things are headed. Consumers are buying cheap smart speakers powered by digital voice assistants without having any strong convictions regarding how such voice assistants should or can be used. The major takeaway from customer surveys regarding smart speaker usage is that there isn’t any clear trend. If anything, smart speakers are being used for rudimentary tasks that can just as easily be done with digital voice assistants found on smartwatches or smartphones. This environment paints a very different picture of the current health of the smart speaker market. The narrative in the press is simply too rosy and optimistic. + I don’t think stationary smart speakers represent the future of computing. Instead, companies are using smart speakers to take advantage of an awkward phase of technology in which there doesn’t seem to be any clear direction as to where things are headed. Consumers are buying cheap smart speakers powered by digital voice assistants without having any strong convictions regarding how such voice assistants should or can be used. The major takeaway from customer surveys regarding smart speaker usage is that there isn’t any clear trend. If anything, smart speakers are being used for rudimentary tasks that can just as easily be done with digital voice assistants found on smartwatches or smartphones. This environment paints a very different picture of the current health of the smart speaker market. The narrative in the press is simply too rosy and optimistic.

Ultimately, smart speakers end up competing with a seemingly unlikely product category: wearables.”

-

Three years later, I wouldn’t change one thing found in the preceding three paragraphs. The smart speaker bubble popped less than 12 months after publishing that article. The product category no longer has a buzz factor, and despite the hopes of Amazon and Google, people are not using stationary speakers for much else besides listening to music and rudimentary tasks like setting kitchen timers.

-

The primary problem found with voice is that it’s not a great medium for transferring a lot of data, information, and context. As a result, companies like Amazon have needed to dial back their grandiose vision for voice-first and voice-only paradigms. Last week’s Amazon hardware event highlighted a growing bet on screens – a complete reversal from the second half of the 2010s. 

+

Three years later, I wouldn’t change one thing found in the preceding three paragraphs. The smart speaker bubble popped less than 12 months after publishing that article. The product category no longer has a buzz factor, and despite the hopes of Amazon and Google, people are not using stationary speakers for much else besides listening to music and rudimentary tasks like setting kitchen timers.

+

The primary problem found with voice is that it’s not a great medium for transferring a lot of data, information, and context. As a result, companies like Amazon have needed to dial back their grandiose vision for voice-first and voice-only paradigms. Last week’s Amazon hardware event highlighted a growing bet on screens – a complete reversal from the second half of the 2010s. 

- Betting on the Wrist  + Betting on the Wrist 

As companies who missed the smartphone boat were placing bets on stationary speakers, Apple was placing a dramatically different bet on a small device with a screen. This device wouldn’t be stationary but instead push the definition of mobile by being worn on the wrist.

-

Jony Ive, who is credited with leading Apple’s push into wrist wearables, referred to the wrist as “the obvious and right place” for a different kind of computer. 

-

When Apple unveiled the Apple Watch in 2014, wearable computing on the wrist was more of a promise than anything else. Apple created an entirely new industry – something that isn’t found much in the traditional Apple playbook. 

-

After years of deep skepticism and cynicism, consensus reaction towards Apple Watch has changed and is now positive. Much of this is due to the fact that it’s impossible to miss Apple Watches appearing on wrists around the world. According to my estimates, approximately 35% of iPhone users in the U.S. now wear an Apple Watch. This is a shockingly high percentage for a five-year-old product category, and it says a lot about how Apple’s intuition about the wrist was right.

+

Jony Ive, who is credited with leading Apple’s push into wrist wearables, referred to the wrist as “the obvious and right place” for a different kind of computer. 

+

When Apple unveiled the Apple Watch in 2014, wearable computing on the wrist was more of a promise than anything else. Apple created an entirely new industry – something that isn’t found much in the traditional Apple playbook. 

+

After years of deep skepticism and cynicism, consensus reaction towards Apple Watch has changed and is now positive. Much of this is due to the fact that it’s impossible to miss Apple Watches appearing on wrists around the world. According to my estimates, approximately 35% of iPhone users in the U.S. now wear an Apple Watch. This is a shockingly high percentage for a five-year-old product category, and it says a lot about how Apple’s intuition about the wrist was right.

- Apple Watch Installed Base  + Apple Watch Installed Base 

-

The number of people wearing an Apple Watch continues to steadily increase. According to my estimate, there were 81 million people wearing an Apple Watch as of the end of June. According to Apple, 75% of Apple Watch sales are going to first-time customers. This means that 23 million people will have bought their first Apple Watch in 2020. To put that number in context, there are about 25 million people wearing a Fitbit. The Apple Watch installed base is increasing by the size of Fitbit’s overall installed base every 12 months. Exhibit 1 highlights the change in the Apple Watch installed base over the years. 

+

The number of people wearing an Apple Watch continues to steadily increase. According to my estimate, there were 81 million people wearing an Apple Watch as of the end of June. According to Apple, 75% of Apple Watch sales are going to first-time customers. This means that 23 million people will have bought their first Apple Watch in 2020. To put that number in context, there are about 25 million people wearing a Fitbit. The Apple Watch installed base is increasing by the size of Fitbit’s overall installed base every 12 months. Exhibit 1 highlights the change in the Apple Watch installed base over the years. 

Exhibit 1: Apple Watch Installed Base (number of people wearing an Apple Watch)

-
+
-

+

-
+

(The calculations and methodology used to reach my Apple Watch installed base estimates is available here for Above Avalon members.)

Deriving Power

-

From where is Apple Watch deriving its momentum? The answer is found in The Grand Unified Theory of Apple Products. 

+

From where is Apple Watch deriving its momentum? The answer is found in The Grand Unified Theory of Apple Products. 

-
+
-

+

-
+

One of the core tenets of my theory is that an Apple product category's design is tied to the role it is meant to play relative to other Apple products. The Apple Watch is designed to handle a growing number of tasks once given to the iPhone. Meanwhile, the iPhone is designed to handle a growing number of tasks given to the iPad. One can continue this exercise to cover all of Apple's major product categories.

-

Apple Watch is not an iPhone replacement because there are things done on an iPhone that can't be done on an Apple Watch. This ends up being a feature, not a bug. The Apple Watch’s design then allows the product to handle entirely new tasks that can’t be handled on an iPhone. This latter attribute goes a long way in explaining how Apple Watch has helped usher in a new paradigm shift in computing. Apple Watch wearers are able to interact with technology differently.

-

(More on The Grand Unified Theory of Apple Products is found in the Above Avalon Report, “Product Vision: How Apple Thinks About the World,” available here for Above Avalon members.) +

Apple Watch is not an iPhone replacement because there are things done on an iPhone that can't be done on an Apple Watch. This ends up being a feature, not a bug. The Apple Watch’s design then allows the product to handle entirely new tasks that can’t be handled on an iPhone. This latter attribute goes a long way in explaining how Apple Watch has helped usher in a new paradigm shift in computing. Apple Watch wearers are able to interact with technology differently.

+

(More on The Grand Unified Theory of Apple Products is found in the Above Avalon Report, “Product Vision: How Apple Thinks About the World,” available here for Above Avalon members.)

A Health Platform

-

In January 2019, Tim Cook surprised many by saying Apple will be remembered more for its contributions to health than for any other reason. Here’s Cook: 

+

In January 2019, Tim Cook surprised many by saying Apple will be remembered more for its contributions to health than for any other reason. Here’s Cook: 

“I believe, if you zoom out into the future, and you look back, and you ask the question, ‘What was Apple’s greatest contribution to mankind?’ it will be about health.”

-

Many assumed that Cook’s comment hinted at Apple unveiling a portfolio of medical-grade devices that would go through the FDA approval process. Such thinking was based on a fundamental misunderstanding of Apple’s ambition and approach to product development. 

-

Apple’s health strategy is based on leveraging hardware, software, and services to rethink the way we approach health. This means Apple wasn’t going to just launch a depository for our health data – something that is needed but which ultimately falls short of being truly revolutionary. In addition, Apple wasn’t going to just offer health and fitness services that amount to counting steps or keeping track of miles run. 

+

Many assumed that Cook’s comment hinted at Apple unveiling a portfolio of medical-grade devices that would go through the FDA approval process. Such thinking was based on a fundamental misunderstanding of Apple’s ambition and approach to product development. 

+

Apple’s health strategy is based on leveraging hardware, software, and services to rethink the way we approach health. This means Apple wasn’t going to just launch a depository for our health data – something that is needed but which ultimately falls short of being truly revolutionary. In addition, Apple wasn’t going to just offer health and fitness services that amount to counting steps or keeping track of miles run. 

By the time Cook gave his bullish comment about health, Apple had already placed its big bet on health four years earlier by unveiling the Apple Watch. In what ended up being one of Apple’s best decisions, the company avoided going the route of medical-grade devices requiring government agency approval to reach consumers. Instead, Apple framed its health platform as a new-age computer that ultimately is an iPhone alternative.

-

Health monitoring is one of the key new tasks that the Apple Watch, not iPhone, handles. To be more precise, Apple Watch is handling the following four health-related items: 

+

Health monitoring is one of the key new tasks that the Apple Watch, not iPhone, handles. To be more precise, Apple Watch is handling the following four health-related items: 

  1. Proactive monitoring (i.e. heart rate and blood oxygen)

    @@ -88,21 +88,21 @@

    Fitness and health activity (i.e. Apple Fitness+)

-

With Apple Fitness+, Apple didn’t just release a virtual fitness class service. Instead, Apple Fitness+ is an Apple Watch service.  In some ways, Apple Fitness+ reminds me of Apple TV+. A future in which Fitness+ workouts are available on third-party gym equipment displays including on treadmills and stationary bikes is not a stretch. In addition, classes from other companies such as Nike could further elevate Apple Fitness+. 

+

With Apple Fitness+, Apple didn’t just release a virtual fitness class service. Instead, Apple Fitness+ is an Apple Watch service.  In some ways, Apple Fitness+ reminds me of Apple TV+. A future in which Fitness+ workouts are available on third-party gym equipment displays including on treadmills and stationary bikes is not a stretch. In addition, classes from other companies such as Nike could further elevate Apple Fitness+. 

Competition

-

If the Apple Watch is a runaway train, there is no obvious candidate in a position to stop or even slow the train. While other companies are slowly waking up and seeing the momentum found with Apple Watch, there is still much indifference, mystery, and misunderstanding as to why people are buying wearables. Too many companies still think of wearables as glorified smartphone accessories. Such thinking makes it impossible for competitors to see how Apple Watch is ushering in a paradigm shift in computing by making technology more personal in a way that other devices have failed to accomplish or replicate.

-

One of the main takeaways from Apple’s product event earlier this month is how Apple is its own toughest competitor. The Apple Watch’s most legitimate competition is found with older Apple Watches and non-consumption (i.e. empty wrists). While this introduces its own set of risks and challenges, there is still no genuine Apple Watch competition from other companies after six years. This is an indication of the power found in controlling your own hardware, software, and services in order to get more out of technology without having technology take over people’s lives. 

+

If the Apple Watch is a runaway train, there is no obvious candidate in a position to stop or even slow the train. While other companies are slowly waking up and seeing the momentum found with Apple Watch, there is still much indifference, mystery, and misunderstanding as to why people are buying wearables. Too many companies still think of wearables as glorified smartphone accessories. Such thinking makes it impossible for competitors to see how Apple Watch is ushering in a paradigm shift in computing by making technology more personal in a way that other devices have failed to accomplish or replicate.

+

One of the main takeaways from Apple’s product event earlier this month is how Apple is its own toughest competitor. The Apple Watch’s most legitimate competition is found with older Apple Watches and non-consumption (i.e. empty wrists). While this introduces its own set of risks and challenges, there is still no genuine Apple Watch competition from other companies after six years. This is an indication of the power found in controlling your own hardware, software, and services in order to get more out of technology without having technology take over people’s lives. 

Listen to the corresponding Above Avalon podcast episode for this article here.

- Receive my analysis and perspective on Apple throughout the week via exclusive daily updates (2-3 stories per day, 10-12 stories per week). Available to Above Avalon members in both written and audio forms. To sign up and for more information on membership, visit the membership page. + Receive my analysis and perspective on Apple throughout the week via exclusive daily updates (2-3 stories per day, 10-12 stories per week). Available to Above Avalon members in both written and audio forms. To sign up and for more information on membership, visit the membership page.

For additional discussion on this topic, check out the Above Avalon daily update from October 1st.

-
\ No newline at end of file +
\ No newline at end of file diff --git a/packages/readabilityjs/test/test-pages/ahhhhfs.com/expected-metadata.json b/packages/readabilityjs/test/test-pages/ahhhhfs.com/expected-metadata.json index 27f7576d9..dbd149a1d 100644 --- a/packages/readabilityjs/test/test-pages/ahhhhfs.com/expected-metadata.json +++ b/packages/readabilityjs/test/test-pages/ahhhhfs.com/expected-metadata.json @@ -8,5 +8,5 @@ "previewImage": "https://www.ahhhhfs.com/wp-content/uploads/2023/02/CCTV%E4%B8%AD%E5%A4%AE%E7%94%B5%E8%A7%86%E5%8F%B0%E7%9B%B4%E6%92%AD%E6%BA%90-Featured-Image.jpg?w=640", "publishedDate": "2023-02-04T11:17:25.000Z", "language": "Chinese", - "readerable": false + "readerable": true } diff --git a/packages/readabilityjs/test/test-pages/ahhhhfs.com/expected.html b/packages/readabilityjs/test/test-pages/ahhhhfs.com/expected.html index 27edbe690..db2243c67 100644 --- a/packages/readabilityjs/test/test-pages/ahhhhfs.com/expected.html +++ b/packages/readabilityjs/test/test-pages/ahhhhfs.com/expected.html @@ -1,121 +1,54 @@
-
-
-
    当前位置:
  1. - 首页 -
  2. -
  3. - 福利 -
  4. -
  5. - 趣站 -
  6. -
  7. 正文
  8. -
-
+
-

流媒体Netflix账号、YouTube、Spotify、HBO、Appletv+Disney、Prime Video

- +

ahhhhfs +

-
-

文章目录 显示 -

-
-

一些CCTV中央电视台直播源,做成了iOS的捷径,添加捷径后直接打开,选择需要观看的CCTV频道即可跳转浏览器进行观看,直播源是m3u8链接。

-

- CCTV中央电视台直播源 -

-

- CCTV中央电视台直播源地址 -

-

直播源地址:iOS捷径 -

-

- 其他M3U直播源资源工具👍 -

-

1、IPTV直播源 国内网络直连 支持IPv4/IPv6双栈访问 -

-

2、4K/8K超高清IPTV直播源在线地址综合汇总 -

-

3、GitHub上一些m3u直播源集合 大人学习资源:YanG-1989 -

-

4、IPTV直播源相关资源汇总- Tvlist-awesome -

-

5、IPTV直播源搜索引擎 搜索公共 IPTV 频道 – IPTV Link Search -

-

6、某传媒3k直播源m3u 干净无广告 订阅更新链接 -

-

7、某些直播源 m3u 看少点 -

-

8、4000+直播源和直播软件 含福利直播源 -

-

9、更多直播源资源集合 -

-

本文链接:https://www.ahhhhfs.com/37715/ -

- -
+
+

本站所有资源收集整理于网络,本站不参与制作,用于互联网爱好者学习和研究,如不慎侵犯了您的权利,请及时联系站长处理删除。敬请谅解! 侵权删帖/违法举报/投稿等联系邮箱(#替换成@):feedback#abskoop.com
Copyright © 2023 | ahhhhfs | All Rights Reserved

+
+
\ No newline at end of file diff --git a/packages/readabilityjs/test/test-pages/bitfieldconsulting/expected-metadata.json b/packages/readabilityjs/test/test-pages/bitfieldconsulting/expected-metadata.json index dcc2a1ef3..4c75ead17 100644 --- a/packages/readabilityjs/test/test-pages/bitfieldconsulting/expected-metadata.json +++ b/packages/readabilityjs/test/test-pages/bitfieldconsulting/expected-metadata.json @@ -4,7 +4,9 @@ "dir": null, "excerpt": "Which is better, Rust or Go? Which language should you choose for your next project, and why? How do the two compare in areas like performance, simplicity, safety, features, scale, and concurrency? Let's find out, in this friendly and even-handed comparison of Rust and Go.", "siteName": "Bitfield Consulting", + "siteIcon": "https://images.squarespace-cdn.com/content/v1/5e10bdc20efb8f0d169f85f9/1601296548308-UF3SJPONEGFITRM2OB64/ke17ZwdGBToddI8pDm48kCm4xBx_xLJDsr83RI3lddVZw-zPPgdn4jUwVcJE1ZvWEtT5uBSRWt4vQZAgTJucoTqqXjS3CfNDSuuf31e0tVGOjmk2xTq_LlcHVgrGNqw90z0CrpLMht6GHAb7xSGjXDFvbuqF0GUInBxxtVhBOn4/favicon.ico?format=100w", "previewImage": "http://static1.squarespace.com/static/5e10bdc20efb8f0d169f85f9/5e949913434baa2223121b85/5fa52db75dfc746ea899bfdc/1606903921091/rust-vs-go-square.png?format=1500w", "publishedDate": "2020-11-06T11:04:17.000Z", + "language": "English", "readerable": true } diff --git a/packages/readabilityjs/test/test-pages/bitfieldconsulting/expected.html b/packages/readabilityjs/test/test-pages/bitfieldconsulting/expected.html index 79b6b9b7b..8e56fe18c 100644 --- a/packages/readabilityjs/test/test-pages/bitfieldconsulting/expected.html +++ b/packages/readabilityjs/test/test-pages/bitfieldconsulting/expected.html @@ -1,12 +1,12 @@ -
+
-
+

rust-vs-go-wide.png

-
+

Which is better, Rust or Go? Which language should you choose for your next project, and why? How do the two compare in areas like performance, simplicity, safety, features, scale, and concurrency? What do they have in common, and where do they fundamentally differ? Let's find out, in this friendly and even-handed comparison of Rust and Golang, from the author of the For the Love of Go book series.

Rust and Go are both awesome

First, it's really important to say that both Go and Rust are absolutely excellent programming languages. They're modern, powerful, widely-adopted, and offer excellent performance. You may have read articles and blog posts aiming to convince you that Go is better than Rust, or vice versa. But that really makes no sense; every programming language represents a set of trade-offs. Each language is optimised for different things, so your choice of language should be determined by what suits you and the problems you want to solve with it.

@@ -35,7 +35,7 @@

For example, whereas C programmers have argued for years about where to put their brackets, and whether code should be indented with tabs or spaces, both Rust and Go eliminate such issues completely by using a standard formatting tool (gofmt for Go, rustfmt for Rust) which rewrites your code automatically using the canonical style. It’s not that this particular style is so wonderful in itself: it’s the standardisation which Rust and Go programmers appreciate.

- gofmt’s style is no one’s favourite, yet gofmt is everyone’s favourite.
Rob Pike + gofmt’s style is no one’s favourite, yet gofmt is everyone’s favourite.
Rob Pike

Another area where both languages score highly is in the build pipeline. Both have excellent, built-in, high-performance standard build and dependency management tools; no more wrestling with complex third-party build systems and having to learn a new one every couple of years.

@@ -236,4 +236,4 @@ for dish, price := range menu {

I am not young enough to know everything, as the saying goes, so I'm very grateful to a number of distinguished Gophers and Rustaceans who took the time to review and correct this piece, as well as providing some really useful suggestions. My special thanks go to Bill Kennedy, Grzegorz Nosek, Sam Rose, Jack Mott, Steve Klabnik, MN Mark, Ola Nordstrom, Levi Lovelock, Emile Pels, Sebastian Lauwers, Carl Lerche, and everyone else who contributed. You might get the impression from reading online hot takes that the Rust and Go communities don't get along. Nothing could be further from the truth, in my experience; we had very civilised and fruitful discussions about the draft article, and it's made a big difference to the finished product. Thanks again.

-
\ No newline at end of file +
\ No newline at end of file diff --git a/packages/readabilityjs/test/test-pages/variety/expected-metadata.json b/packages/readabilityjs/test/test-pages/variety/expected-metadata.json index 2ab42e6fe..464f157b6 100644 --- a/packages/readabilityjs/test/test-pages/variety/expected-metadata.json +++ b/packages/readabilityjs/test/test-pages/variety/expected-metadata.json @@ -1,10 +1,12 @@ { - "title": "Variety", + "title": "Trilith Town for Marvel's Studios in Georgia Could Be Future of Cities", "byline": "Pat Saperstein", "dir": null, "excerpt": "Popular on Variety Imagine that you’re a makeup artist or a production manager on a Marvel movie. You’re working long hours and barely have time to shop for groceries or get in any exercise, let alone do yardwork or socialize. But what if your house was a pleasant 10-minute stroll from the studio, during which […]", "siteName": "Variety", + "siteIcon": "https://variety.com/wp-content/themes/pmc-variety-2020/assets/app/icons/apple-touch-icon.png", "previewImage": "https://variety.com/wp-content/uploads/2022/03/Triangle-Park-Homes.jpg", "publishedDate": "2022-03-11T00:59:41.000Z", + "language": "English", "readerable": true } diff --git a/packages/readabilityjs/test/test-pages/variety/expected.html b/packages/readabilityjs/test/test-pages/variety/expected.html index 943b32f26..07199bff4 100644 --- a/packages/readabilityjs/test/test-pages/variety/expected.html +++ b/packages/readabilityjs/test/test-pages/variety/expected.html @@ -1,22 +1,22 @@ -
+
-

Trilith +

Trilith

-

+

Courtesy Trilith
-

+

-

+

Is Trilith, a ‘Company Town’ for Marvel’s Georgia Production Workers, the Template for Future Cities?

-

+

Imagine that you’re a makeup artist or a production manager on a Marvel movie. You’re working long hours and barely have time to shop for groceries or get in any exercise, let alone do yardwork or socialize. But what if your house was a pleasant 10-minute stroll from the studio, during which you passed a craft beer bar and outdoor concert stage? You’re too tired when you get home to think about grocery shopping, but that’s OK, because the delivery robot has just rolled up in front of your house with your weekly order.

@@ -62,4 +62,4 @@

Parker says Trilith has enough land to double the size of the town. “It’s very attractive to a lot of different people,” he says.

-
\ No newline at end of file +
\ No newline at end of file diff --git a/packages/readabilityjs/test/test-pages/yuyue.com/expected-metadata.json b/packages/readabilityjs/test/test-pages/yuyue.com/expected-metadata.json index 4e699688b..5a07a08a9 100644 --- a/packages/readabilityjs/test/test-pages/yuyue.com/expected-metadata.json +++ b/packages/readabilityjs/test/test-pages/yuyue.com/expected-metadata.json @@ -8,5 +8,5 @@ "previewImage": "https://cdn.nlark.com/yuque/0/2022/png/22724648/1671339142303-ce7c7caa-57b6-473b-8fb3-bfaf59a79d3b.png", "publishedDate": null, "language": "English", - "readerable": false + "readerable": true }