Skip to content

Commit

Permalink
Refactor to move implementation to lib/
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Jan 9, 2023
1 parent cf12471 commit 8437572
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 99 deletions.
101 changes: 2 additions & 99 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,102 +1,5 @@
/**
* @typedef {import('hast').Root} Root
* @typedef {import('hast').Content} Content
* @typedef {import('./lib/index.js').Options} Options
*/

/**
* @typedef {Root | Content} Node
*
* @typedef Options
* Configuration
* @property {number | null | undefined} [age=16]
* Target age group.
*
* This is the age your target audience was still in school.
* Set it to 18 if you expect all readers to have finished high school,
* 21 if you expect your readers to all be college graduates, etc.
*/

import {toText} from 'hast-util-to-text'
import readabilityScores from 'readability-scores'
// @ts-expect-error: untyped.
import median from 'compute-median'

// See source [1].
const firstGradeAge = 5
const highschoolGraduationAge = 18
const graduationAge = 22

// See source [2].
// Note that different other algorithms vary between 200, 230, 270, and 280.
// 228 seems to at least be based in research.
const reasonableWpm = 228
const reasonableWpmMax = 340

// See source [3].
const addedWpmPerGrade = 14
const baseWpm =
reasonableWpm - (highschoolGraduationAge - firstGradeAge) * addedWpmPerGrade

const accuracy = 1e6

/**
* Estimate the reading time, taking readability of the document and a target
* age group into account.
*
* * [1] For more info on US education/grade levels, see:
* <https://en.wikipedia.org/wiki/Educational_stage#United_States>.
* * [2] For the wpm of people reading English, see:
* <https://en.wikipedia.org/wiki/Words_per_minute#Reading_and_comprehension>
* * [3] For information on reading rate, including how grade levels affect
* them, see: <https://en.wikipedia.org/wiki/Reading#Reading_rate>.
*
* And some more background info/history and a few insight on where this comes
* from, see: <https://martech.org/estimated-reading-times-increase-engagement/>.
*
* @param {Node} tree
* Tree to inspect.
* @param {Options | null | undefined} [options]
* Configuration.
* @returns {number}
* Estimated reading time in minutes.
*/
export function readingTime(tree, options) {
const settings = options || {}
// Cap an age to a reasonable and meaningful age in school.
const targetAge = Math.min(
graduationAge,
Math.max(firstGradeAge, Math.round(settings.age || 16))
)
const text = toText(tree)
const scores = readabilityScores(text) || {}
const score = median(
[
scores.daleChall,
scores.ari,
scores.colemanLiau,
scores.fleschKincaid,
scores.smog,
scores.gunningFog
].filter((d) => d !== undefined)
)

if (score === null) {
return 0
}

/** @type {number} */
const readabilityAge = firstGradeAge + score

// WPM the target audience normally reads.
const targetWpm = baseWpm + (targetAge - firstGradeAge) * addedWpmPerGrade

// If the text requires higher comprehension than the target age group is,
// estimated to have, make it a bit slower (and vice versa).
const adjustedWpm =
targetWpm - (readabilityAge - targetAge) * (addedWpmPerGrade / 2)

// Cap it to a WPM that’s reasonable.
const wpm = Math.min(reasonableWpmMax, Math.max(baseWpm, adjustedWpm))

return Math.round((scores.wordCount / wpm) * accuracy) / accuracy
}
export {readingTime} from './lib/index.js'
102 changes: 102 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* @typedef {import('hast').Root} Root
* @typedef {import('hast').Content} Content
*/

/**
* @typedef {Root | Content} Node
*
* @typedef Options
* Configuration
* @property {number | null | undefined} [age=16]
* Target age group.
*
* This is the age your target audience was still in school.
* Set it to 18 if you expect all readers to have finished high school,
* 21 if you expect your readers to all be college graduates, etc.
*/

import {toText} from 'hast-util-to-text'
import readabilityScores from 'readability-scores'
// @ts-expect-error: untyped.
import median from 'compute-median'

// See source [1].
const firstGradeAge = 5
const highschoolGraduationAge = 18
const graduationAge = 22

// See source [2].
// Note that different other algorithms vary between 200, 230, 270, and 280.
// 228 seems to at least be based in research.
const reasonableWpm = 228
const reasonableWpmMax = 340

// See source [3].
const addedWpmPerGrade = 14
const baseWpm =
reasonableWpm - (highschoolGraduationAge - firstGradeAge) * addedWpmPerGrade

const accuracy = 1e6

/**
* Estimate the reading time, taking readability of the document and a target
* age group into account.
*
* * [1] For more info on US education/grade levels, see:
* <https://en.wikipedia.org/wiki/Educational_stage#United_States>.
* * [2] For the wpm of people reading English, see:
* <https://en.wikipedia.org/wiki/Words_per_minute#Reading_and_comprehension>
* * [3] For information on reading rate, including how grade levels affect
* them, see: <https://en.wikipedia.org/wiki/Reading#Reading_rate>.
*
* And some more background info/history and a few insight on where this comes
* from, see: <https://martech.org/estimated-reading-times-increase-engagement/>.
*
* @param {Node} tree
* Tree to inspect.
* @param {Options | null | undefined} [options]
* Configuration.
* @returns {number}
* Estimated reading time in minutes.
*/
export function readingTime(tree, options) {
const settings = options || {}
// Cap an age to a reasonable and meaningful age in school.
const targetAge = Math.min(
graduationAge,
Math.max(firstGradeAge, Math.round(settings.age || 16))
)
const text = toText(tree)
const scores = readabilityScores(text) || {}
const score = median(
[
scores.daleChall,
scores.ari,
scores.colemanLiau,
scores.fleschKincaid,
scores.smog,
scores.gunningFog
].filter((d) => d !== undefined)
)

if (score === null) {
return 0
}

/** @type {number} */
const readabilityAge = firstGradeAge + score

// WPM the target audience normally reads.
const targetWpm = baseWpm + (targetAge - firstGradeAge) * addedWpmPerGrade

// If the text requires higher comprehension than the target age group is,
// estimated to have, make it a bit slower (and vice versa).
const adjustedWpm =
targetWpm - (readabilityAge - targetAge) * (addedWpmPerGrade / 2)

// Cap it to a WPM that’s reasonable.
const wpm = Math.min(reasonableWpmMax, Math.max(baseWpm, adjustedWpm))

return Math.round((scores.wordCount / wpm) * accuracy) / accuracy
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"main": "index.js",
"types": "index.d.ts",
"files": [
"lib/",
"index.d.ts",
"index.js"
],
Expand Down

0 comments on commit 8437572

Please sign in to comment.