mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-20 06:55:06 -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']
|
||||
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']
|
||||
IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
|
||||
IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
|
||||
|
@ -135,6 +136,8 @@ declare module '@vue/runtime-core' {
|
|||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDivider: typeof import('naive-ui')['NDivider']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NFlex: typeof import('naive-ui')['NFlex']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NH1: typeof import('naive-ui')['NH1']
|
||||
NH3: typeof import('naive-ui')['NH3']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
|
@ -142,7 +145,6 @@ declare module '@vue/runtime-core' {
|
|||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||
NMenu: typeof import('naive-ui')['NMenu']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NTable: typeof import('naive-ui')['NTable']
|
||||
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']
|
||||
|
@ -162,6 +164,7 @@ declare module '@vue/runtime-core' {
|
|||
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']
|
||||
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']
|
||||
SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.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 base64StringConverter } from './base64-string-converter';
|
||||
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 asciiTextDrawer } from './ascii-text-drawer';
|
||||
|
@ -183,6 +184,7 @@ export const toolsByCategory: ToolCategory[] = [
|
|||
stringObfuscator,
|
||||
textDiff,
|
||||
numeronymGenerator,
|
||||
smartTextReplacer,
|
||||
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