This commit is contained in:
sharevb 2025-04-13 04:13:33 +02:00 committed by GitHub
commit 5422e46f00
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 6484 additions and 8273 deletions

6
components.d.ts vendored
View file

@ -85,6 +85,7 @@ declare module '@vue/runtime-core' {
HashText: typeof import('./src/tools/hash-text/hash-text.vue')['default'] HashText: typeof import('./src/tools/hash-text/hash-text.vue')['default']
HmacGenerator: typeof import('./src/tools/hmac-generator/hmac-generator.vue')['default'] HmacGenerator: typeof import('./src/tools/hmac-generator/hmac-generator.vue')['default']
'Home.page': typeof import('./src/pages/Home.page.vue')['default'] 'Home.page': typeof import('./src/pages/Home.page.vue')['default']
HtmlCleaner: typeof import('./src/tools/html-cleaner/html-cleaner.vue')['default']
HtmlEntities: typeof import('./src/tools/html-entities/html-entities.vue')['default'] HtmlEntities: typeof import('./src/tools/html-entities/html-entities.vue')['default']
HtmlWysiwygEditor: typeof import('./src/tools/html-wysiwyg-editor/html-wysiwyg-editor.vue')['default'] HtmlWysiwygEditor: typeof import('./src/tools/html-wysiwyg-editor/html-wysiwyg-editor.vue')['default']
HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default'] HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default']
@ -135,14 +136,14 @@ declare module '@vue/runtime-core' {
NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDivider: typeof import('naive-ui')['NDivider'] NDivider: typeof import('naive-ui')['NDivider']
NEllipsis: typeof import('naive-ui')['NEllipsis'] NEllipsis: typeof import('naive-ui')['NEllipsis']
NFormItem: typeof import('naive-ui')['NFormItem']
NH1: typeof import('naive-ui')['NH1'] NH1: typeof import('naive-ui')['NH1']
NH3: typeof import('naive-ui')['NH3'] NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon'] NIcon: typeof import('naive-ui')['NIcon']
NLayout: typeof import('naive-ui')['NLayout'] NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu'] NMenu: typeof import('naive-ui')['NMenu']
NSpace: typeof import('naive-ui')['NSpace'] NScrollbar: typeof import('naive-ui')['NScrollbar']
NTable: typeof import('naive-ui')['NTable']
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] 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'] 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'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
@ -161,6 +162,7 @@ declare module '@vue/runtime-core' {
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default'] RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default']
SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default'] SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default']
SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default']
SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default'] SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default'] SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default']
SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default'] SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default']

View file

@ -46,6 +46,7 @@
"@tiptap/starter-kit": "2.1.6", "@tiptap/starter-kit": "2.1.6",
"@tiptap/vue-3": "2.0.3", "@tiptap/vue-3": "2.0.3",
"@types/figlet": "^1.5.8", "@types/figlet": "^1.5.8",
"@types/js-beautify": "^1.14.3",
"@types/markdown-it": "^13.0.7", "@types/markdown-it": "^13.0.7",
"@vicons/material": "^0.12.0", "@vicons/material": "^0.12.0",
"@vicons/tabler": "^0.12.0", "@vicons/tabler": "^0.12.0",
@ -70,6 +71,7 @@
"highlight.js": "^11.7.0", "highlight.js": "^11.7.0",
"iarna-toml-esm": "^3.0.5", "iarna-toml-esm": "^3.0.5",
"ibantools": "^4.3.3", "ibantools": "^4.3.3",
"js-beautify": "^1.15.1",
"js-base64": "^3.7.6", "js-base64": "^3.7.6",
"json5": "^2.2.3", "json5": "^2.2.3",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",

14647
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -128,7 +128,7 @@ function activateOption(option: PaletteOption) {
<c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text placeholder="Type to search a tool or a command..." autofocus clearable /> <c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text placeholder="Type to search a tool or a command..." autofocus clearable />
<div v-for="(options, category) in filteredSearchResult" :key="category"> <div v-for="(options, category) in filteredSearchResult" :key="category">
<div ml-3 mt-3 text-sm font-bold text-primary op-60> <div ml-3 mt-3 text-sm text-primary font-bold op-60>
{{ category }} {{ category }}
</div> </div>
<command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" /> <command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" />

View file

@ -0,0 +1,54 @@
<script setup lang="ts">
import DOMPurify from 'dompurify';
import beautify from 'js-beautify';
import TextareaCopyable from '@/components/TextareaCopyable.vue';
const inputHtml = ref('');
const outputHtml = computed(() => {
const cleanedHtml = DOMPurify.sanitize(inputHtml.value ?? '',
{
ALLOWED_ATTR: [
'href', 'src',
'width', 'height',
'alt',
'colspan', 'rowspan',
],
FORBID_TAGS: ['form', 'span'],
ALLOW_DATA_ATTR: false,
ALLOW_ARIA_ATTR: false,
RETURN_DOM: true,
}).outerHTML;
return beautify.html(cleanedHtml, {
unformatted: ['code', 'pre', 'em', 'strong', 'span'],
indent_inner_html: true,
indent_char: ' ',
indent_size: 2,
eol: '\n',
});
});
</script>
<template>
<div>
<c-input-text
v-model:value="inputHtml"
multiline raw-text
placeholder="Your HTML content..."
rows="8"
autofocus
label="Your HTML to clean (can paste from clipboard):"
paste-html
/>
<n-divider />
<n-form-item label="Output cleaned HTML:">
<TextareaCopyable
:value="outputHtml"
multiline
language="html"
:word-wrap="true"
/>
</n-form-item>
</div>
</template>

View file

@ -0,0 +1,12 @@
import { BrandHtml5 } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'Html cleaner',
path: '/html-cleaner',
description: 'Clean HTML',
keywords: ['html', 'cleaner'],
component: () => import('./html-cleaner.vue'),
icon: BrandHtml5,
createdAt: new Date('2024-02-25'),
});

View file

@ -7,6 +7,7 @@ import { tool as asciiTextDrawer } from './ascii-text-drawer';
import { tool as textToUnicode } from './text-to-unicode'; import { tool as textToUnicode } from './text-to-unicode';
import { tool as safelinkDecoder } from './safelink-decoder'; import { tool as safelinkDecoder } from './safelink-decoder';
import { tool as htmlCleaner } from './html-cleaner';
import { tool as xmlToJson } from './xml-to-json'; import { tool as xmlToJson } from './xml-to-json';
import { tool as jsonToXml } from './json-to-xml'; import { tool as jsonToXml } from './json-to-xml';
import { tool as regexTester } from './regex-tester'; import { tool as regexTester } from './regex-tester';
@ -113,6 +114,7 @@ export const toolsByCategory: ToolCategory[] = [
listConverter, listConverter,
tomlToJson, tomlToJson,
tomlToYaml, tomlToYaml,
htmlCleaner,
xmlToJson, xmlToJson,
jsonToXml, jsonToXml,
markdownToHtml, markdownToHtml,

View file

@ -31,6 +31,7 @@ const props = withDefaults(
autosize?: boolean autosize?: boolean
autofocus?: boolean autofocus?: boolean
monospace?: boolean monospace?: boolean
pasteHtml?: boolean
}>(), }>(),
{ {
value: '', value: '',
@ -58,13 +59,14 @@ const props = withDefaults(
autosize: false, autosize: false,
autofocus: false, autofocus: false,
monospace: false, monospace: false,
pasteHtml: false,
}, },
); );
const emit = defineEmits(['update:value']); const emit = defineEmits(['update:value']);
const value = useVModel(props, 'value', emit); const value = useVModel(props, 'value', emit);
const showPassword = ref(false); const showPassword = ref(false);
const { id, placeholder, label, validationRules, labelPosition, labelWidth, labelAlign, autosize, readonly, disabled, clearable, type, multiline, rows, rawText, autofocus, monospace } = toRefs(props); const { id, placeholder, label, validationRules, labelPosition, labelWidth, labelAlign, autosize, readonly, disabled, clearable, type, multiline, rows, rawText, autofocus, monospace, pasteHtml } = toRefs(props);
const validation const validation
= props.validation = props.validation
@ -81,6 +83,28 @@ const textareaRef = ref<HTMLTextAreaElement>();
const inputRef = ref<HTMLInputElement>(); const inputRef = ref<HTMLInputElement>();
const inputWrapperRef = ref<HTMLElement>(); const inputWrapperRef = ref<HTMLElement>();
interface HTMLElementWithValue {
value?: string
}
function onPasteInputHtml(evt: ClipboardEvent) {
if (!pasteHtml.value) {
return false;
}
const target = (evt.target as HTMLElementWithValue);
if (!target) {
return false;
}
const textHtmlData = evt.clipboardData?.getData('text/html');
if (textHtmlData && textHtmlData !== '') {
evt.preventDefault();
value.value = textHtmlData;
return true;
}
return false;
}
watch( watch(
[value, autosize, multiline, inputWrapperRef, textareaRef], [value, autosize, multiline, inputWrapperRef, textareaRef],
() => nextTick(() => { () => nextTick(() => {
@ -171,6 +195,7 @@ defineExpose({
:autocorrect="autocorrect ?? (rawText ? 'off' : undefined)" :autocorrect="autocorrect ?? (rawText ? 'off' : undefined)"
:spellcheck="spellcheck ?? (rawText ? false : undefined)" :spellcheck="spellcheck ?? (rawText ? false : undefined)"
:rows="rows" :rows="rows"
@paste="onPasteInputHtml"
/> />
<input <input
@ -192,6 +217,7 @@ defineExpose({
:autocomplete="autocomplete ?? (rawText ? 'off' : undefined)" :autocomplete="autocomplete ?? (rawText ? 'off' : undefined)"
:autocorrect="autocorrect ?? (rawText ? 'off' : undefined)" :autocorrect="autocorrect ?? (rawText ? 'off' : undefined)"
:spellcheck="spellcheck ?? (rawText ? false : undefined)" :spellcheck="spellcheck ?? (rawText ? false : undefined)"
@paste="onPasteInputHtml"
> >
<c-button v-if="clearable && value" variant="text" circle size="small" @click="value = ''"> <c-button v-if="clearable && value" variant="text" circle size="small" @click="value = ''">

View file

@ -151,7 +151,7 @@ function onSearchInput() {
> >
<div flex-1 truncate> <div flex-1 truncate>
<slot name="displayed-value"> <slot name="displayed-value">
<input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full lh-normal color-current @input="onSearchInput"> <input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full color-current lh-normal @input="onSearchInput">
<span v-else-if="selectedOption" lh-normal> <span v-else-if="selectedOption" lh-normal>
{{ selectedOption.label }} {{ selectedOption.label }}
</span> </span>

View file

@ -39,7 +39,7 @@ const headers = computed(() => {
<template> <template>
<div class="relative overflow-x-auto rounded"> <div class="relative overflow-x-auto rounded">
<table class="w-full border-collapse text-left text-sm text-gray-500 dark:text-gray-400" role="table" :aria-label="description"> <table class="w-full border-collapse text-left text-sm text-gray-500 dark:text-gray-400" role="table" :aria-label="description">
<thead v-if="!hideHeaders" class="bg-#ffffff uppercase text-gray-700 dark:bg-#333333 dark:text-gray-400" border-b="1px solid dark:transparent #efeff5"> <thead v-if="!hideHeaders" class="bg-#ffffff text-gray-700 uppercase dark:bg-#333333 dark:text-gray-400" border-b="1px solid dark:transparent #efeff5">
<tr> <tr>
<th v-for="header in headers" :key="header.key" scope="col" class="px-6 py-3 text-xs"> <th v-for="header in headers" :key="header.key" scope="col" class="px-6 py-3 text-xs">
{{ header.label }} {{ header.label }}