Skip to content

Commit

Permalink
feat(new tool): Smart Text Replacer and LineBreaks manager
Browse files Browse the repository at this point in the history
Smart Replacer functionality taken as base from CorentinTh#976 by @utf26
Fixed linebreaking display in Smart Replacer
Add linebreaking options

Fix CorentinTh#1279 CorentinTh#1194 CorentinTh#616
  • Loading branch information
sharevb committed Sep 11, 2024
1 parent 80e46c9 commit 2f2b3db
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 1 deletion.
11 changes: 10 additions & 1 deletion src/tools/index.ts
Original file line number Diff line number Diff line change
@@ -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 pdfSignatureChecker } from './pdf-signature-checker';
import { tool as numeronymGenerator } from './numeronym-generator';
import { tool as macAddressGenerator } from './mac-address-generator';
Expand Down Expand Up @@ -155,7 +156,15 @@ export const toolsByCategory: ToolCategory[] = [
},
{
name: 'Text',
components: [loremIpsumGenerator, textStatistics, emojiPicker, stringObfuscator, textDiff, numeronymGenerator],
components: [
loremIpsumGenerator,
textStatistics,
emojiPicker,
stringObfuscator,
textDiff,
numeronymGenerator,
smartTextReplacer,
],
},
{
name: 'Data',
Expand Down
13 changes: 13 additions & 0 deletions src/tools/smart-text-replacer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Search } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';

export const tool = defineTool({
name: translate('tools.smart-text-replacer.title'),
path: '/smart-text-replacer',
description: translate('tools.smart-text-replacer.description'),
keywords: ['smart', 'text-replacer', 'linebreak', 'remove', 'add', 'split', 'search', 'replace'],
component: () => import('./smart-text-replacer.vue'),
icon: Search,
createdAt: new Date('2024-04-03'),
});
204 changes: 204 additions & 0 deletions src/tools/smart-text-replacer/smart-text-replacer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
<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.trim();
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="(can include $1 or $<groupName>)" @keyup.enter="replaceSelected()" />
</div>
</div>

<div mt-4 w-full flex gap-10px>
<div flex-2 flex items-baseline gap-10px>
<c-button @click="findNext()">
<label>Find Next</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>
</div>
<div flex flex-1 justify-end gap-10px>
<c-button @click="replaceSelected()">
<label>Replace</label>
</c-button>
<c-button @click="replaceAll()">
<label>Replace All</label>
</c-button>
</div>
</div>

<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>
<div flex-1 break-anywhere text-wrap style="white-space: pre" 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>

0 comments on commit 2f2b3db

Please sign in to comment.