Skip to content

Commit

Permalink
feat(new tool): Unicode Search
Browse files Browse the repository at this point in the history
  • Loading branch information
sharevb committed Sep 14, 2024
1 parent 6709498 commit c4f9eb3
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 1 deletion.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@
"@tiptap/pm": "2.1.6",
"@tiptap/starter-kit": "2.1.6",
"@tiptap/vue-3": "2.0.3",
"@types/markdown-it": "^13.0.7",
"@types/figlet": "^1.5.8",
"@types/markdown-it": "^13.0.7",
"@types/utf8": "^3.0.3",
"@unicode/unicode-15.1.0": "^1.6.0",
"@vicons/material": "^0.12.0",
"@vicons/tabler": "^0.12.0",
"@vueuse/core": "^10.3.0",
Expand Down Expand Up @@ -89,6 +91,7 @@
"ulid": "^2.3.0",
"unicode-emoji-json": "^0.4.0",
"unplugin-auto-import": "^0.16.4",
"utf8": "^3.0.0",
"uuid": "^9.0.0",
"vue": "^3.3.4",
"vue-i18n": "^9.9.1",
Expand Down
21 changes: 21 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,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 emailNormalizer } from './email-normalizer';
import { tool as unicodeSearch } from './unicode-search';

import { tool as asciiTextDrawer } from './ascii-text-drawer';

Expand Down Expand Up @@ -135,6 +136,7 @@ export const toolsByCategory: ToolCategory[] = [
httpStatusCodes,
jsonDiff,
safelinkDecoder,
unicodeSearch,
],
},
{
Expand Down
12 changes: 12 additions & 0 deletions src/tools/unicode-search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { FileText } from '@vicons/tabler';
import { defineTool } from '../tool';

export const tool = defineTool({
name: 'Unicode Search',
path: '/unicode-search',
description: 'Search in Unicode Characters',
keywords: ['unicode', 'search'],
component: () => import('./unicode-search.vue'),
icon: FileText,
createdAt: new Date('2024-08-15'),
});
122 changes: 122 additions & 0 deletions src/tools/unicode-search/unicode-search.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<script setup lang="ts">
import unicodeNames from '@unicode/unicode-15.1.0/Names/index.js';
import unicodeCategories from '@unicode/unicode-15.1.0/General_Category';
import utf8 from 'utf8';
import { useFuzzySearch } from '@/composable/fuzzySearch';
import useDebouncedRef from '@/composable/debouncedref';
function toPaddedHex(num: number) {
return num.toString(16).padStart(4, '0').toUpperCase();
}
function toUTF8(codePoint: number) {
const utf8String = utf8.encode(String.fromCodePoint(codePoint));
return [...utf8String].map(c => `\\x${c.codePointAt(0)?.toString(16).toUpperCase()}`);
}
const searchQuery = useDebouncedRef('', 500);
const parsedSearchQuery = computed(() => {
const parsedRegex = /^\s*(?:\&#x(?<hex1>[\da-f]+);|\&#(?<dec>\d+);|(?:U\+|\\u)?\s*(?<hex2>[\da-f]+))\s*$/gi;
const parsedQuery = parsedRegex.exec(searchQuery.value);
if (parsedQuery) {
if (parsedQuery.groups?.hex1 || parsedQuery.groups?.hex2) {
return `=${toPaddedHex(Number.parseInt(parsedQuery.groups.hex1 || parsedQuery.groups.hex1, 16))}`;
}
if (parsedQuery.groups?.dec) {
return `=${toPaddedHex(Number.parseInt(parsedQuery.groups.dec, 10))}`;
}
}
return searchQuery.value;
});
const unicodeSearchData = [...unicodeNames].map(([codePoint, characterName]) => {
const hex = toPaddedHex(codePoint);
return {
codePoint,
characterName: `${characterName} (U+${hex})`,
hex,
};
});
const { searchResult } = useFuzzySearch({
search: parsedSearchQuery,
data: unicodeSearchData,
options: {
keys: ['characterName', 'hex'],
threshold: 0.2,
isCaseSensitive: false,
minMatchCharLength: 3,
useExtendedSearch: true,
},
});
</script>

<template>
<div mx-auto max-w-2400px important:flex-1>
<div flex items-center gap-3>
<c-input-text
v-model:value="searchQuery"
placeholder="Search Unicode by name (e.g. 'zero width') or code point..."
mx-auto max-w-600px
>
<template #prefix>
<icon-mdi-search mr-6px color-black op-70 dark:color-white />
</template>
</c-input-text>
</div>

<div v-if="searchQuery.trim().length > 0">
<div
v-if="searchResult.length === 0"
mt-4
text-20px
font-bold
>
No results
</div>

<div v-else>
<div mt-4 text-20px font-bold>
Search result
</div>

<n-table>
<thead>
<th>UCOD</th>
<th>Display/UTF8</th>
<th style="width:30%">
Category
</th>
<th>Html</th>
<th style="width:30%">
Name
</th>
</thead>
<tbody>
<tr v-for="(result, ix) in searchResult" :key="ix">
<td>
<input-copyable :value="`U+${toPaddedHex(result.codePoint)}`" mb-1 />
<!-- //NOSONAR --><n-a :href="`https://unicodeplus.com/U+${toPaddedHex(result.codePoint)}`" target="_blank">
&gt; More info
</n-a>
</td>
<td>
<input-copyable :value="String.fromCodePoint(result.codePoint)" mb-1 />
<input-copyable :value="toUTF8(result.codePoint)" />
</td>
<td>
<input-copyable :value="unicodeCategories.get(result.codePoint)" />
</td>
<td>
<input-copyable :value="`\&\#x${toPaddedHex(result.codePoint)};`" mb-1 />
<input-copyable :value="`\&\#${result.codePoint};`" />
</td>
<td><input-copyable :value="result.characterName" /></td>
</tr>
</tbody>
</n-table>
</div>
</div>
</div>
</template>
9 changes: 9 additions & 0 deletions src/tools/unicode-search/unicode.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
declare module '@unicode/unicode-15.1.0/Names/index.js'{
const unicode: HashSet<number, string>;
export default unicode;
}

declare module '@unicode/unicode-15.1.0/General_Category'{
const unicode: HashSet<number, string>;
export default unicode;
}

0 comments on commit c4f9eb3

Please sign in to comment.