Skip to content

Commit

Permalink
Add support for Slack label/data (section/reading time)
Browse files Browse the repository at this point in the history
This adds support for showing the section of an article and the estimated
reading time.
This data is used by Slack to unfurl pages (and probably others), and is
misleadingly a “twitter” metadata field.

Closes GH-1.
  • Loading branch information
wooorm committed Sep 10, 2021
1 parent 226c1af commit 8232a49
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 6 deletions.
49 changes: 48 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@
* `'2019-12-03T19:13:00.000Z'`).
*
* *Note*: parsing a string is [inconsistent][timestamp], prefer dates.
* @property {number|[number]|[number, number]} [readingTime]
* Reading time of the document in minutes (`number`, optional).
* If two numbers are given, they represent a range of two estimates.
*
* @typedef DataFields
* @property {boolean} first
Expand Down Expand Up @@ -124,7 +127,8 @@ const generators = [
twitterCard,
twitterImage,
twitterSite,
twitterCreator
twitterCreator,
twitterData
]

/**
Expand Down Expand Up @@ -498,6 +502,49 @@ function twitterCreator(data, root) {
}
}

/**
* @param {Data} data
* @param {Element} root
*/
function twitterData(data, root) {
const {twitter, section, readingTime} = data
const time = (
readingTime
? Array.isArray(readingTime)
? readingTime
: [readingTime, readingTime]
: []
).map((d) => Math.ceil(d))
/** @type {string|undefined} */
let timeLabel

if (time.length > 1 && time[0] !== time[1]) {
timeLabel = time[0] + '-' + time[1] + ' minutes'
} else if (time[0]) {
timeLabel = time[0] + ' minute' + (time[0] > 1 ? 's' : '')
}

const items = twitter
? [
{label: 'Posted in', data: section},
{label: 'Reading time', data: timeLabel}
].filter((d) => d.data !== undefined)
: []

let index = -1

while (++index < items.length) {
const no = index + 1
ensure(
data,
root,
'meta[name=twitter:label' + no + ']'
).properties.content = items[index].label
ensure(data, root, 'meta[name=twitter:data' + no + ']').properties.content =
items[index].data
}
}

/**
* @param {Data} data
* @param {Root|Element} root
Expand Down
90 changes: 87 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ rehype()
height: '550'
},
published: '2019-12-02T10:00:00.000Z',
modified: '2019-12-03T19:13:00.000Z'
modified: '2019-12-03T19:13:00.000Z',
readingTime: 11.1
})
.process('')
.then((file) => {
Expand Down Expand Up @@ -133,6 +134,10 @@ no issues found
<meta name="twitter:image:alt" content="M.T.A. map designed in 1979">
<meta name="twitter:site" content="@nytimes">
<meta name="twitter:creator" content="@jane">
<meta name="twitter:label1" content="Posted in">
<meta name="twitter:data1" content="New York">
<meta name="twitter:label2" content="Reading time">
<meta name="twitter:data2" content="12 minutes">
</head>
```

Expand Down Expand Up @@ -201,7 +206,11 @@ Whether to add Twitter metadata (`boolean`, default: `false`).
Affects: [`meta[name=twitter:card]`][m-twitter-card],
[`meta[name=twitter:image]`][m-twitter-image],
[`meta[name=twitter:site]`][m-twitter-site],
[`meta[name=twitter:creator]`][m-twitter-creator].
[`meta[name=twitter:creator]`][m-twitter-creator],
[`meta[name=twitter:label1]`][m-twitter-label1],
[`meta[name=twitter:data1]`][m-twitter-data1],
[`meta[name=twitter:label2]`][m-twitter-label2],
[`meta[name=twitter:data2]`][m-twitter-data2].

###### `config.copyright`

Expand Down Expand Up @@ -319,7 +328,8 @@ Affects: [`meta[name=description]`][m-description],
Section associated with the document (`string`, optional, example:
`'New York'`).

Affects: [`meta[property=article:section]`][m-article-section].
Affects: [`meta[property=article:section]`][m-article-section], [`meta[name=twitter:label1]`][m-twitter-label1],
[`meta[name=twitter:data1]`][m-twitter-data1].

###### `config.tags`

Expand Down Expand Up @@ -367,6 +377,17 @@ Date the document was last modified (`Date` or `string`, optional, example:

Affects: [`meta[property=article:modified_time]`][m-article-modified-time].

###### `config.readingTime`

Estimated reading time in minutes for the document (`[number, number]` or
`number`, optional, example: `1.219403`).
If two numbers are given, they represent a range of two estimates.

Affects: [`meta[name=twitter:label1]`][m-twitter-label1],
[`meta[name=twitter:data1]`][m-twitter-data1],
[`meta[name=twitter:label2]`][m-twitter-label2],
[`meta[name=twitter:data2]`][m-twitter-data2].

## Metadata

The following metadata can be added by `rehype-meta`.
Expand Down Expand Up @@ -772,6 +793,59 @@ If `twitter` is `true` and `authorTwitter` is `'@example'`:
<meta name="twitter:creator" content="@example">
```

###### `meta[name=twitter:label1]`

###### `meta[name=twitter:data1]`

Affected by: [`twitter`][c-twitter], [`section`][c-section],
[`readingTime`][c-readingtime].

*Note*: this data is used by Slack, not by Twitter.

If `twitter` is not `true`, `meta[name=twitter:label1]` and
`meta[name=twitter:data1]` are not added.

If `twitter` is `true` and `section` is `'Food'`:

```html
<meta name="twitter:label1" content="Posted in">
<meta name="twitter:data1" content="Food">
```

If `twitter` is `true`, `section` is not defined, and `reading time` is `3.083`:

```html
<meta name="twitter:label1" content="Reading time">
<meta name="twitter:data1" content="4 minutes">
```

###### `meta[name=twitter:label2]`

###### `meta[name=twitter:data2]`

Affected by: [`twitter`][c-twitter], [`section`][c-section],
[`readingTime`][c-readingtime].

*Note*: this data is used by Slack, not by Twitter.

If `twitter` is not `true`, `section` is not defined, or `readingTime` is not
defined, `meta[name=twitter:label2]` and `meta[name=twitter:data2]` are not
added.

If `twitter` is `true`, `section` is defined, and `readingTime` is `0.8`:

```html
<meta name="twitter:label2" content="Reading time">
<meta name="twitter:data2" content="1 minute">
```

If `twitter` is `true`, `section` is defined, and `readingTime` is `[8, 12]`:

```html
<meta name="twitter:label2" content="Reading time">
<meta name="twitter:data2" content="8-12 minutes">
```

## Security

Use of `rehype-meta` is relatively safe, however, it is possible for an attacker
Expand Down Expand Up @@ -902,6 +976,8 @@ abide by its terms.

[c-modified]: #configmodified

[c-readingtime]: #configreadingtime

[m-title]: #title

[m-canonical]: #linkrelcanonical
Expand Down Expand Up @@ -945,3 +1021,11 @@ abide by its terms.
[m-twitter-site]: #metanametwittersite

[m-twitter-creator]: #metanametwittercreator

[m-twitter-label1]: #metanametwitterlabel1

[m-twitter-data1]: #metanametwitterdata1

[m-twitter-label2]: #metanametwitterlabel2

[m-twitter-data2]: #metanametwitterdata2
84 changes: 82 additions & 2 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,76 @@ test('rehypeMeta', (t) => {
'should add up to 6 `meta[property=article:tag]` if `og` is true, `type` is `article`, and `tags` are set'
)

st.equal(
rehype()
.data('settings', {fragment: true})
.use(rehypeMeta, {twitter: true, section: 'a'})
.processSync('')
.toString(),
[
'<head>',
'<meta name="twitter:card" content="summary">',
'<meta name="twitter:label1" content="Posted in">',
'<meta name="twitter:data1" content="a">',
'</head>',
''
].join('\n'),
'should add `meta[name=twitter:label1]`, `meta[name=twitter:data1]` if `twitter` is true and `section` is set'
)

st.equal(
rehype()
.data('settings', {fragment: true})
.use(rehypeMeta, {twitter: true, readingTime: 0.1})
.processSync('')
.toString(),
[
'<head>',
'<meta name="twitter:card" content="summary">',
'<meta name="twitter:label1" content="Reading time">',
'<meta name="twitter:data1" content="1 minute">',
'</head>',
''
].join('\n'),
'should add `meta[name=twitter:label1]`, `meta[name=twitter:data1]` if `twitter` is true, `readingTime` is set, and section is not'
)

st.equal(
rehype()
.data('settings', {fragment: true})
.use(rehypeMeta, {twitter: true, section: 'a', readingTime: 0.1})
.processSync('')
.toString(),
[
'<head>',
'<meta name="twitter:card" content="summary">',
'<meta name="twitter:label1" content="Posted in">',
'<meta name="twitter:data1" content="a">',
'<meta name="twitter:label2" content="Reading time">',
'<meta name="twitter:data2" content="1 minute">',
'</head>',
''
].join('\n'),
'should add `meta[name=twitter:label2]`, `meta[name=twitter:data2]` if `twitter` is true, `readingTime` is set, and section is set'
)

st.equal(
rehype()
.data('settings', {fragment: true})
.use(rehypeMeta, {twitter: true, readingTime: [8.2, 11.1]})
.processSync('')
.toString(),
[
'<head>',
'<meta name="twitter:card" content="summary">',
'<meta name="twitter:label1" content="Reading time">',
'<meta name="twitter:data1" content="9-12 minutes">',
'</head>',
''
].join('\n'),
'should set `meta[name=twitter:data1]` to a `readingTime` range if `readingTime` is a tuple'
)

st.end()
})

Expand Down Expand Up @@ -782,7 +852,8 @@ test('rehypeMeta', (t) => {
height: '550'
},
published: '2019-12-02T10:00:00.000Z',
modified: '2019-12-03T19:13:00.000Z'
modified: '2019-12-03T19:13:00.000Z',
readingTime: 11.1
})
.processSync('')
.toString(),
Expand Down Expand Up @@ -818,6 +889,10 @@ test('rehypeMeta', (t) => {
'<meta name="twitter:image:alt" content="M.T.A. map designed in 1979">',
'<meta name="twitter:site" content="@nytimes">',
'<meta name="twitter:creator" content="@jane">',
'<meta name="twitter:label1" content="Posted in">',
'<meta name="twitter:data1" content="New York">',
'<meta name="twitter:label2" content="Reading time">',
'<meta name="twitter:data2" content="12 minutes">',
'</head>',
''
].join('\n'),
Expand Down Expand Up @@ -867,7 +942,8 @@ test('rehypeMeta', (t) => {
height: '1012'
},
published: '2014-06-30T15:01:35-05:00',
modified: '2017-04-26T22:37:10-05:00'
modified: '2017-04-26T22:37:10-05:00',
readingTime: 3.083
})
.processSync('')
.toString(),
Expand Down Expand Up @@ -907,6 +983,10 @@ test('rehypeMeta', (t) => {
'<meta name="twitter:image" content="https://hostthetoast.com/wp-content/uploads/2014/06/Salt-and-Vinegar-Potatoes-6.jpg">',
'<meta name="twitter:site" content="@hostthetoast">',
'<meta name="twitter:creator" content="@jane">',
'<meta name="twitter:label1" content="Posted in">',
'<meta name="twitter:data1" content="Food">',
'<meta name="twitter:label2" content="Reading time">',
'<meta name="twitter:data2" content="4 minutes">',
'</head>',
'<body>',
'<script src="index.js"></script>',
Expand Down

0 comments on commit 8232a49

Please sign in to comment.