@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 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(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/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(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/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.5.0",
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/crypto.spec.ts' --testTimeout=10000"
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.0.3",
35
+ "@types/node": "^24.10.13",
36
36
  "tsup": "^8.5.0",
37
37
  "tsx": "^4.20.4",
38
38
  "typescript": "^5.8.3"
@@ -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(/&amp;/g, '&')
115
115
  .replace(/&quot;/g, '"')
@@ -117,3 +117,22 @@ export function htmlEntityDecode(str = ''): string {
117
117
  .replace(/&lt;/g, '<')
118
118
  .replace(/&gt;/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 &amp; Jerry &lt;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
@@ -11,7 +11,8 @@
11
11
  "strict": true,
12
12
  "skipLibCheck": true,
13
13
  "resolveJsonModule": true,
14
- "isolatedModules": true
14
+ "isolatedModules": true,
15
+ "types": ["node"]
15
16
  },
16
17
  "include": ["src", "tests"],
17
18
  "exclude": ["dist", "node_modules", "**/*.test.ts"]