Skip to content

Commit 510968b

Browse files
committed
.
0 parents  commit 510968b

File tree

12 files changed

+524
-0
lines changed

12 files changed

+524
-0
lines changed

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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

.github/workflows/bb.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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}}

.github/workflows/main.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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@v2
11+
- uses: dcodeIO/setup-node-nvm@master
12+
with:
13+
node-version: ${{matrix.node}}
14+
- run: npm install
15+
- run: npm test
16+
- uses: codecov/codecov-action@v1
17+
strategy:
18+
matrix:
19+
node:
20+
- lts/erbium
21+
- node

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.DS_Store
2+
*.d.ts
3+
*.log
4+
coverage/
5+
node_modules/
6+
yarn.lock

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
coverage/
2+
*.md

index.js

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/**
2+
* @typedef {import('hast').Root} Root
3+
* @typedef {import('hast').Content} Content
4+
* @typedef {import('hast').Text} Text
5+
* @typedef {Root|Content} Node
6+
*
7+
* @typedef Options
8+
* Configuration.
9+
* @property {number} [size=140]
10+
* Number of characters to truncate to.
11+
* @property {string} [ellipsis]
12+
* Value to use at truncation point.
13+
* @property {number} [maxCharacterStrip=30]
14+
* The algorithm attempts to break right after a word rather than the exact
15+
* `size`.
16+
* Take for example the `|`, which is the actual break defined by `size`, and
17+
* the `…` is the location where the ellipsis is placed: `This… an|d that`.
18+
* Breaking at `|` would at best look bad but could likely result in things
19+
* such as `ass…` for `assignment` — which is not ideal.
20+
* `maxCharacterStrip` defines how far back the algorithm will walk to find
21+
* a pretty word break.
22+
* This prevents a potential slow operation on larger `size`s without any
23+
* whitespace.
24+
* If `maxCharacterStrip` characters are walked back and no nice break point
25+
* is found, the bad break point.
26+
* Set `maxCharacterStrip: 0` to not find a nice break.
27+
* @property {Content[]} [ignore=[]]
28+
* Nodes to exclude from the resulting tree.
29+
* These are not counted towards `size`.
30+
*/
31+
32+
import {unicodeWhitespace, unicodePunctuation} from 'micromark-util-character'
33+
34+
/**
35+
* Truncate the tree to a certain number of characters.
36+
*
37+
* @template {Node} Tree
38+
* @param {Tree} tree
39+
* @param {Options} [options]
40+
* @returns {Tree}
41+
*/
42+
export function truncate(tree, options = {}) {
43+
// To do: support units.
44+
const {size = 140, ellipsis, maxCharacterStrip = 30, ignore = []} = options
45+
let searchSize = 0
46+
/** @type {Text|undefined} */
47+
let overflowingText
48+
const result = preorder(tree)
49+
50+
if (overflowingText) {
51+
const uglyBreakpoint = size - searchSize
52+
let breakpoint = uglyBreakpoint
53+
54+
// If the number at the break is not an alphanumerical…
55+
if (unicodeAlphanumeric(overflowingText.value.charCodeAt(breakpoint))) {
56+
let remove = -1
57+
58+
// Move back while the character before breakpoint is an alphanumerical.
59+
while (
60+
breakpoint &&
61+
++remove < maxCharacterStrip &&
62+
unicodeAlphanumeric(overflowingText.value.charCodeAt(breakpoint - 1))
63+
) {
64+
breakpoint--
65+
}
66+
67+
// Move back while the character before breakpoint is *not* an alphanumerical.
68+
while (
69+
breakpoint &&
70+
++remove < maxCharacterStrip &&
71+
!unicodeAlphanumeric(overflowingText.value.charCodeAt(breakpoint - 1))
72+
) {
73+
breakpoint--
74+
}
75+
}
76+
77+
overflowingText.value = overflowingText.value.slice(
78+
0,
79+
breakpoint || uglyBreakpoint
80+
)
81+
82+
if (ellipsis) {
83+
overflowingText.value += ellipsis
84+
}
85+
}
86+
87+
// @ts-expect-error: `preorder` for the top node always returns itself.
88+
return result
89+
90+
/**
91+
* @param {Node} node
92+
* @returns {Node|undefined}
93+
*/
94+
function preorder(node) {
95+
if (node.type === 'text') {
96+
if (searchSize + node.value.length > size) {
97+
overflowingText = {...node}
98+
return overflowingText
99+
}
100+
101+
searchSize += node.value.length
102+
}
103+
104+
/** @type {Node} */
105+
const replacement = {...node}
106+
107+
if ('children' in node) {
108+
/** @type {Content[]} */
109+
const children = []
110+
let index = -1
111+
112+
while (++index < node.children.length) {
113+
const child = node.children[index]
114+
115+
if (!ignore.includes(child)) {
116+
const result = preorder(child)
117+
// @ts-expect-error: assume content matches.
118+
if (result) children.push(result)
119+
}
120+
121+
if (overflowingText) {
122+
break
123+
}
124+
}
125+
126+
// @ts-expect-error: assume content matches.
127+
replacement.children = children
128+
}
129+
130+
return replacement
131+
}
132+
}
133+
134+
/**
135+
* @param {number} code
136+
* @returns {boolean}
137+
*/
138+
function unicodeAlphanumeric(code) {
139+
return !unicodeWhitespace(code) && !unicodePunctuation(code)
140+
}

license

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
(The MIT License)
2+
3+
Copyright (c) 2021 Titus Wormer <tituswormer@gmail.com>
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.

package.json

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
{
2+
"name": "hast-util-truncate",
3+
"version": "0.0.0",
4+
"description": "hast utility to truncate the tree to a certain number of characters",
5+
"license": "MIT",
6+
"keywords": [
7+
"unist",
8+
"hast",
9+
"hast-util",
10+
"util",
11+
"utility",
12+
"html",
13+
"truncate",
14+
"excerpt",
15+
"summary"
16+
],
17+
"repository": "syntax-tree/hast-util-truncate",
18+
"bugs": "https://github.com/syntax-tree/hast-util-truncate/issues",
19+
"funding": {
20+
"type": "opencollective",
21+
"url": "https://opencollective.com/unified"
22+
},
23+
"author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
24+
"contributors": [
25+
"Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
26+
],
27+
"sideEffects": false,
28+
"type": "module",
29+
"main": "index.js",
30+
"types": "index.d.ts",
31+
"files": [
32+
"index.d.ts",
33+
"index.js"
34+
],
35+
"dependencies": {
36+
"@types/hast": "^2.0.0",
37+
"micromark-util-character": "^1.1.0"
38+
},
39+
"devDependencies": {
40+
"@types/tape": "^4.0.0",
41+
"c8": "^7.0.0",
42+
"hast-util-select": "^5.0.1",
43+
"hastscript": "^7.0.2",
44+
"prettier": "^2.0.0",
45+
"remark-cli": "^10.0.0",
46+
"remark-preset-wooorm": "^9.0.0",
47+
"rimraf": "^3.0.0",
48+
"tape": "^5.0.0",
49+
"type-coverage": "^2.0.0",
50+
"typescript": "^4.0.0",
51+
"xo": "^0.44.0"
52+
},
53+
"scripts": {
54+
"prepack": "npm run build && npm run format",
55+
"build": "rimraf \"*.d.ts\" && tsc && type-coverage",
56+
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
57+
"test-api": "node test.js",
58+
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js",
59+
"test": "npm run build && npm run format && npm run test-coverage"
60+
},
61+
"prettier": {
62+
"tabWidth": 2,
63+
"useTabs": false,
64+
"singleQuote": true,
65+
"bracketSpacing": false,
66+
"semi": false,
67+
"trailingComma": "none"
68+
},
69+
"xo": {
70+
"prettier": true
71+
},
72+
"remarkConfig": {
73+
"plugins": [
74+
"preset-wooorm"
75+
]
76+
},
77+
"typeCoverage": {
78+
"atLeast": 100,
79+
"detail": true,
80+
"strict": true,
81+
"ignoreCatch": true
82+
}
83+
}

0 commit comments

Comments
 (0)