Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat(gatsby-transformer-remark): Better timeToRead for Chinese/Japanese texts #21312

Merged
merged 5 commits into from
Feb 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ Object {
exports[`Wordcount and timeToRead are generated correctly from schema correctly generate timeToRead for CJK 1`] = `
Object {
"timeToRead": 2,
"timeToRead": 1,
}
`;
Expand Down
11 changes: 3 additions & 8 deletions packages/gatsby-transformer-remark/src/__tests__/extend-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -867,18 +867,13 @@ In quis lectus sed eros efficitur luctus. Morbi tempor, nisl eget feugiat tincid
以声明式编写 UI,可以让你的代码更加可靠,且方便调试。
创建拥有各自状态的组件,再由这些组件构成更加复杂的 UI。
组件逻辑使用 JavaScript 编写而非模版,因此你可以轻松地在应用中传递数据,并使得状态与 DOM 分离。

React は、インタラクティブなユーザーインターフェイスの作成にともなう苦痛を取り除きます。アプリケーションの各状態に対応するシンプルな View を設計するだけで、React はデータの変更を検知し、関連するコンポーネントだけを効率的に更新、描画します。
宣言的な View を用いてアプリケーションを構築することで、コードはより見通しが立ちやすく、デバッグのしやすいものになります。
自分自身の状態を管理するカプセル化されたコンポーネントをまず作成し、これらを組み合わせることで複雑なユーザーインターフェイスを構築します。
コンポーネントのロジックは、Template ではなく JavaScript そのもので書くことができるので、様々なデータをアプリケーション内で簡単に取り回すことができ、かつ DOM に状態を持たせないようにすることができます。

React는 상호작용이 많은 UI를 만들 때 생기는 어려움을 줄여줍니다. 애플리케이션의 각 상태에 대한 간단한 뷰만 설계하세요. 그럼 React는 데이터가 변경됨에 따라 적절한 컴포넌트만 효율적으로 갱신하고 렌더링합니다.선언형 뷰는 코드를 예측 가능하고 디버그하기 쉽게 만들어 줍니다.
宣言的な View React は、インタラクティブなユーザインターフェイスの作成にともなう苦痛を取り除きます。アプリケーションの各状態に対応するシンプルな View を設計するだけで、React はデータの変更を検知し、関連するコンポーネントだけを効率的に更新、描画します。 宣言的な View を用いてアプリケーションを構築することで、コードはより見通しが立ちやすく、デバッグのしやすいものになります。
コンポーネントベース 自分自身の状態を管理するカプセル化されたコンポーネントをまず作成し、これらを組み合わせることで複雑なユーザインターフェイスを構築します。 コンポーネントのロジックは、Template ではなく JavaScript そのもので書くことができるので、様々なデータをアプリケーション内で簡単に取り回すことができ、かつ DOM に状態を持たせないようにすることができます。
`,
`timeToRead`,
node => {
expect(node).toMatchSnapshot()
expect(node.timeToRead).toEqual(2)
expect(node.timeToRead).toEqual(1)
}
)

Expand Down
17 changes: 2 additions & 15 deletions packages/gatsby-transformer-remark/src/extend-node-type.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const Remark = require(`remark`)
const select = require(`unist-util-select`)
const sanitizeHTML = require(`sanitize-html`)
const _ = require(`lodash`)
const visit = require(`unist-util-visit`)
const toHAST = require(`mdast-util-to-hast`)
Expand All @@ -22,6 +21,7 @@ const {
findLastTextNode,
} = require(`./hast-processing`)
const codeHandler = require(`./code-handler`)
const { timeToRead } = require(`./utils/time-to-read`)

let fileNodes
let pluginsCacheStr = ``
Expand Down Expand Up @@ -604,20 +604,7 @@ module.exports = (
timeToRead: {
type: `Int`,
resolve(markdownNode) {
return getHTML(markdownNode).then(html => {
let timeToRead = 0
const pureText = sanitizeHTML(html, { allowTags: [] })
const avgWPM = 265
const wordCount =
_.words(pureText).length +
_.words(pureText, /[\p{sc=Katakana}\p{sc=Hiragana}\p{sc=Han}]/gu)
.length
timeToRead = Math.round(wordCount / avgWPM)
if (timeToRead === 0) {
timeToRead = 1
}
return timeToRead
})
return getHTML(markdownNode).then(timeToRead)
},
},
tableOfContents: {
Expand Down
67 changes: 67 additions & 0 deletions packages/gatsby-transformer-remark/src/utils/time-to-read.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const sanitizeHTML = require(`sanitize-html`)
const _ = require(`lodash`)

// Unicode ranges for Han (Chinese) and Hiragana/Katakana (Japanese) characters
const cjRanges = [
[11904, 11930], // Han
[11931, 12020],
[12032, 12246],
[12293, 12294],
[12295, 12296],
[12321, 12330],
[12344, 12348],
[13312, 19894],
[19968, 40939],
[63744, 64110],
[64112, 64218],
[131072, 173783],
[173824, 177973],
[177984, 178206],
[178208, 183970],
[183984, 191457],
[194560, 195102],
[12353, 12439], // Hiragana
[12445, 12448],
[110593, 110879],
[127488, 127489],
[12449, 12539], // Katakana
[12541, 12544],
[12784, 12800],
[13008, 13055],
[13056, 13144],
[65382, 65392],
[65393, 65438],
[110592, 110593],
]

function isCjChar(char) {
const charCode = char.codePointAt(0)
return cjRanges.some(([from, to]) => charCode >= from && charCode < to)
}

export const timeToRead = html => {
let timeToRead = 0
const pureText = sanitizeHTML(html, { allowTags: [] })
const avgWPM = 265

let latinChars = []
let cjChars = []

for (const char of pureText) {
if (isCjChar(char)) {
cjChars.push(char)
} else {
latinChars.push(char)
}
}

// Multiply non-latin character string length by 0.56, because
// on average one word consists of 2 characters in both Chinese and Japanese
const wordCount = _.words(latinChars.join(``)).length + cjChars.length * 0.56

timeToRead = Math.round(wordCount / avgWPM)
if (timeToRead === 0) {
timeToRead = 1
}
return timeToRead
}