Skip to content

Commit 8dc706d

Browse files
author
Saiya
committed
feat: 1. Add an option called customNodeStrategy to the custom node truncate strategy #41
2. Rewrite the library to a purely functional library to prevent configurations from getting polluted. 3. Update the build toolchain to Vite and Vitetest.
1 parent cdff5d5 commit 8dc706d

File tree

10 files changed

+2030
-3712
lines changed

10 files changed

+2030
-3712
lines changed

.eslintrc.cjs

Lines changed: 0 additions & 15 deletions
This file was deleted.

eslint.config.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import eslint from '@eslint/js';
2+
import tseslint from 'typescript-eslint';
3+
4+
export default tseslint.config(
5+
eslint.configs.recommended,
6+
...tseslint.configs.strict,
7+
...tseslint.configs.stylistic,
8+
);

package.json

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,37 @@
55
"main": "dist/truncate.cjs.js",
66
"module": "dist/truncate.es.js",
77
"types": "dist/truncate.d.ts",
8-
"source": "./src/truncate.ts",
8+
"exports": {
9+
".": {
10+
"import": "./dist/truncate.es.js",
11+
"require": "./dist/truncate.cjs.js"
12+
}
13+
},
914
"files": [
1015
"dist"
1116
],
1217
"scripts": {
13-
"dev": "ts-node ./test/demo.ts",
14-
"prebuild": "npm run test && npm run lint && npm run clean",
15-
"build": "parcel build src/truncate.ts --no-source-maps",
16-
"postbuild": "node ./scripts/post-build.js",
17-
"clean": "rimraf dist/*",
18-
"prepublish": "npm run build",
19-
"test": "jest",
20-
"test:server": "jest --watch",
21-
"lint": "eslint src --ext .ts"
18+
"dev": "tsx ./test/demo.ts",
19+
"build": "vite build",
20+
"prepublish": "yarn run build",
21+
"test": "vitest --coverage",
22+
"test:server": "vitest --watch",
23+
"lint": "eslint src"
2224
},
2325
"dependencies": {
2426
"cheerio": "1.0.0-rc.12"
2527
},
2628
"devDependencies": {
27-
"@parcel/packager-ts": "^2.12.0",
28-
"@parcel/transformer-typescript-types": "^2.12.0",
29-
"@types/jest": "^29.5.2",
30-
"@types/node": "^20.3.1",
31-
"@typescript-eslint/eslint-plugin": "^5.59.11",
32-
"@typescript-eslint/parser": "^5.59.11",
33-
"eslint": "^8.43.0",
34-
"jest": "^29.5.0",
35-
"parcel": "^2.12.0",
36-
"rimraf": "^5.0.1",
37-
"ts-jest": "^29.1.0",
38-
"ts-node": "^10.9.1",
39-
"typescript": "^5.1.3"
29+
"@eslint/js": "^9.12.0",
30+
"@types/eslint__js": "^8.42.3",
31+
"@vitest/coverage-v8": "2.1.2",
32+
"eslint": "^9.12.0",
33+
"tsx": "^4.19.1",
34+
"typescript": "^5.6.3",
35+
"typescript-eslint": "^8.8.1",
36+
"vite": "^5.4.8",
37+
"vite-plugin-dts": "^4.2.4",
38+
"vitest": "^2.1.2"
4039
},
4140
"repository": {
4241
"type": "git",

readme.md

Lines changed: 157 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,39 +38,80 @@ Click **<https://npm.runkit.com/truncate-html>** to try.
3838

3939
## API
4040

41-
```javascript
41+
```ts
42+
/**
43+
* custom node strategy, default to Cheerio<AnyNode>
44+
* * 'remove' to remove the node
45+
* * 'keep' to keep the node(and anything inside it) anyway
46+
* * Cheerio<AnyNode> truncate the returned node
47+
* * undefined or any falsy value to truncate original node
48+
*/
49+
type ICustomNodeStrategy = (node: Cheerio<AnyNode>) => 'remove' | 'keep' | Cheerio<AnyNode> | undefined
50+
4251
/**
43-
* truncate html
44-
* @method truncate(html, [length], [options])
45-
* @param {String|CheerioStatic} html html string to truncate, or existing cheerio instance(aka cheerio $)
46-
* @param {Object|number} length how many letters(words if `byWords` is true) you want reserve
47-
* @param {Object|null} options
48-
* @param {Boolean} [options.stripTags] remove all tags, default false
49-
* @param {String} [options.ellipsis] ellipsis sign, default '...'
50-
* @param {Boolean} [options.decodeEntities] decode html entities(e.g. convert `&amp;` to `&`) before
51-
* counting length, default false
52-
* @param {String|Array} [options.excludes] elements' selector you want ignore
53-
* @param {Number} [options.length] how many letters(words if `byWords` is true)
54-
* you want reserve
55-
* @param {Boolean} [options.byWords] if true, length means how many words to reserve
56-
* @param {Boolean|Number} [options.reserveLastWord] how to deal with when truncate in the middle of a word
57-
* 1. by default, just cut at that position.
58-
* 2. set it to true, with max exceed 10 letters can exceed to reserver the last word
59-
* 3. set it to a positive number decide how many letters can exceed to reserve the last word
60-
* 4. set it to negetive number to remove the last word if cut in the middle.
61-
* @param {Boolean} [options.trimTheOnlyWord] whether to trim the only word when `reserveLastWord` < 0
62-
* if reserveLastWord set to negetive number, and there is only one word in the html string,
63-
* when trimTheOnlyWord set to true, the extra letters will be cutted if word's length longer
64-
* than `length`.
65-
* see issue #23 for more details
66-
* @param {Boolean} [options.keepWhitespaces] keep whitespaces, by default continuous
67-
* spaces will be replaced with one space
68-
* set it true to reserve them, and continuous spaces will count as one
69-
* @return {String}
52+
* truncate-html full options object
7053
*/
71-
truncate(html, [length], [options])
54+
interface IFullOptions {
55+
/**
56+
* remove all tags, default false
57+
*/
58+
stripTags: boolean
59+
/**
60+
* ellipsis sign, default '...'
61+
*/
62+
ellipsis: string
63+
/**
64+
* decode html entities(e.g. convert `&amp;` to `&`) before counting length, default false
65+
*/
66+
decodeEntities: boolean
67+
/**
68+
* elements' selector you want ignore
69+
*/
70+
excludes: string | string[]
71+
/**
72+
* custom node strategy, default to Cheerio<AnyNode>
73+
* * 'remove' to remove the node
74+
* * 'keep' to keep the node(and anything inside it) anyway
75+
* * Cheerio<AnyNode> truncate the returned node
76+
* * undefined or any falsy value to truncate original node
77+
*/
78+
customNodeStrategy: ICustomNodeStrategy
79+
/**
80+
* how many letters(words if `byWords` is true) you want reserve
81+
*/
82+
length: number
83+
/**
84+
* if true, length means how many words to reserve
85+
*/
86+
byWords: boolean
87+
/**
88+
* how to deal with when truncate in the middle of a word
89+
* 1. by default, just cut at that position.
90+
* 2. set it to true, with max exceed 10 letters can exceed to reserver the last word
91+
* 3. set it to a positive number decide how many letters can exceed to reserve the last word
92+
* 4. set it to negative number to remove the last word if cut in the middle.
93+
*/
94+
reserveLastWord: boolean | number
95+
/**
96+
* if reserveLastWord set to negative number, and there is only one word in the html string, when trimTheOnlyWord set to true, the extra letters will be sliced if word's length longer than `length`.
97+
* see issue #23 for more details
98+
*/
99+
trimTheOnlyWord: boolean
100+
/**
101+
* keep whitespaces, by default continuous paces will
102+
* be replaced with one space, set it true to keep them
103+
*/
104+
keepWhitespaces: boolean
105+
}
106+
107+
/**
108+
* options interface for function
109+
*/
110+
type IOptions = Partial<IFullOptions>
111+
112+
function truncate(html: string | CheerioAPI, length?: number | IOptions, truncateOptions?: IOptions): string
72113
// and truncate.setup to change default options
73-
truncate.setup(options)
114+
truncate.setup(options: IOptions): void
74115
```
75116

76117
### Default options
@@ -92,15 +133,15 @@ You can change default options by using `truncate.setup`
92133

93134
e.g.
94135

95-
```js
136+
```ts
96137
truncate.setup({ stripTags: true, length: 10 })
97138
truncate('<p><img src="xxx.jpg">Hello from earth!</p>')
98139
// => Hello from
99140
```
100141

101142
or use existing [cheerio instance](https://github.com/cheeriojs/cheerio#loading)
102143

103-
```js
144+
```ts
104145
import * as cheerio from 'cheerio'
105146
truncate.setup({ stripTags: true, length: 10 })
106147
// truncate option `decodeEntities` will not work
@@ -114,12 +155,68 @@ truncate($)
114155
// => Hello from
115156
```
116157

158+
117159
## Notice
118160

119161
### Typescript support
120162

121163
This lib is written with typescript and has a type definition file along with it. ~~You may need to update your `tsconfig.json` by adding `"esModuleInterop": true` to the `compilerOptions` if you encounter some typing errors, see [#19](https://github.com/oe/truncate-html/issues/19).~~
122164

165+
```ts
166+
import truncate, { type IOptions } from 'truncate-html'
167+
168+
169+
const html = '<p><img src="abc.png"><i>italic<b>bold</b></i>This is a string</p> for test.'
170+
171+
const options: IOptions = {
172+
length: 10,
173+
byWords: true
174+
}
175+
176+
truncate(html, options)
177+
// => <p><img src="abc.png"><i>italic<b>bold...</b></i></p>
178+
```
179+
180+
### custom node truncate strategy
181+
In complex html string, you may want to keep some special elements and truncate the others. You can use `customNodeStrategy` to achieve this:
182+
* return `'remove'` to remove the node
183+
* `'keep'` to keep the node(and anything inside it) anyway
184+
* `Cheerio<AnyNode>` to truncate the returned node, or any falsy value to truncate the original node.
185+
186+
```ts
187+
import truncate, { type IOptions, type ICustomNodeStrategy } from 'truncate-html'
188+
189+
// argument node is a cheerio instance
190+
const customNodeStrategy: ICustomNodeStrategy = node => {
191+
// remove img tag
192+
if (node.is('img')) {
193+
return 'remove'
194+
}
195+
// keep italic tag and its children
196+
if (node.is('i')) {
197+
return 'keep'
198+
}
199+
// truncate summary tag that inside details tag instead of details tag
200+
if (node.is('details')) {
201+
return node.find('summary')
202+
}
203+
}
204+
205+
const html = '<div><img src="abc.png"><i>italic<b>bold</b></i><details><summary>Click me</summary><p>Some details</p></details>This is a string</div> for test.'
206+
207+
const options: IOptions = {
208+
length: 10,
209+
customNodeStrategy
210+
}
211+
212+
truncate(html, options)
213+
// => <div><i>italic<b>bold</b></i><details><summary>Click me</summary><p>Some details</p></details>Th...</div>
214+
215+
216+
```
217+
218+
219+
123220
### About final string length
124221

125222
If the html string content's length is shorter than `options.length`, then no ellipsis will be appended to the final html string. If longer, then the final string length will be `options.length` + `options.ellipsis`. And if you set `reserveLastWord` to true or none zero number, the final string will be various.
@@ -240,6 +337,34 @@ truncate(html, {
240337
})
241338
// returns: <p> test for &lt;p&gt; &#x4E2D;&#x6587; str...</p>
242339
// to fix this, see below for instructions
340+
341+
342+
// custom node strategy to keep some special elements
343+
var html = '<p><img src="abc.png"><i>italic<b>bold</b></i>This is a string</p> for test.'
344+
truncate(html, {
345+
length: 10,
346+
customNodeStrategy: node => {
347+
if (node.is('img')) {
348+
return 'remove'
349+
}
350+
if (node.is('i')) {
351+
return 'keep'
352+
}
353+
}
354+
})
355+
// returns: <p><i>italic<b>bold</b></i>This is a ...</p>
356+
357+
// custom node strategy to truncate summary instead of original node
358+
var html = '<div><details><summary>Click me</summary><p>Some details</p></details>other things</div>'
359+
truncate(html, {
360+
length: 10,
361+
customNodeStrategy: node => {
362+
if (node.is('details')) {
363+
return node.find('summary')
364+
}
365+
}
366+
})
367+
// returns: <div><details><summary>Click me</summary><p>Some details</p></details>ot...</div>
243368
```
244369
245370
for More usages, check [truncate.spec.ts](./test/truncate.spec.ts)

scripts/post-build.js

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)