From 089853e05b21bd538187e625752e83ca3095f3b6 Mon Sep 17 00:00:00 2001 From: Daniel Eduardo Almagro Date: Fri, 10 May 2024 21:15:40 -0500 Subject: [PATCH 1/4] feat(new tool): Text extractor form HTML Fix https://github.com/CorentinTh/it-tools/issues/1035 --- components.d.ts | 10 +++--- .../extract-text-from-html.e2e.spec.ts | 17 +++++++++ .../extract-text-from-html.service.test.ts | 36 +++++++++++++++++++ .../extract-text-from-html.service.ts | 21 +++++++++++ .../extract-text-from-html.vue | 33 +++++++++++++++++ src/tools/extract-text-from-html/index.ts | 13 +++++++ src/tools/index.ts | 2 ++ 7 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts create mode 100644 src/tools/extract-text-from-html/extract-text-from-html.service.test.ts create mode 100644 src/tools/extract-text-from-html/extract-text-from-html.service.ts create mode 100644 src/tools/extract-text-from-html/extract-text-from-html.vue create mode 100644 src/tools/extract-text-from-html/index.ts diff --git a/components.d.ts b/components.d.ts index f2c3146f..b358b7b3 100644 --- a/components.d.ts +++ b/components.d.ts @@ -77,6 +77,7 @@ declare module '@vue/runtime-core' { EmojiPicker: typeof import('./src/tools/emoji-picker/emoji-picker.vue')['default'] Encryption: typeof import('./src/tools/encryption/encryption.vue')['default'] EtaCalculator: typeof import('./src/tools/eta-calculator/eta-calculator.vue')['default'] + ExtractTextFromHtml: typeof import('./src/tools/extract-text-from-html/extract-text-from-html.vue')['default'] FavoriteButton: typeof import('./src/components/FavoriteButton.vue')['default'] FormatTransformer: typeof import('./src/components/FormatTransformer.vue')['default'] GitMemo: typeof import('./src/tools/git-memo/git-memo.vue')['default'] @@ -126,25 +127,26 @@ declare module '@vue/runtime-core' { MenuLayout: typeof import('./src/components/MenuLayout.vue')['default'] MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] + NAlert: typeof import('naive-ui')['NAlert'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] + NColorPicker: typeof import('naive-ui')['NColorPicker'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NDivider: typeof import('naive-ui')['NDivider'] NEllipsis: typeof import('naive-ui')['NEllipsis'] NFormItem: typeof import('naive-ui')['NFormItem'] - NGi: typeof import('naive-ui')['NGi'] - NGrid: typeof import('naive-ui')['NGrid'] NH1: typeof import('naive-ui')['NH1'] NH3: typeof import('naive-ui')['NH3'] NIcon: typeof import('naive-ui')['NIcon'] + NInputGroup: typeof import('naive-ui')['NInputGroup'] + NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel'] NInputNumber: typeof import('naive-ui')['NInputNumber'] - NLabel: typeof import('naive-ui')['NLabel'] NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] NScrollbar: typeof import('naive-ui')['NScrollbar'] - NSpin: typeof import('naive-ui')['NSpin'] + NSwitch: typeof import('naive-ui')['NSwitch'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] diff --git a/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts b/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts new file mode 100644 index 00000000..c48029ab --- /dev/null +++ b/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts @@ -0,0 +1,17 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Tool - Extract text from html', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/extract-text-from-html'); + }); + + test('Has correct title', async ({ page }) => { + await expect(page).toHaveTitle('Extract text from HTML'); + }); + + test('Extract text from HTML', async ({ page }) => { + await page.getByTestId('input').fill('

Paste your HTML in the input form on the left

'); + const extractedText = await page.getByTestId('area-content').innerText(); + expect(extractedText.trim()).toEqual('Paste your HTML in the input form on the left'.trim()); + }); +}); diff --git a/src/tools/extract-text-from-html/extract-text-from-html.service.test.ts b/src/tools/extract-text-from-html/extract-text-from-html.service.test.ts new file mode 100644 index 00000000..2648a2cb --- /dev/null +++ b/src/tools/extract-text-from-html/extract-text-from-html.service.test.ts @@ -0,0 +1,36 @@ +import { expect, describe, it } from 'vitest'; +import { getTextFromHtml, validateHtml } from './extract-text-from-html.service'; + +describe('extract-text-from-html service', () => { + describe('validateHtml', () => { + it('check if the value is valid html', () => { + expect(validateHtml('

Paste your HTML in the input form on the left

')).toBeTruthy(); + expect(validateHtml('
Paste your HTML in the input form on the left
')).toBeTruthy(); + expect(validateHtml('

Paste your HTML in the input form on the left

')).toBeTruthy(); + expect(validateHtml('

Paste your HTML in the input form on the left

')).toBeTruthy(); + expect(validateHtml('

Paste your HTML in the input form on the left

')).toBeTruthy(); + }); + + it('check if the value is an html invlid', () => { + expect(validateHtml('

Paste your HTML in the input form on the left

')).toBeFalsy(); + expect(validateHtml('Paste your HTML in the input form on the left

')).toBeFalsy(); + expect(validateHtml('

Paste your HTML in the input form on the left')).toBeFalsy(); + expect(validateHtml('

Paste your HTML in the input form on the left<>')).toBeFalsy(); + expect(validateHtml('<>Paste your HTML in the input form on the left<>')).toBeFalsy(); + expect(validateHtml('

Paste your HTML in the input form on the left')).toBeFalsy(); + expect(validateHtml('

Paste your HTML in the input form on the left

')).toBeTruthy(); + }); + }); + + describe('getTextFromHtml', () => { + it('must be return a string', () => { + expect(getTextFromHtml('

Paste your HTML in the input form on the left

')).toString(); + }); + + it('must be return text from html', () => { + expect(getTextFromHtml('

Paste your HTML in the input form on the left

')).toStrictEqual( + 'Paste your HTML in the input form on the left', + ); + }); + }); +}); diff --git a/src/tools/extract-text-from-html/extract-text-from-html.service.ts b/src/tools/extract-text-from-html/extract-text-from-html.service.ts new file mode 100644 index 00000000..66705a62 --- /dev/null +++ b/src/tools/extract-text-from-html/extract-text-from-html.service.ts @@ -0,0 +1,21 @@ +function validateHtml(value: string) { + try { + new DOMParser().parseFromString(value, 'text/html'); + } catch (error) { + return false; + } + + const regex = /<([a-z][a-z0-9]*)\b[^>]*>(.*?)<\/\1>|<([a-z][a-z0-9]*)\b[^\/]*\/>/gi; + const matches = value.match(regex); + + return Boolean(matches !== null && matches.length); +} + +function getTextFromHtml(value: string) { + const element = document.createElement('div'); + element.innerHTML = value; + const text = element?.innerText || element?.textContent || ''; + return text.replace(/\s+/g, ' '); +} + +export { validateHtml, getTextFromHtml }; diff --git a/src/tools/extract-text-from-html/extract-text-from-html.vue b/src/tools/extract-text-from-html/extract-text-from-html.vue new file mode 100644 index 00000000..471226fb --- /dev/null +++ b/src/tools/extract-text-from-html/extract-text-from-html.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/src/tools/extract-text-from-html/index.ts b/src/tools/extract-text-from-html/index.ts new file mode 100644 index 00000000..b724e809 --- /dev/null +++ b/src/tools/extract-text-from-html/index.ts @@ -0,0 +1,13 @@ +import { CursorText } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Extract text from HTML', + path: '/extract-text-from-html', + description: + 'Paste your HTML in the input form on the left and you will get text instantly. Occasionally, you may need to extract plain text from an HTML page where CSS properties (like user-select: none;) prevent text selection. The typical workaround involves using the DevTools (F12) to select "Copy → outer HTML". The proposed tool would simplify this process by extracting the "inner Text" directly from the copied HTML.', + keywords: ['extract', 'text', 'from', 'html'], + component: () => import('./extract-text-from-html.vue'), + icon: CursorText, + createdAt: new Date('2024-05-10'), +}); diff --git a/src/tools/index.ts b/src/tools/index.ts index aa861c93..db6d3b47 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,6 +1,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; +import { tool as extractTextFromHtml } from './extract-text-from-html'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -148,6 +149,7 @@ export const toolsByCategory: ToolCategory[] = [ dockerRunToDockerComposeConverter, xmlFormatter, yamlViewer, + extractTextFromHtml, ], }, { From e672cdae634917ea90c7ac78054f35b25cea7b22 Mon Sep 17 00:00:00 2001 From: Daniel Eduardo Almagro Date: Sun, 12 May 2024 23:52:41 -0500 Subject: [PATCH 2/4] style(eslint): fixing eslint errors and warnings --- components.d.ts | 25 +++++++++++++++++++ .../extract-text-from-html.e2e.spec.ts | 2 +- .../extract-text-from-html.service.test.ts | 2 +- .../extract-text-from-html.service.ts | 3 ++- .../extract-text-from-html.vue | 2 -- 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/components.d.ts b/components.d.ts index b358b7b3..ff12e40a 100644 --- a/components.d.ts +++ b/components.d.ts @@ -90,17 +90,28 @@ declare module '@vue/runtime-core' { HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default'] IbanValidatorAndParser: typeof import('./src/tools/iban-validator-and-parser/iban-validator-and-parser.vue')['default'] 'IconMdi:brushVariant': typeof import('~icons/mdi/brush-variant')['default'] + 'IconMdi:contentCopy': typeof import('~icons/mdi/content-copy')['default'] 'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default'] + IconMdiArrowDown: typeof import('~icons/mdi/arrow-down')['default'] + IconMdiArrowRightBottom: typeof import('~icons/mdi/arrow-right-bottom')['default'] + IconMdiCamera: typeof import('~icons/mdi/camera')['default'] IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default'] IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default'] IconMdiClose: typeof import('~icons/mdi/close')['default'] IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['default'] + IconMdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default'] + IconMdiDownload: typeof import('~icons/mdi/download')['default'] IconMdiEye: typeof import('~icons/mdi/eye')['default'] IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default'] IconMdiHeart: typeof import('~icons/mdi/heart')['default'] + IconMdiPause: typeof import('~icons/mdi/pause')['default'] + IconMdiPlay: typeof import('~icons/mdi/play')['default'] + IconMdiRecord: typeof import('~icons/mdi/record')['default'] + IconMdiRefresh: typeof import('~icons/mdi/refresh')['default'] IconMdiSearch: typeof import('~icons/mdi/search')['default'] IconMdiTranslate: typeof import('~icons/mdi/translate')['default'] IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default'] + IconMdiVideo: typeof import('~icons/mdi/video')['default'] InputCopyable: typeof import('./src/components/InputCopyable.vue')['default'] IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default'] Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default'] @@ -129,24 +140,38 @@ declare module '@vue/runtime-core' { MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] NAlert: typeof import('naive-ui')['NAlert'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] + NCheckbox: typeof import('naive-ui')['NCheckbox'] NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NColorPicker: typeof import('naive-ui')['NColorPicker'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] + NDatePicker: typeof import('naive-ui')['NDatePicker'] NDivider: typeof import('naive-ui')['NDivider'] + NDynamicInput: typeof import('naive-ui')['NDynamicInput'] NEllipsis: typeof import('naive-ui')['NEllipsis'] + NForm: typeof import('naive-ui')['NForm'] NFormItem: typeof import('naive-ui')['NFormItem'] + NGi: typeof import('naive-ui')['NGi'] + NGrid: typeof import('naive-ui')['NGrid'] NH1: typeof import('naive-ui')['NH1'] + NH2: typeof import('naive-ui')['NH2'] NH3: typeof import('naive-ui')['NH3'] NIcon: typeof import('naive-ui')['NIcon'] + NImage: typeof import('naive-ui')['NImage'] NInputGroup: typeof import('naive-ui')['NInputGroup'] NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel'] NInputNumber: typeof import('naive-ui')['NInputNumber'] NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] + NProgress: typeof import('naive-ui')['NProgress'] NScrollbar: typeof import('naive-ui')['NScrollbar'] + NSlider: typeof import('naive-ui')['NSlider'] + NSpin: typeof import('naive-ui')['NSpin'] + NStatistic: typeof import('naive-ui')['NStatistic'] NSwitch: typeof import('naive-ui')['NSwitch'] + NTable: typeof import('naive-ui')['NTable'] + NTag: typeof import('naive-ui')['NTag'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] diff --git a/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts b/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts index c48029ab..3a58b9b2 100644 --- a/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts +++ b/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; test.describe('Tool - Extract text from html', () => { test.beforeEach(async ({ page }) => { diff --git a/src/tools/extract-text-from-html/extract-text-from-html.service.test.ts b/src/tools/extract-text-from-html/extract-text-from-html.service.test.ts index 2648a2cb..841730be 100644 --- a/src/tools/extract-text-from-html/extract-text-from-html.service.test.ts +++ b/src/tools/extract-text-from-html/extract-text-from-html.service.test.ts @@ -1,4 +1,4 @@ -import { expect, describe, it } from 'vitest'; +import { describe, expect, it } from 'vitest'; import { getTextFromHtml, validateHtml } from './extract-text-from-html.service'; describe('extract-text-from-html service', () => { diff --git a/src/tools/extract-text-from-html/extract-text-from-html.service.ts b/src/tools/extract-text-from-html/extract-text-from-html.service.ts index 66705a62..4061c091 100644 --- a/src/tools/extract-text-from-html/extract-text-from-html.service.ts +++ b/src/tools/extract-text-from-html/extract-text-from-html.service.ts @@ -1,7 +1,8 @@ function validateHtml(value: string) { try { new DOMParser().parseFromString(value, 'text/html'); - } catch (error) { + } + catch (error) { return false; } diff --git a/src/tools/extract-text-from-html/extract-text-from-html.vue b/src/tools/extract-text-from-html/extract-text-from-html.vue index 471226fb..44a1a222 100644 --- a/src/tools/extract-text-from-html/extract-text-from-html.vue +++ b/src/tools/extract-text-from-html/extract-text-from-html.vue @@ -29,5 +29,3 @@ const rules: UseValidationRule[] = [ :transformer="transformer" /> - - From b2a4b9679d7472985c08699ee465c6d3096fb480 Mon Sep 17 00:00:00 2001 From: Daniel Eduardo Almagro Date: Mon, 13 May 2024 00:13:15 -0500 Subject: [PATCH 3/4] test(eslint): fixing correct title of tool text from html --- .../extract-text-from-html/extract-text-from-html.e2e.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts b/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts index 3a58b9b2..f2fb1668 100644 --- a/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts +++ b/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts @@ -6,7 +6,7 @@ test.describe('Tool - Extract text from html', () => { }); test('Has correct title', async ({ page }) => { - await expect(page).toHaveTitle('Extract text from HTML'); + await expect(page).toHaveTitle('Extract text from HTML - IT Tools'); }); test('Extract text from HTML', async ({ page }) => { From 0f44e70cd4d2ab724739462aef4799bb9184e6c4 Mon Sep 17 00:00:00 2001 From: sunnydanu Date: Mon, 4 Nov 2024 16:28:11 +0530 Subject: [PATCH 4/4] fix(extract-text-from-html): test case fixed --- .../extract-text-from-html/extract-text-from-html.e2e.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts b/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts index f2fb1668..6d79e678 100644 --- a/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts +++ b/src/tools/extract-text-from-html/extract-text-from-html.e2e.spec.ts @@ -6,7 +6,7 @@ test.describe('Tool - Extract text from html', () => { }); test('Has correct title', async ({ page }) => { - await expect(page).toHaveTitle('Extract text from HTML - IT Tools'); + await expect(page).toHaveTitle('GoDev.Run - Extract text from HTML'); }); test('Extract text from HTML', async ({ page }) => {