├── .prettierignore ├── .npmrc ├── .gitignore ├── index.js ├── .editorconfig ├── .github └── workflows │ ├── bb.yml │ └── main.yml ├── tsconfig.json ├── license ├── package.json ├── lib └── index.js ├── test.js └── readme.md /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | *.md 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-scripts=true 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.d.ts 3 | *.log 4 | coverage/ 5 | node_modules/ 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('./lib/index.js').Options} Options 3 | */ 4 | 5 | export {readingTime} from './lib/index.js' 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/workflows/bb.yml: -------------------------------------------------------------------------------- 1 | name: bb 2 | on: 3 | issues: 4 | types: [opened, reopened, edited, closed, labeled, unlabeled] 5 | pull_request_target: 6 | types: [opened, reopened, edited, closed, labeled, unlabeled] 7 | jobs: 8 | main: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: unifiedjs/beep-boop-beta@main 12 | with: 13 | repo-token: ${{secrets.GITHUB_TOKEN}} 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "checkJs": true, 4 | "customConditions": ["development"], 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "exactOptionalPropertyTypes": true, 8 | "lib": ["es2022"], 9 | "module": "node16", 10 | // Needed for `readability-scores` :'( 11 | "skipLibCheck": true, 12 | "strict": true, 13 | "target": "es2022" 14 | }, 15 | "exclude": ["coverage/", "node_modules/"], 16 | "include": ["**/*.js"] 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | on: 3 | - pull_request 4 | - push 5 | jobs: 6 | main: 7 | name: ${{matrix.node}} 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: ${{matrix.node}} 14 | - run: npm install 15 | - run: npm test 16 | - uses: codecov/codecov-action@v3 17 | strategy: 18 | matrix: 19 | node: 20 | - lts/gallium 21 | - node 22 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2021 Titus Wormer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hast-util-reading-time", 3 | "version": "2.0.0", 4 | "description": "hast utility to estimate the reading time", 5 | "license": "MIT", 6 | "keywords": [ 7 | "unist", 8 | "hast", 9 | "hast-util", 10 | "util", 11 | "utility", 12 | "html", 13 | "reading", 14 | "time", 15 | "reading-time", 16 | "summary" 17 | ], 18 | "repository": "syntax-tree/hast-util-reading-time", 19 | "bugs": "https://github.com/syntax-tree/hast-util-reading-time/issues", 20 | "funding": { 21 | "type": "opencollective", 22 | "url": "https://opencollective.com/unified" 23 | }, 24 | "author": "Titus Wormer (https://wooorm.com)", 25 | "contributors": [ 26 | "Titus Wormer (https://wooorm.com)" 27 | ], 28 | "sideEffects": false, 29 | "type": "module", 30 | "exports": "./index.js", 31 | "files": [ 32 | "lib/", 33 | "index.d.ts", 34 | "index.js" 35 | ], 36 | "dependencies": { 37 | "@types/hast": "^3.0.0", 38 | "compute-median": "^2.0.0", 39 | "hast-util-to-text": "^4.0.0", 40 | "readability-scores": "^1.0.0" 41 | }, 42 | "devDependencies": { 43 | "@types/node": "^20.0.0", 44 | "c8": "^8.0.0", 45 | "hast-util-from-html": "^2.0.0", 46 | "prettier": "^3.0.0", 47 | "remark-cli": "^11.0.0", 48 | "remark-preset-wooorm": "^9.0.0", 49 | "type-coverage": "^2.0.0", 50 | "typescript": "^5.0.0", 51 | "xo": "^0.55.0" 52 | }, 53 | "scripts": { 54 | "prepack": "npm run build && npm run format", 55 | "build": "tsc --build --clean && tsc --build && type-coverage", 56 | "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix", 57 | "test-api": "node --conditions development test.js", 58 | "test-coverage": "c8 --100 --reporter lcov npm run test-api", 59 | "test": "npm run build && npm run format && npm run test-coverage" 60 | }, 61 | "prettier": { 62 | "bracketSpacing": false, 63 | "semi": false, 64 | "singleQuote": true, 65 | "tabWidth": 2, 66 | "trailingComma": "none", 67 | "useTabs": false 68 | }, 69 | "remarkConfig": { 70 | "plugins": [ 71 | "remark-preset-wooorm" 72 | ] 73 | }, 74 | "typeCoverage": { 75 | "atLeast": 100, 76 | "detail": true, 77 | "ignoreCatch": true, 78 | "strict": true 79 | }, 80 | "xo": { 81 | "prettier": true 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('hast').Nodes} Nodes 3 | */ 4 | 5 | /** 6 | * @typedef Options 7 | * Configuration 8 | * @property {number | null | undefined} [age=16] 9 | * Target age group (default: `16`). 10 | * 11 | * This is the age your target audience was still in school. 12 | * Set it to 18 if you expect all readers to have finished high school, 13 | * 21 if you expect your readers to all be college graduates, etc. 14 | */ 15 | 16 | // @ts-expect-error: untyped. 17 | import computeMedian_ from 'compute-median' 18 | import {toText} from 'hast-util-to-text' 19 | import readabilityScores from 'readability-scores' 20 | 21 | const computeMedian = /** @type {(x: Array) => number | null} */ ( 22 | computeMedian_ 23 | ) 24 | 25 | /** @type {Readonly} */ 26 | const emptyOptions = {} 27 | 28 | /** @type {Readonly>} */ 29 | const emptyScores = {} 30 | 31 | // See 32 | // for more info on US education/grade levels. 33 | const firstGradeAge = 5 34 | const highschoolGraduationAge = 18 35 | const graduationAge = 22 36 | 37 | // See 38 | // for the wpm of people reading English. 39 | // 40 | // Note that different other algorithms vary between 200, 230, 270, and 280. 41 | // 228 seems to at least be based in research. 42 | const reasonableWpm = 228 43 | const reasonableWpmMax = 340 44 | 45 | // See 46 | // for information on reading rate, including how grade levels affect them. 47 | const addedWpmPerGrade = 14 48 | const baseWpm = 49 | reasonableWpm - (highschoolGraduationAge - firstGradeAge) * addedWpmPerGrade 50 | 51 | const accuracy = 1e6 52 | 53 | /** 54 | * Estimate the reading time, taking readability of the document and a target 55 | * age group into account. 56 | * 57 | * For some more background info/history and a few insight on where this all 58 | * comes from, see: . 59 | * 60 | * ###### Algorithm 61 | * 62 | * The algorithm works as follows: 63 | * 64 | * * estimate the WPM (words per minute) of the audience age based on the facts 65 | * that English can be read at ±228 WPM (Trauzettel-Klosinski), and that 66 | * reading rate increases 14 WPM per grade (Carver) 67 | * * apply the readability algorithms Dale—Chall, Automated Readability, 68 | * Coleman-Liau, Flesch, Gunning-Fog, SMOG, and Spache 69 | * * adjust the WPM of the audience for whether the readability algorithms 70 | * estimate its above or below their level 71 | * * `wordCount / adjustedWpm = readingTime` 72 | * 73 | * > ⚠️ **Important**: this algorithm is specific to English. 74 | * 75 | * @param {Nodes} tree 76 | * Tree to inspect. 77 | * @param {Readonly | null | undefined} [options] 78 | * Configuration (optional). 79 | * @returns {number} 80 | * Estimated reading time in minutes. 81 | * 82 | * The result is not rounded so it’s possible to retrieve estimated seconds 83 | * from it. 84 | */ 85 | export function readingTime(tree, options) { 86 | const settings = options || emptyOptions 87 | // Cap an age to a reasonable and meaningful age in school. 88 | const targetAge = Math.min( 89 | graduationAge, 90 | Math.max(firstGradeAge, Math.round(settings.age || 16)) 91 | ) 92 | const text = toText(tree) 93 | const scores = readabilityScores(text) || emptyScores 94 | const scoreNumbers = /** @type {Array} */ ( 95 | [ 96 | scores.ari, 97 | scores.colemanLiau, 98 | scores.daleChall, 99 | scores.fleschKincaid, 100 | scores.gunningFog, 101 | scores.smog 102 | ].filter(function (d) { 103 | return d !== undefined 104 | }) 105 | ) 106 | 107 | const score = computeMedian(scoreNumbers) 108 | 109 | if (score === null) { 110 | return 0 111 | } 112 | 113 | /** @type {number} */ 114 | const readabilityAge = firstGradeAge + score 115 | 116 | // WPM the target audience normally reads. 117 | const targetWpm = baseWpm + (targetAge - firstGradeAge) * addedWpmPerGrade 118 | 119 | // If the text requires higher comprehension than the target age group is, 120 | // estimated to have, make it a bit slower (and vice versa). 121 | const adjustedWpm = 122 | targetWpm - (readabilityAge - targetAge) * (addedWpmPerGrade / 2) 123 | 124 | // Cap it to a WPM that’s reasonable. 125 | const wpm = Math.min(reasonableWpmMax, Math.max(baseWpm, adjustedWpm)) 126 | 127 | return Math.round((scores.wordCount / wpm) * accuracy) / accuracy 128 | } 129 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | import test from 'node:test' 3 | import {fromHtml} from 'hast-util-from-html' 4 | import {readingTime} from 'hast-util-reading-time' 5 | 6 | // https://simple.wikipedia.org/wiki/Reading 7 | const somewhatSimple = `

Reading is what we do when we understand writing.

8 |

More fully, it a cognitive process of understanding information represented by printed or written language. It is a way of getting information and insights about something that is written. Reading involves understanding the symbols in that language. It can only be done if one knows the language. Reading and hearing are the two most common ways to get information. Information gained from reading can include entertainment, especially when reading fiction or humor.

9 |

Proofreading is a kind of reading that is done to find mistakes in a piece of writing

10 |

Directed Reading-Thinking Activity is a method which aims to develop better reading.

11 |
    12 |
  1. Making predictions/hypothesis about the content, idea, and concepts from the title of the reading material.
  2. 13 |
  3. Sectional reading or processing (chunking) of the material.
  4. 14 |
  5. Checking the reliability and similarity of the read content with the predictions supported by evidence from the text.
  6. 15 |
  7. For better comprehension, to know what and why the text says: review vocabulary, understanding of the main idea, syntax of the sentence, details/facts and sequence of the story, and make inferences about the characters’ attitudes, behaviors or circumstances in the story.
  8. 16 |
  9. Make plausible predictions about what the next section will be about in the reading material.
  10. 17 |
` 18 | 19 | // https://en.wikipedia.org/wiki/Words_per_minute#Alphanumeric_entry 20 | // Capped at exactly the same number of words of `somewhatSimple`. 21 | const somewhatComplex = `

Since the length or duration of words is clearly variable, for the purpose of measurement of text entry, the definition of each "word" is often standardized to be five characters or keystrokes long in English, including spaces and punctuation. For example, under such a method applied to plain English text the phrase "I run" counts as one word, but "rhinoceros" and "let's talk" would both count as two.

22 |

Karat et al. found that one study of average computer users in 1999, the average rate for transcription was 32.5 words per minute, and 19.0 words per minute for composition. In the same study, when the group was divided into "fast", "moderate", and "slow" groups, the average speeds were 40 wpm, 35 wpm, and 23 wpm, respectively.

23 |

With the onset of the era of desktop computers, fast typing skills became much more widespread.

24 |

An average professional typist types usually in speeds of 43 to 80 wpm, while some positions can require 80 to 95 (usually the minimum required for dispatch positions and other time-sensitive typing jobs), and some advanced typists work at speeds above 120 wpm. Two-finger typists, sometimes also referred to as "hunt and peck" typists, commonly reach sustained speeds of about 37 wpm for memorized

25 | ` 26 | 27 | const tree = fromHtml(somewhatComplex, {fragment: true}) 28 | const treeSomewhatSimple = fromHtml(somewhatSimple, {fragment: true}) 29 | 30 | test('readingTime', async function (t) { 31 | await t.test('should expose the public api', async function () { 32 | assert.deepEqual( 33 | Object.keys(await import('hast-util-reading-time')).sort(), 34 | ['readingTime'] 35 | ) 36 | }) 37 | 38 | await t.test('should estimate (somewhat complex)', async function () { 39 | assert.deepEqual(readingTime(tree).toFixed(2), '1.22') 40 | }) 41 | 42 | await t.test('should estimate (somewhat simple)', async function () { 43 | assert.deepEqual(readingTime(treeSomewhatSimple).toFixed(2), '1.10') 44 | }) 45 | 46 | await t.test('should estimate (empty)', async function () { 47 | assert.deepEqual( 48 | readingTime({type: 'root', children: []}).toFixed(2), 49 | '0.00' 50 | ) 51 | }) 52 | 53 | await t.test( 54 | 'should take age into account (1, somewhat complex)', 55 | async function () { 56 | assert.deepEqual(readingTime(tree, {age: 12}).toFixed(2), '2.44') 57 | } 58 | ) 59 | 60 | await t.test( 61 | 'should take age into account (1, somewhat simple)', 62 | async function () { 63 | assert.deepEqual( 64 | readingTime(treeSomewhatSimple, {age: 12}).toFixed(2), 65 | '1.98' 66 | ) 67 | } 68 | ) 69 | 70 | await t.test( 71 | 'should take age into account (2, somewhat complex)', 72 | async function () { 73 | assert.deepEqual(readingTime(tree, {age: 21}).toFixed(2), '0.75') 74 | } 75 | ) 76 | 77 | await t.test( 78 | 'should take age into account (2, somewhat simple)', 79 | async function () { 80 | assert.deepEqual( 81 | readingTime(treeSomewhatSimple, {age: 21}).toFixed(2), 82 | '0.71' 83 | ) 84 | } 85 | ) 86 | 87 | await t.test('should cap at a reasonable time (1)', async function () { 88 | assert.deepEqual(readingTime(tree, {age: 1}).toFixed(2), '4.46') 89 | }) 90 | 91 | await t.test('should cap at a reasonable time (2)', async function () { 92 | assert.deepEqual(readingTime(tree, {age: 81}).toFixed(2), '0.70') 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # hast-util-reading-time 2 | 3 | [![Build][build-badge]][build] 4 | [![Coverage][coverage-badge]][coverage] 5 | [![Downloads][downloads-badge]][downloads] 6 | [![Size][size-badge]][size] 7 | [![Sponsors][sponsors-badge]][collective] 8 | [![Backers][backers-badge]][collective] 9 | [![Chat][chat-badge]][chat] 10 | 11 | [hast][] utility to estimate the reading time, taking readability of the 12 | document into account. 13 | 14 | ## Contents 15 | 16 | * [What is this?](#what-is-this) 17 | * [When should I use this?](#when-should-i-use-this) 18 | * [Install](#install) 19 | * [Use](#use) 20 | * [API](#api) 21 | * [`readingTime(tree[, options])`](#readingtimetree-options) 22 | * [`Options`](#options) 23 | * [Types](#types) 24 | * [Compatibility](#compatibility) 25 | * [Security](#security) 26 | * [Related](#related) 27 | * [Contribute](#contribute) 28 | * [License](#license) 29 | 30 | ## What is this? 31 | 32 | This package is a utility that takes a [hast][] (HTML) syntax tree and estimates 33 | the reading time, taking readability of the document and a target age group into 34 | account. 35 | 36 | ## When should I use this? 37 | 38 | This is a small utility useful when you have an AST, know your audience, and 39 | want a really smart reading time algorithm. 40 | 41 | The rehype plugin 42 | [`rehype-infer-reading-time-meta`][rehype-infer-reading-time-meta] 43 | wraps this utility to figure, for use with [`rehype-meta`][rehype-meta]. 44 | 45 | ## Install 46 | 47 | This package is [ESM only][esm]. 48 | In Node.js (version 16.0+), install with [npm][]: 49 | 50 | ```sh 51 | npm install hast-util-reading-time 52 | ``` 53 | 54 | In Deno with [`esm.sh`][esmsh]: 55 | 56 | ```js 57 | import {readingTime} from 'https://esm.sh/hast-util-reading-time@2' 58 | ``` 59 | 60 | In browsers with [`esm.sh`][esmsh]: 61 | 62 | ```html 63 | 66 | ``` 67 | 68 | ## Use 69 | 70 | Say our document `example.html` contains (from [“Word per minute: Alphanumeric 71 | entry” on Wikipedia][wiki]: 72 | 73 | ```html 74 |

Since the length or duration of words is clearly variable, for the purpose of measurement of text entry, the definition of each "word" is often standardized to be five characters or keystrokes long in English, including spaces and punctuation. For example, under such a method applied to plain English text the phrase "I run" counts as one word, but "rhinoceros" and "let's talk" would both count as two.

75 |

Karat et al. found that one study of average computer users in 1999, the average rate for transcription was 32.5 words per minute, and 19.0 words per minute for composition. In the same study, when the group was divided into "fast", "moderate", and "slow" groups, the average speeds were 40 wpm, 35 wpm, and 23 wpm, respectively.

76 |

With the onset of the era of desktop computers, fast typing skills became much more widespread.

77 |

An average professional typist types usually in speeds of 43 to 80 wpm, while some positions can require 80 to 95 (usually the minimum required for dispatch positions and other time-sensitive typing jobs), and some advanced typists work at speeds above 120 wpm. Two-finger typists, sometimes also referred to as "hunt and peck" typists, commonly reach sustained speeds of about 37 wpm for memorized text and 27 wpm when copying text, but in bursts may be able to reach much higher speeds. From the 1920s through the 1970s, typing speed (along with shorthand speed) was an important secretarial qualification and typing contests were popular and often publicized by typewriter companies as promotional tools.

78 | ``` 79 | 80 | …and our module `example.js` looks as follows: 81 | 82 | ```js 83 | import fs from 'node:fs/promises' 84 | import {fromHtml} from 'hast-util-from-html' 85 | import {readingTime} from 'hast-util-reading-time' 86 | 87 | const tree = fromHtml(await fs.readFile('example.html'), {fragment: true}) 88 | 89 | console.log( 90 | `It takes about ${Math.ceil(readingTime(tree, {age: 18}))}-${Math.ceil(readingTime(tree, {age: 14}))}m to read` 91 | ) 92 | ``` 93 | 94 | …now running `node example.js` yields: 95 | 96 | ```txt 97 | It takes about 2-3m to read 98 | ``` 99 | 100 | ## API 101 | 102 | This package exports the identifier [`readingTime`][api-reading-time]. 103 | There is no default export. 104 | 105 | ### `readingTime(tree[, options])` 106 | 107 | Estimate the reading time, taking readability of the document and a target age 108 | group into account. 109 | 110 | For some more background info/history and a few insight on where this all comes 111 | from, see [How estimated reading times increase content engagement][martech]. 112 | 113 | ###### Algorithm 114 | 115 | The algorithm works as follows: 116 | 117 | * estimate the WPM (words per minute) of the audience age based on the facts 118 | that English can be read at ±228 WPM (Trauzettel-Klosinski), and that 119 | reading rate increases 14 WPM per grade (Carver) 120 | * apply the readability algorithms [Dale—Chall][dale-chall], 121 | [Automated Readability][automated-readability], [Coleman-Liau][], 122 | [Flesch][], [Gunning-Fog][], [SMOG][], and [Spache][] 123 | * adjust the WPM of the audience for whether the readability algorithms 124 | estimate its above or below their level 125 | * `wordCount / adjustedWpm = readingTime` 126 | 127 | > ⚠️ **Important**: this algorithm is specific to English. 128 | 129 | ###### Parameters 130 | 131 | * `tree` ([`Node`][node]) 132 | — tree to inspect 133 | * `options` ([`Options`][api-options], optional) 134 | — configuration 135 | 136 | ###### Returns 137 | 138 | Estimated reading time in minutes (`number`). 139 | 140 | The result is not rounded so it’s possible to retrieve estimated seconds from 141 | it. 142 | 143 | ### `Options` 144 | 145 | Configuration (TypeScript type). 146 | 147 | ##### Fields 148 | 149 | ###### `age` 150 | 151 | Target age group (`number`, default: `16`). 152 | 153 | This is the age your target audience was still in school. 154 | Set it to 18 if you expect all readers to have finished high school, 21 if you 155 | expect your readers to all be college graduates, etc. 156 | 157 | ## Types 158 | 159 | This package is fully typed with [TypeScript][]. 160 | It exports the additional type [`Options`][api-options]. 161 | 162 | ## Compatibility 163 | 164 | Projects maintained by the unified collective are compatible with maintained 165 | versions of Node.js. 166 | 167 | When we cut a new major release, we drop support for unmaintained versions of 168 | Node. 169 | This means we try to keep the current release line, 170 | `hast-util-reading-time@^2`, compatible with Node.js 16. 171 | 172 | ## Security 173 | 174 | Use of `hast-util-reading-time` is safe. 175 | 176 | ## Related 177 | 178 | * [`rehype-infer-reading-time-meta`][rehype-infer-reading-time-meta] 179 | — infer reading time as file metadata from the document 180 | * [`rehype-meta`][rehype-meta] 181 | — add metadata to the head of a document 182 | 183 | ## Contribute 184 | 185 | See [`contributing.md`][contributing] in [`syntax-tree/.github`][health] for 186 | ways to get started. 187 | See [`support.md`][support] for ways to get help. 188 | 189 | This project has a [code of conduct][coc]. 190 | By interacting with this repository, organization, or community you agree to 191 | abide by its terms. 192 | 193 | ## License 194 | 195 | [MIT][license] © [Titus Wormer][author] 196 | 197 | 198 | 199 | [build-badge]: https://github.com/syntax-tree/hast-util-reading-time/workflows/main/badge.svg 200 | 201 | [build]: https://github.com/syntax-tree/hast-util-reading-time/actions 202 | 203 | [coverage-badge]: https://img.shields.io/codecov/c/github/syntax-tree/hast-util-reading-time.svg 204 | 205 | [coverage]: https://codecov.io/github/syntax-tree/hast-util-reading-time 206 | 207 | [downloads-badge]: https://img.shields.io/npm/dm/hast-util-reading-time.svg 208 | 209 | [downloads]: https://www.npmjs.com/package/hast-util-reading-time 210 | 211 | [size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=hast-util-reading-time 212 | 213 | [size]: https://bundlejs.com/?q=hast-util-reading-time 214 | 215 | [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg 216 | 217 | [backers-badge]: https://opencollective.com/unified/backers/badge.svg 218 | 219 | [collective]: https://opencollective.com/unified 220 | 221 | [chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg 222 | 223 | [chat]: https://github.com/syntax-tree/unist/discussions 224 | 225 | [npm]: https://docs.npmjs.com/cli/install 226 | 227 | [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c 228 | 229 | [esmsh]: https://esm.sh 230 | 231 | [typescript]: https://www.typescriptlang.org 232 | 233 | [license]: license 234 | 235 | [author]: https://wooorm.com 236 | 237 | [health]: https://github.com/syntax-tree/.github 238 | 239 | [contributing]: https://github.com/syntax-tree/.github/blob/main/contributing.md 240 | 241 | [support]: https://github.com/syntax-tree/.github/blob/main/support.md 242 | 243 | [coc]: https://github.com/syntax-tree/.github/blob/main/code-of-conduct.md 244 | 245 | [hast]: https://github.com/syntax-tree/hast 246 | 247 | [node]: https://github.com/syntax-tree/hast#nodes 248 | 249 | [dale-chall]: https://github.com/words/dale-chall-formula 250 | 251 | [automated-readability]: https://github.com/words/automated-readability 252 | 253 | [coleman-liau]: https://github.com/words/coleman-liau 254 | 255 | [flesch]: https://github.com/words/flesch 256 | 257 | [gunning-fog]: https://github.com/words/gunning-fog 258 | 259 | [spache]: https://github.com/words/spache-formula 260 | 261 | [smog]: https://github.com/words/smog-formula 262 | 263 | [rehype-infer-reading-time-meta]: https://github.com/rehypejs/rehype-infer-reading-time-meta 264 | 265 | [rehype-meta]: https://github.com/rehypejs/rehype-meta 266 | 267 | [wiki]: https://en.wikipedia.org/wiki/Words_per_minute#Alphanumeric_entry 268 | 269 | [martech]: https://martech.org/estimated-reading-times-increase-engagement/ 270 | 271 | [api-reading-time]: #readingtimetree-options 272 | 273 | [api-options]: #options 274 | --------------------------------------------------------------------------------