@excofy/utils 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +10 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +9 -0
- package/package.json +3 -3
- package/src/helpers/sanitize.ts +21 -2
- package/src/index.ts +2 -1
- package/tests/helpers/htmlToPlainText.spec.ts +33 -0
- package/tests/helpers/validator.spec.ts +75 -0
- package/tsconfig.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
cryptoUtils: () => cryptoUtils,
|
|
37
37
|
generateCode: () => generateCode,
|
|
38
38
|
htmlEntityDecode: () => htmlEntityDecode,
|
|
39
|
+
htmlToPlainText: () => htmlToPlainText,
|
|
39
40
|
numberUtils: () => number_exports,
|
|
40
41
|
slugUtils: () => slug_exports,
|
|
41
42
|
stringUtils: () => stringUtils
|
|
@@ -163,6 +164,14 @@ function htmlEntityDecode(str = "") {
|
|
|
163
164
|
(_, code) => String.fromCharCode(Number.parseInt(code, 16))
|
|
164
165
|
).replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
165
166
|
}
|
|
167
|
+
function htmlToPlainText(html) {
|
|
168
|
+
if (!html || typeof html !== "string" || html.length === 0) {
|
|
169
|
+
return "";
|
|
170
|
+
}
|
|
171
|
+
const decodedHtml = htmlEntityDecode(html);
|
|
172
|
+
const plainText = decodedHtml.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<\/li>/gi, "\n").replace(/<li>/gi, "\u2022 ").replace(/<\/?[^>]+(>|$)/g, "").replace(/\n{2,}/g, "\n").trim();
|
|
173
|
+
return plainText;
|
|
174
|
+
}
|
|
166
175
|
|
|
167
176
|
// src/helpers/video.ts
|
|
168
177
|
var video = {
|
|
@@ -973,6 +982,7 @@ var divide = (numerator, denominator) => {
|
|
|
973
982
|
cryptoUtils,
|
|
974
983
|
generateCode,
|
|
975
984
|
htmlEntityDecode,
|
|
985
|
+
htmlToPlainText,
|
|
976
986
|
numberUtils,
|
|
977
987
|
slugUtils,
|
|
978
988
|
stringUtils
|
package/dist/index.d.cts
CHANGED
|
@@ -7,6 +7,7 @@ type AllowedTag = Tag | {
|
|
|
7
7
|
attributes?: Attributes[];
|
|
8
8
|
};
|
|
9
9
|
declare function htmlEntityDecode(str?: string): string;
|
|
10
|
+
declare function htmlToPlainText(html: string): string;
|
|
10
11
|
|
|
11
12
|
type TMessage = {
|
|
12
13
|
message: string;
|
|
@@ -241,4 +242,4 @@ declare class ExpiredTokenError extends CryptoError {
|
|
|
241
242
|
constructor(message?: string);
|
|
242
243
|
}
|
|
243
244
|
|
|
244
|
-
export { ExpiredTokenError, InvalidTokenError, createValidator, cryptoUtils, generateCode, htmlEntityDecode, number as numberUtils, slug as slugUtils, stringUtils };
|
|
245
|
+
export { ExpiredTokenError, InvalidTokenError, createValidator, cryptoUtils, generateCode, htmlEntityDecode, htmlToPlainText, number as numberUtils, slug as slugUtils, stringUtils };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ type AllowedTag = Tag | {
|
|
|
7
7
|
attributes?: Attributes[];
|
|
8
8
|
};
|
|
9
9
|
declare function htmlEntityDecode(str?: string): string;
|
|
10
|
+
declare function htmlToPlainText(html: string): string;
|
|
10
11
|
|
|
11
12
|
type TMessage = {
|
|
12
13
|
message: string;
|
|
@@ -241,4 +242,4 @@ declare class ExpiredTokenError extends CryptoError {
|
|
|
241
242
|
constructor(message?: string);
|
|
242
243
|
}
|
|
243
244
|
|
|
244
|
-
export { ExpiredTokenError, InvalidTokenError, createValidator, cryptoUtils, generateCode, htmlEntityDecode, number as numberUtils, slug as slugUtils, stringUtils };
|
|
245
|
+
export { ExpiredTokenError, InvalidTokenError, createValidator, cryptoUtils, generateCode, htmlEntityDecode, htmlToPlainText, number as numberUtils, slug as slugUtils, stringUtils };
|
package/dist/index.js
CHANGED
|
@@ -125,6 +125,14 @@ function htmlEntityDecode(str = "") {
|
|
|
125
125
|
(_, code) => String.fromCharCode(Number.parseInt(code, 16))
|
|
126
126
|
).replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
127
127
|
}
|
|
128
|
+
function htmlToPlainText(html) {
|
|
129
|
+
if (!html || typeof html !== "string" || html.length === 0) {
|
|
130
|
+
return "";
|
|
131
|
+
}
|
|
132
|
+
const decodedHtml = htmlEntityDecode(html);
|
|
133
|
+
const plainText = decodedHtml.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<\/li>/gi, "\n").replace(/<li>/gi, "\u2022 ").replace(/<\/?[^>]+(>|$)/g, "").replace(/\n{2,}/g, "\n").trim();
|
|
134
|
+
return plainText;
|
|
135
|
+
}
|
|
128
136
|
|
|
129
137
|
// src/helpers/video.ts
|
|
130
138
|
var video = {
|
|
@@ -934,6 +942,7 @@ export {
|
|
|
934
942
|
cryptoUtils,
|
|
935
943
|
generateCode,
|
|
936
944
|
htmlEntityDecode,
|
|
945
|
+
htmlToPlainText,
|
|
937
946
|
number_exports as numberUtils,
|
|
938
947
|
slug_exports as slugUtils,
|
|
939
948
|
stringUtils
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@excofy/utils",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Biblioteca de utilitários para o Excofy",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"typecheck": "tsc --noEmit",
|
|
17
17
|
"prepublishOnly": "npm run build",
|
|
18
18
|
"deploy": "act",
|
|
19
|
-
"test": "tsx --test 'tests/helpers/
|
|
19
|
+
"test": "tsx --test 'tests/helpers/htmlToPlainText.spec.ts' --testTimeout=10000"
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@biomejs/biome": "^1.9.4",
|
|
34
34
|
"@types/big.js": "^6.2.2",
|
|
35
|
-
"@types/node": "^24.
|
|
35
|
+
"@types/node": "^24.10.13",
|
|
36
36
|
"tsup": "^8.5.0",
|
|
37
37
|
"tsx": "^4.20.4",
|
|
38
38
|
"typescript": "^5.8.3"
|
package/src/helpers/sanitize.ts
CHANGED
|
@@ -69,7 +69,7 @@ const allAttributes: Attributes[] = [
|
|
|
69
69
|
|
|
70
70
|
export function sanitizeValue(
|
|
71
71
|
value: string,
|
|
72
|
-
allowedTags?: AllowedTag[]
|
|
72
|
+
allowedTags?: AllowedTag[],
|
|
73
73
|
): string {
|
|
74
74
|
let whiteList: IWhiteList = {};
|
|
75
75
|
|
|
@@ -109,7 +109,7 @@ export function htmlEntityDecode(str = ''): string {
|
|
|
109
109
|
return str
|
|
110
110
|
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code))
|
|
111
111
|
.replace(/&#x([0-9a-f]+);/gi, (_, code) =>
|
|
112
|
-
String.fromCharCode(Number.parseInt(code, 16))
|
|
112
|
+
String.fromCharCode(Number.parseInt(code, 16)),
|
|
113
113
|
)
|
|
114
114
|
.replace(/&/g, '&')
|
|
115
115
|
.replace(/"/g, '"')
|
|
@@ -117,3 +117,22 @@ export function htmlEntityDecode(str = ''): string {
|
|
|
117
117
|
.replace(/</g, '<')
|
|
118
118
|
.replace(/>/g, '>');
|
|
119
119
|
}
|
|
120
|
+
|
|
121
|
+
export function htmlToPlainText(html: string): string {
|
|
122
|
+
if (!html || typeof html !== 'string' || html.length === 0) {
|
|
123
|
+
return '';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const decodedHtml = htmlEntityDecode(html);
|
|
127
|
+
|
|
128
|
+
const plainText = decodedHtml
|
|
129
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
130
|
+
.replace(/<\/p>/gi, '\n')
|
|
131
|
+
.replace(/<\/li>/gi, '\n')
|
|
132
|
+
.replace(/<li>/gi, '• ')
|
|
133
|
+
.replace(/<\/?[^>]+(>|$)/g, '') // Remove outras tags
|
|
134
|
+
.replace(/\n{2,}/g, '\n') // Remove quebras de linha duplicadas
|
|
135
|
+
.trim();
|
|
136
|
+
|
|
137
|
+
return plainText;
|
|
138
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createValidator } from './helpers/validator';
|
|
2
|
-
import { htmlEntityDecode } from './helpers/sanitize';
|
|
2
|
+
import { htmlEntityDecode, htmlToPlainText } from './helpers/sanitize';
|
|
3
3
|
import { generateCode } from './helpers/generateCode';
|
|
4
4
|
import { stringUtils } from './helpers/string';
|
|
5
5
|
import { cryptoUtils } from './helpers/crypto';
|
|
@@ -9,6 +9,7 @@ import * as numberUtils from './helpers/number';
|
|
|
9
9
|
export {
|
|
10
10
|
createValidator,
|
|
11
11
|
htmlEntityDecode,
|
|
12
|
+
htmlToPlainText,
|
|
12
13
|
slugUtils,
|
|
13
14
|
numberUtils,
|
|
14
15
|
stringUtils,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { htmlToPlainText } from '../../src/helpers/sanitize';
|
|
4
|
+
|
|
5
|
+
test('Converte <br> em quebra de linha', () => {
|
|
6
|
+
const html = 'Linha 1<br>Linha 2';
|
|
7
|
+
const expected = 'Linha 1\nLinha 2';
|
|
8
|
+
assert.equal(htmlToPlainText(html), expected);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('Remove todas as tags HTML exceto marcadores de lista', () => {
|
|
12
|
+
const html = '<ul><li>Item 1</li><li>Item 2</li></ul>';
|
|
13
|
+
const expected = '• Item 1\n• Item 2';
|
|
14
|
+
assert.equal(htmlToPlainText(html), expected);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('Converte <p> em quebra de linha', () => {
|
|
18
|
+
const html = '<p>Parágrafo 1</p><p>Parágrafo 2</p>';
|
|
19
|
+
const expected = 'Parágrafo 1\nParágrafo 2';
|
|
20
|
+
assert.equal(htmlToPlainText(html), expected);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('Decodifica entidades HTML', () => {
|
|
24
|
+
const html = 'Tom & Jerry <3';
|
|
25
|
+
const expected = 'Tom & Jerry';
|
|
26
|
+
assert.equal(htmlToPlainText(html), expected);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('Remove quebras de linha duplicadas', () => {
|
|
30
|
+
const html = 'Linha 1<br><br>Linha 2';
|
|
31
|
+
const expected = 'Linha 1\nLinha 2';
|
|
32
|
+
assert.equal(htmlToPlainText(html), expected);
|
|
33
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createValidator } from '../../src/helpers/validator';
|
|
4
|
+
|
|
5
|
+
describe('validator', () => {
|
|
6
|
+
describe('asNumber', () => {
|
|
7
|
+
it('deve converter uma string "10" para o número 10', () => {
|
|
8
|
+
type InputRaw = { limit: string };
|
|
9
|
+
type InputParsed = { limit: number };
|
|
10
|
+
|
|
11
|
+
const v = createValidator<InputRaw, InputParsed>();
|
|
12
|
+
v.setInputs({ limit: '10' });
|
|
13
|
+
v.validate('limit')
|
|
14
|
+
.isRequired('Limite é obrigatório')
|
|
15
|
+
.type('string', 'Limite deve ser uma string')
|
|
16
|
+
.asNumber('Limite deve ser um número válido');
|
|
17
|
+
|
|
18
|
+
const inputs = v.getSanitizedInputs();
|
|
19
|
+
|
|
20
|
+
assert.equal(inputs.limit, 10);
|
|
21
|
+
assert.equal(typeof inputs.limit, 'number');
|
|
22
|
+
assert.equal(v.hasErrors(), false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('deve adicionar erro quando o valor não pode ser convertido para número', () => {
|
|
26
|
+
type InputRaw = { limit: string };
|
|
27
|
+
type InputParsed = { limit: number };
|
|
28
|
+
|
|
29
|
+
const v = createValidator<InputRaw, InputParsed>();
|
|
30
|
+
v.setInputs({ limit: 'abc' });
|
|
31
|
+
v.validate('limit')
|
|
32
|
+
.isRequired('Limite é obrigatório')
|
|
33
|
+
.type('string', 'Limite deve ser uma string')
|
|
34
|
+
.asNumber('Limite deve ser um número válido');
|
|
35
|
+
|
|
36
|
+
assert.equal(v.hasErrors(), true);
|
|
37
|
+
const errors = v.getFlatErrors();
|
|
38
|
+
assert.equal(errors.limit[0].message, 'Limite deve ser um número válido');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('deve converter um número diretamente', () => {
|
|
42
|
+
type InputRaw = { limit: number };
|
|
43
|
+
type InputParsed = { limit: number };
|
|
44
|
+
|
|
45
|
+
const v = createValidator<InputRaw, InputParsed>();
|
|
46
|
+
v.setInputs({ limit: 25 });
|
|
47
|
+
v.validate('limit')
|
|
48
|
+
.isRequired('Limite é obrigatório')
|
|
49
|
+
.type('number', 'Limite deve ser um número')
|
|
50
|
+
.asNumber('Limite deve ser um número válido');
|
|
51
|
+
|
|
52
|
+
const inputs = v.getSanitizedInputs();
|
|
53
|
+
|
|
54
|
+
assert.equal(inputs.limit, 25);
|
|
55
|
+
assert.equal(typeof inputs.limit, 'number');
|
|
56
|
+
assert.equal(v.hasErrors(), false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('deve lidar com valores opcionais quando não fornecidos', () => {
|
|
60
|
+
type InputRaw = { limit?: string };
|
|
61
|
+
type InputParsed = { limit?: number };
|
|
62
|
+
|
|
63
|
+
const v = createValidator<InputRaw, InputParsed>();
|
|
64
|
+
v.setInputs({});
|
|
65
|
+
v.validate('limit')
|
|
66
|
+
.isNotRequired()
|
|
67
|
+
.type('string', 'Limite deve ser uma string')
|
|
68
|
+
.asNumber('Limite deve ser um número válido');
|
|
69
|
+
|
|
70
|
+
assert.equal(v.hasErrors(), false);
|
|
71
|
+
const inputs = v.getSanitizedInputs();
|
|
72
|
+
assert.equal(inputs.limit, undefined);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
package/tsconfig.json
CHANGED