@open-mercato/shared 0.6.8-develop.7023.1.a1960eb208 → 0.6.8-develop.7027.1.c9b5e86524
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/.turbo/turbo-build.log +1 -1
- package/dist/lib/html/htmlToPlainText.js +16 -0
- package/dist/lib/html/htmlToPlainText.js.map +7 -0
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/package.json +4 -2
- package/src/lib/html/__tests__/htmlToPlainText.test.ts +59 -0
- package/src/lib/html/htmlToPlainText.ts +17 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
[build:shared] found
|
|
1
|
+
[build:shared] found 263 entry points
|
|
2
2
|
[build:shared] built successfully
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { convert } from "html-to-text";
|
|
2
|
+
function htmlToPlainText(html) {
|
|
3
|
+
if (!html) return "";
|
|
4
|
+
return convert(html, {
|
|
5
|
+
wordwrap: false,
|
|
6
|
+
preserveNewlines: true,
|
|
7
|
+
selectors: [
|
|
8
|
+
{ selector: "script", format: "skip" },
|
|
9
|
+
{ selector: "style", format: "skip" }
|
|
10
|
+
]
|
|
11
|
+
}).replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
htmlToPlainText
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=htmlToPlainText.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/html/htmlToPlainText.ts"],
|
|
4
|
+
"sourcesContent": ["import { convert } from 'html-to-text'\n\nexport function htmlToPlainText(html: string): string {\n if (!html) return ''\n return convert(html, {\n wordwrap: false,\n preserveNewlines: true,\n selectors: [\n { selector: 'script', format: 'skip' },\n { selector: 'style', format: 'skip' },\n ],\n })\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,eAAe;AAEjB,SAAS,gBAAgB,MAAsB;AACpD,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,QAAQ,MAAM;AAAA,IACnB,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,WAAW;AAAA,MACT,EAAE,UAAU,UAAU,QAAQ,OAAO;AAAA,MACrC,EAAE,UAAU,SAAS,QAAQ,OAAO;AAAA,IACtC;AAAA,EACF,CAAC,EACE,QAAQ,SAAS,IAAI,EACrB,QAAQ,OAAO,IAAI,EACnB,QAAQ,WAAW,MAAM,EACzB,KAAK;AACV;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/lib/version.js
CHANGED
package/dist/lib/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.8-develop.
|
|
4
|
+
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.8-develop.7027.1.c9b5e86524';\nexport const appVersion = APP_VERSION;\n"],
|
|
5
5
|
"mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/shared",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.7027.1.c9b5e86524",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -109,9 +109,11 @@
|
|
|
109
109
|
"@mikro-orm/core": "^7.1.8",
|
|
110
110
|
"@mikro-orm/decorators": "^7.1.8",
|
|
111
111
|
"@mikro-orm/postgresql": "^7.1.8",
|
|
112
|
-
"@open-mercato/cache": "0.6.8-develop.
|
|
112
|
+
"@open-mercato/cache": "0.6.8-develop.7027.1.c9b5e86524",
|
|
113
|
+
"@types/html-to-text": "^9.0.4",
|
|
113
114
|
"@types/sanitize-html": "^2.16.1",
|
|
114
115
|
"dotenv": "^17.4.2",
|
|
116
|
+
"html-to-text": "^10.0.0",
|
|
115
117
|
"pino": "^10.3.1",
|
|
116
118
|
"rate-limiter-flexible": "^11.2.0",
|
|
117
119
|
"re2js": "2.8.6",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { htmlToPlainText } from '../htmlToPlainText'
|
|
2
|
+
|
|
3
|
+
describe('htmlToPlainText', () => {
|
|
4
|
+
it('returns an empty string for empty input without invoking the parser', () => {
|
|
5
|
+
expect(htmlToPlainText('')).toBe('')
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
it('renders block-level markup as readable prose', () => {
|
|
9
|
+
const html = '<html><body><p>Hello,</p><p>second paragraph.</p></body></html>'
|
|
10
|
+
|
|
11
|
+
const text = htmlToPlainText(html)
|
|
12
|
+
|
|
13
|
+
expect(text).toContain('Hello,')
|
|
14
|
+
expect(text).toContain('second paragraph.')
|
|
15
|
+
expect(text).not.toContain('<p>')
|
|
16
|
+
expect(text).not.toContain('</body>')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('drops script and style content instead of inlining it as text', () => {
|
|
20
|
+
// Email HTML routinely carries a <style> block and a tracking <script>.
|
|
21
|
+
// Rendering either as text is what made ingested bodies unreadable.
|
|
22
|
+
const html = [
|
|
23
|
+
'<!DOCTYPE html><html><head>',
|
|
24
|
+
'<style>body { margin: 0; font-family: Helvetica; }</style>',
|
|
25
|
+
'<script>trackOpen("beacon")</script>',
|
|
26
|
+
'</head><body><p>Visible copy</p></body></html>',
|
|
27
|
+
].join('')
|
|
28
|
+
|
|
29
|
+
const text = htmlToPlainText(html)
|
|
30
|
+
|
|
31
|
+
expect(text).toBe('Visible copy')
|
|
32
|
+
expect(text).not.toContain('font-family')
|
|
33
|
+
expect(text).not.toContain('trackOpen')
|
|
34
|
+
expect(text).not.toContain('<!DOCTYPE')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('normalizes CRLF and lone CR line endings to LF', () => {
|
|
38
|
+
const text = htmlToPlainText('<pre>first\r\nsecond\rthird</pre>')
|
|
39
|
+
|
|
40
|
+
expect(text).not.toContain('\r')
|
|
41
|
+
expect(text).toBe('first\nsecond\nthird')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('collapses runs of three or more blank lines down to one', () => {
|
|
45
|
+
const text = htmlToPlainText('<pre>top\n\n\n\n\nbottom</pre>')
|
|
46
|
+
|
|
47
|
+
expect(text).toBe('top\n\nbottom')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('preserves a single blank line between paragraphs', () => {
|
|
51
|
+
const text = htmlToPlainText('<pre>top\n\nbottom</pre>')
|
|
52
|
+
|
|
53
|
+
expect(text).toBe('top\n\nbottom')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('trims surrounding whitespace', () => {
|
|
57
|
+
expect(htmlToPlainText('<pre>\n\n padded \n\n</pre>')).toBe('padded')
|
|
58
|
+
})
|
|
59
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { convert } from 'html-to-text'
|
|
2
|
+
|
|
3
|
+
export function htmlToPlainText(html: string): string {
|
|
4
|
+
if (!html) return ''
|
|
5
|
+
return convert(html, {
|
|
6
|
+
wordwrap: false,
|
|
7
|
+
preserveNewlines: true,
|
|
8
|
+
selectors: [
|
|
9
|
+
{ selector: 'script', format: 'skip' },
|
|
10
|
+
{ selector: 'style', format: 'skip' },
|
|
11
|
+
],
|
|
12
|
+
})
|
|
13
|
+
.replace(/\r\n/g, '\n')
|
|
14
|
+
.replace(/\r/g, '\n')
|
|
15
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
16
|
+
.trim()
|
|
17
|
+
}
|