mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-20 14:56:17 -04:00
Merge 1702fc32c8
into 07eea0f484
This commit is contained in:
commit
ec4e5c9ae0
4 changed files with 218 additions and 1 deletions
5
components.d.ts
vendored
5
components.d.ts
vendored
|
@ -90,6 +90,7 @@ declare module '@vue/runtime-core' {
|
||||||
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']
|
||||||
IbanValidatorAndParser: typeof import('./src/tools/iban-validator-and-parser/iban-validator-and-parser.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: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']
|
'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default']
|
||||||
IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
|
IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
|
||||||
IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
|
IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
|
||||||
|
@ -135,6 +136,8 @@ 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']
|
||||||
|
NFlex: typeof import('naive-ui')['NFlex']
|
||||||
|
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']
|
||||||
|
@ -142,7 +145,6 @@ declare module '@vue/runtime-core' {
|
||||||
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']
|
NSpace: typeof import('naive-ui')['NSpace']
|
||||||
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']
|
||||||
|
@ -162,6 +164,7 @@ declare module '@vue/runtime-core' {
|
||||||
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']
|
||||||
SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
|
SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
|
||||||
|
SmartTextReplacer: typeof import('./src/tools/smart-text-replacer/smart-text-replacer.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']
|
||||||
StringObfuscator: typeof import('./src/tools/string-obfuscator/string-obfuscator.vue')['default']
|
StringObfuscator: typeof import('./src/tools/string-obfuscator/string-obfuscator.vue')['default']
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { tool as base64FileConverter } from './base64-file-converter';
|
import { tool as base64FileConverter } from './base64-file-converter';
|
||||||
import { tool as base64StringConverter } from './base64-string-converter';
|
import { tool as base64StringConverter } from './base64-string-converter';
|
||||||
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
||||||
|
import { tool as smartTextReplacer } from './smart-text-replacer';
|
||||||
import { tool as emailNormalizer } from './email-normalizer';
|
import { tool as emailNormalizer } from './email-normalizer';
|
||||||
|
|
||||||
import { tool as asciiTextDrawer } from './ascii-text-drawer';
|
import { tool as asciiTextDrawer } from './ascii-text-drawer';
|
||||||
|
@ -183,6 +184,7 @@ export const toolsByCategory: ToolCategory[] = [
|
||||||
stringObfuscator,
|
stringObfuscator,
|
||||||
textDiff,
|
textDiff,
|
||||||
numeronymGenerator,
|
numeronymGenerator,
|
||||||
|
smartTextReplacer,
|
||||||
asciiTextDrawer,
|
asciiTextDrawer,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
12
src/tools/smart-text-replacer/index.ts
Normal file
12
src/tools/smart-text-replacer/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { Search } from '@vicons/tabler';
|
||||||
|
import { defineTool } from '../tool';
|
||||||
|
|
||||||
|
export const tool = defineTool({
|
||||||
|
name: 'Smart Text Replacer and Linebreaker',
|
||||||
|
path: '/smart-text-replacer',
|
||||||
|
description: 'Search and replace a word on single or multiple occurrences just like windows notepad search and replace. Also allows to manage linebreaking and text splitting',
|
||||||
|
keywords: ['smart', 'text-replacer', 'linebreak', 'remove', 'add', 'split', 'search', 'replace'],
|
||||||
|
component: () => import('./smart-text-replacer.vue'),
|
||||||
|
icon: Search,
|
||||||
|
createdAt: new Date('2024-04-03'),
|
||||||
|
});
|
200
src/tools/smart-text-replacer/smart-text-replacer.vue
Normal file
200
src/tools/smart-text-replacer/smart-text-replacer.vue
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useCopy } from '@/composable/copy';
|
||||||
|
|
||||||
|
const str = ref('Lorem ipsum dolor sit amet DOLOR Lorem ipsum dolor sit amet DOLOR');
|
||||||
|
const findWhat = ref('');
|
||||||
|
const replaceWith = ref('');
|
||||||
|
const matchCase = ref(false);
|
||||||
|
const keepLineBreaks = ref(true);
|
||||||
|
const addLineBreakPlace = ref('before');
|
||||||
|
const addLineBreakRegex = ref('');
|
||||||
|
const splitEveryCharacterCounts = ref(0);
|
||||||
|
|
||||||
|
// Tracks the index of the currently active highlight.
|
||||||
|
const currentActiveIndex = ref(0);
|
||||||
|
// Tracks the total number of matches found to cycle through them.
|
||||||
|
const totalMatches = ref(0);
|
||||||
|
|
||||||
|
const highlightedText = computed(() => {
|
||||||
|
const findWhatValue = findWhat.value;
|
||||||
|
let strValue = str.value;
|
||||||
|
|
||||||
|
if (!strValue) {
|
||||||
|
return strValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keepLineBreaks.value) {
|
||||||
|
strValue = strValue.replace(/\r?\n/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addLineBreakRegex.value) {
|
||||||
|
const addLBRegex = new RegExp(addLineBreakRegex.value, matchCase.value ? 'g' : 'gi');
|
||||||
|
if (addLineBreakPlace.value === 'before') {
|
||||||
|
strValue = strValue.replace(addLBRegex, m => `\n${m}`);
|
||||||
|
}
|
||||||
|
else if (addLineBreakPlace.value === 'after') {
|
||||||
|
strValue = strValue.replace(addLBRegex, m => `${m}\n`);
|
||||||
|
}
|
||||||
|
else if (addLineBreakPlace.value === 'place') {
|
||||||
|
strValue = strValue.replace(addLBRegex, '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (splitEveryCharacterCounts.value) {
|
||||||
|
strValue = strValue.replace(new RegExp(`[^\n]{${splitEveryCharacterCounts.value}}`, 'g'), m => `${m}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!findWhatValue) {
|
||||||
|
return strValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = new RegExp(findWhatValue, matchCase.value ? 'g' : 'gi');
|
||||||
|
let index = 0;
|
||||||
|
const newStr = strValue.replace(regex, (match) => {
|
||||||
|
index++;
|
||||||
|
return `<span class="${match === findWhatValue ? 'highlight' : 'outline'}">${match}</span>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
totalMatches.value = index;
|
||||||
|
// Reset to -1 to ensure the first match is highlighted upon next search
|
||||||
|
currentActiveIndex.value = -1;
|
||||||
|
return newStr;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Automatically highlight the first occurrence after any change
|
||||||
|
watchEffect(async () => {
|
||||||
|
if (highlightedText.value) {
|
||||||
|
await nextTick();
|
||||||
|
updateHighlighting();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(matchCase, () => {
|
||||||
|
// Use nextTick to wait for the DOM to update after highlightedText re-reaction
|
||||||
|
nextTick().then(() => {
|
||||||
|
const matches = document.querySelectorAll('.outline, .highlight');
|
||||||
|
if (matches.length === 0) {
|
||||||
|
// No matches after change, reset
|
||||||
|
currentActiveIndex.value = -1;
|
||||||
|
totalMatches.value = 0;
|
||||||
|
}
|
||||||
|
else if (matches.length <= currentActiveIndex.value || currentActiveIndex.value === -1) {
|
||||||
|
// Current selection is out of range or reset, select the first match
|
||||||
|
currentActiveIndex.value = 0;
|
||||||
|
updateHighlighting(); // Ensure correct highlighting
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// The current selection is still valid, ensure it's highlighted correctly
|
||||||
|
updateHighlighting(); // This might need adjustment to not advance the index
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to add active highlighting
|
||||||
|
function updateHighlighting() {
|
||||||
|
currentActiveIndex.value = (currentActiveIndex.value + 1) % totalMatches.value;
|
||||||
|
const matches = document.querySelectorAll('.outline, .highlight');
|
||||||
|
matches.forEach((match, index) => {
|
||||||
|
match.className = index === currentActiveIndex.value ? 'highlight' : 'outline';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceSelected() {
|
||||||
|
const matches = document.querySelectorAll('.outline, .highlight');
|
||||||
|
if (matches.length > currentActiveIndex.value) {
|
||||||
|
const selectedMatch = matches[currentActiveIndex.value];
|
||||||
|
if (selectedMatch) {
|
||||||
|
const newText = replaceWith.value;
|
||||||
|
selectedMatch.textContent = newText;
|
||||||
|
selectedMatch.classList.remove('highlight');
|
||||||
|
currentActiveIndex.value--;
|
||||||
|
totalMatches.value--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateHighlighting();
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceAll() {
|
||||||
|
const matches = document.querySelectorAll('.outline, .highlight');
|
||||||
|
matches.forEach((match) => {
|
||||||
|
match.textContent = replaceWith.value;
|
||||||
|
match.classList.remove('highlight');
|
||||||
|
match.classList.remove('outline');
|
||||||
|
});
|
||||||
|
currentActiveIndex.value = -1;
|
||||||
|
totalMatches.value = matches.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNext() {
|
||||||
|
updateHighlighting();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { copy } = useCopy({ source: highlightedText });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<c-input-text v-model:value="str" raw-text placeholder="Enter text here..." label="Text to search and replace:" clearable multiline rows="10" />
|
||||||
|
|
||||||
|
<div mt-4 w-full flex gap-10px>
|
||||||
|
<div flex-1>
|
||||||
|
<div>Find what:</div>
|
||||||
|
<c-input-text v-model:value="findWhat" placeholder="Search regex" @keyup.enter="findNext()" />
|
||||||
|
</div>
|
||||||
|
<div flex-1>
|
||||||
|
<div>Replace with:</div>
|
||||||
|
<c-input-text v-model:value="replaceWith" placeholder="Replacement expression" @keyup.enter="replaceSelected()" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<n-space mt-4 gap-1 align="baseline" justify="space-between">
|
||||||
|
<c-button @click="findNext()">
|
||||||
|
<label>Find Next</label>
|
||||||
|
</c-button>
|
||||||
|
<c-button @click="replaceSelected()">
|
||||||
|
<label>Replace</label>
|
||||||
|
</c-button>
|
||||||
|
<c-button @click="replaceAll()">
|
||||||
|
<label>Replace All</label>
|
||||||
|
</c-button>
|
||||||
|
<n-checkbox v-model:checked="matchCase">
|
||||||
|
<label>Match case</label>
|
||||||
|
</n-checkbox>
|
||||||
|
<n-checkbox v-model:checked="keepLineBreaks">
|
||||||
|
<label>Keep linebreaks</label>
|
||||||
|
</n-checkbox>
|
||||||
|
</n-space>
|
||||||
|
|
||||||
|
<n-divider />
|
||||||
|
|
||||||
|
<div mt-4 w-full flex items-baseline gap-10px>
|
||||||
|
<c-select
|
||||||
|
v-model:value="addLineBreakPlace"
|
||||||
|
:options="[{ value: 'before', label: 'Add linebreak before' }, { value: 'after', label: 'Add linebreak after' }, { value: 'place', label: 'Add linebreak in place of' }]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="addLineBreakRegex"
|
||||||
|
placeholder="Split text regex"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div mt-4 w-full flex items-baseline gap-10px>
|
||||||
|
<n-form-item label="Split every characters:" label-placement="left">
|
||||||
|
<n-input-number v-model:value="splitEveryCharacterCounts" :min="0" />
|
||||||
|
</n-form-item>
|
||||||
|
</div>
|
||||||
|
<c-card v-if="highlightedText" mt-60px max-w-600px flex items-center gap-5px font-mono>
|
||||||
|
<!-- //NOSONAR --><div flex-1 break-anywhere text-wrap style="white-space: pre-wrap" v-html="highlightedText" />
|
||||||
|
|
||||||
|
<c-button @click="copy()">
|
||||||
|
<icon-mdi:content-copy />
|
||||||
|
</c-button>
|
||||||
|
</c-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.highlight {
|
||||||
|
background-color: #ff0;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Add table
Add a link
Reference in a new issue