Merge branch 'CorentinTh:main' into main

This commit is contained in:
樱吹雪 2023-11-23 14:27:22 +08:00 committed by GitHub
commit ee9bbcb327
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 655 additions and 475 deletions

View file

@ -100,7 +100,7 @@ pnpm lint
To create a new tool, there is a script that generate the boilerplate of the new tool, simply run:
```sh
pnpm run script:create-new-tool my-tool-name
pnpm run script:create:tool my-tool-name
```
It will create a directory in `src/tools` with the correct files, and a the import in `src/tools/index.ts`. You will just need to add the imported tool in the proper category and develop the tool.

2
components.d.ts vendored
View file

@ -43,6 +43,8 @@ declare module '@vue/runtime-core' {
CLabel: typeof import('./src/ui/c-label/c-label.vue')['default']
CLink: typeof import('./src/ui/c-link/c-link.vue')['default']
'CLink.demo': typeof import('./src/ui/c-link/c-link.demo.vue')['default']
CMarkdown: typeof import('./src/ui/c-markdown/c-markdown.vue')['default']
'CMarkdown.demo': typeof import('./src/ui/c-markdown/c-markdown.demo.vue')['default']
CModal: typeof import('./src/ui/c-modal/c-modal.vue')['default']
'CModal.demo': typeof import('./src/ui/c-modal/c-modal.demo.vue')['default']
CModalValue: typeof import('./src/ui/c-modal-value/c-modal-value.vue')['default']

View file

@ -7,6 +7,7 @@ home:
toggleMenu: 'Toggle menu'
home: Home
uiLib: 'UI Lib'
support: 'Support IT Tools development'
buyMeACoffee: 'Buy me a coffee'
follow:
title: 'You like it-tools?'
@ -26,26 +27,31 @@ home:
lightMode: 'Light mode'
mode: 'Toggle dark/light mode'
about:
h1: 'About IT-Tools'
h1p1: 'This wonderful website, made with ❤ by'
h1p2: ", aggregates useful tools for developer and people working in IT. If you find it useful, please feel free to share it to people you think may find it useful too and don''t forget to bookmark it in your shortcut bar!"
h1p3: 'IT Tools is open-source (under the MIT license) and free, and will always be, but it costs me money to host and renew the domain name. If you want to support my work, and encourage me to add more tools, please consider supporting by'
h1p4: 'sponsoring me'
h2: Technologies
h2p1: 'IT Tools is made in Vue.js (Vue 3) with the the Naive UI component library and is hosted and continuously deployed by Vercel. Third-party open-source libraries are used in some tools, you may find the complete list in the'
h2p2: 'file of the repository.'
h3: 'Found a bug? A tool is missing?'
h3p1: 'If you need a tool that is currently not present here, and you think can be useful, you are welcome to submit a feature request in the'
h3p2: 'issues section'
h3p3: 'in the GitHub repository.'
h3p4: "And if you found a bug, or something doesn''t work as expected, please file a bug report in the"
h3p5: 'issues section'
h3p6: 'in the GitHub repository.'
content: >
# About IT-Tools
This wonderful website, made with ❤ by [Corentin Thomasset](https://github.com/CorentinTh) , aggregates useful tools for developer and people working in IT. If you find it useful, please feel free to share it to people you think may find it useful too and don't forget to bookmark it in your shortcut bar!
IT Tools is open-source (under the MIT license) and free, and will always be, but it costs me money to host and renew the domain name. If you want to support my work, and encourage me to add more tools, please consider supporting by [sponsoring me](https://www.buymeacoffee.com/cthmsst).
## Technologies
IT Tools is made in Vue.js (Vue 3) with the the Naive UI component library and is hosted and continuously deployed by Vercel. Third-party open-source libraries are used in some tools, you may find the complete list in the [package.json](https://github.com/CorentinTh/it-tools/blob/main/package.json) file of the repository.
## Found a bug? A tool is missing?
If you need a tool that is currently not present here, and you think can be useful, you are welcome to submit a feature request in the [issues section](https://github.com/CorentinTh/it-tools/issues/new/choose) in the GitHub repository.
And if you found a bug, or something doesn't work as expected, please file a bug report in the [issues section](https://github.com/CorentinTh/it-tools/issues/new/choose) in the GitHub repository.
404:
notFound: '404 Not Found'
sorry: 'Sorry, this page does not seem to exist'
maybe: 'Maybe the cache is doing tricky things, try force-refreshing?'
backHome: 'Back home'
favoriteButton:
remove: 'Remove from favorites'
add: 'Add to favorites'
toolCard:
new: New
search:

View file

@ -25,6 +25,22 @@ home:
darkMode: 'Mode sombre'
lightMode: 'Mode clair'
mode: 'Basculer le mode sombre/clair'
about:
content: >
# À propos de IT-Tools
Ce merveilleux site, fait avec ❤ par [Corentin Thomasset](https://github.com/CorentinTh), regroupe des outils utiles pour les développeurs et les personnes travaillant dans l'informatique. Si vous le trouvez utile, n'hésitez pas à le partager et n'oubliez pas de le mettre dans vos favoris !
IT Tools est open-source (sous licence MIT) et gratuit, et le restera toujours, mais cela me coûte de l'argent pour l'héberger et renouveler le nom de domaine. Si vous voulez soutenir mon travail, et m'encourager à ajouter plus d'outils, n'hésitez pas à me [soutenir](https://www.buymeacoffee.com/cthmsst).
## Technologies
IT Tools est fait en Vue.js (Vue 3) avec la bibliothèque de composants Naive UI et est hébergé et déployé en continu par Vercel. Des bibliothèques open-source tierces sont utilisées dans certains outils, vous pouvez trouver la liste complète dans le fichier [package.json](https://github.com/CorentinTh/it-tools/blob/main/package.json) du dépôt.
## Vous avez trouvé un bug ? Un outil manque ?
Si vous avez besoin d'un outil qui n'est pas encore présent ici, et que vous pensez qu'il peut être utile, vous êtes invité à soumettre une demande de fonctionnalité dans la [section issue](https://github.com/CorentinTh/it-tools/issues/new/choose) du dépôt GitHub.
404:
notFound: '404 Not Found'
sorry: "Désolé, cette page n'existe pas"

71
locales/zh.yml Normal file
View file

@ -0,0 +1,71 @@
home:
categories:
newestTools: '最新工具'
favoriteTools: '我的收藏'
allTools: '全部工具'
subtitle: '助力开发人员和 IT 工作者'
toggleMenu: '切换菜单'
home: '主页'
uiLib: 'UI 库'
support: '支持 IT 工具开发'
buyMeACoffee: '赞助'
follow:
title: '关注我们'
p1: '给我们 Star'
githubRepository: 'GitHub 仓库'
p2: '关注我们的'
twitterAccount: 'Twitter'
thankYou: '感谢您的支持!'
nav:
github: 'GitHub 仓库'
githubRepository: 'GitHub 仓库'
twitter: 'Twitter 账号'
twitterAccount: 'Twitter 账号'
about: '关于 IT-Tools'
aboutLabel: '关于'
darkMode: '深色模式'
lightMode: '浅色模式'
mode: '颜色模式'
about:
content: >
# 关于 IT-Tools
IT-Tools 由 [Corentin Thomasset](https://github.com/CorentinTh) 用 ❤ 开发,汇集了对开发人员和 IT 从业者有用的工具。如果对您有帮助,请将其分享给您的朋友,并且添加到收藏夹中!
IT-Tools 永久免费且开源MIT 许可证),但需要资金用于托管和续订域名。如果您想支持我的工作,并鼓励我添加更多工具,请考虑通过 [赞助我](https://www.buymeacoffee.com/cthmsst) 进行支持。
## 技术
IT-Tools 采用 Vue.jsVue 3和 Naive UI 组件库开发,并由 Vercel 托管和持续部署。某些工具使用了第三方开源库,您可以在仓库的 [package.json](https://github.com/CorentinTh/it-tools/blob/main/package.json) 文件中找到完整的列表。
## 发现了 Bug缺少工具
如果目前这里没有您需要的工具,并且您认为它可能有用,欢迎在 GitHub 仓库的 [issues](https://github.com/CorentinTh/it-tools/issues/new/choose) 中提交新增功能的请求。
如果您发现了 Bug或者某些功能未能按预期工作请在 GitHub 仓库的 [issues](https://github.com/CorentinTh/it-tools/issues/new/choose) 中提交错误报告。
404:
notFound: '404 页面不存在'
sorry: '抱歉,该页面似乎不存在'
maybe: '也许缓存出现了一些问题,试试强制刷新页面?'
backHome: '返回主页'
favoriteButton:
remove: '取消收藏'
add: '加入收藏'
toolCard:
new: '新'
search:
label: '搜索'
tools:
categories:
favorite-tools: '我的收藏'
crypto: '加密'
converter: '转换器'
web: Web
images and videos: '图片和视频'
development: '开发'
network: '网络'
math: '数学'
measurement: '测量'
text: '文本'
data: '数据'

View file

@ -55,6 +55,7 @@
"cronstrue": "^2.26.0",
"crypto-js": "^4.1.1",
"date-fns": "^2.29.3",
"dompurify": "^3.0.6",
"emojilib": "^3.0.10",
"figue": "^1.2.0",
"fuse.js": "^6.6.2",
@ -65,13 +66,14 @@
"jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.10.28",
"lodash": "^4.17.21",
"marked": "^10.0.0",
"mathjs": "^11.9.1",
"mime-types": "^2.1.35",
"monaco-editor": "^0.43.0",
"naive-ui": "^2.35.0",
"netmask": "^2.0.2",
"node-forge": "^1.3.1",
"oui": "^12.0.52",
"oui-data": "^1.0.10",
"pdf-signature-reader": "^1.4.2",
"pinia": "^2.0.34",
"plausible-tracker": "^0.3.8",
@ -98,6 +100,7 @@
"@tsconfig/node18": "^18.2.0",
"@types/bcryptjs": "^2.4.2",
"@types/crypto-js": "^4.1.1",
"@types/dompurify": "^3.0.5",
"@types/jsdom": "^21.0.0",
"@types/lodash": "^4.14.192",
"@types/mime-types": "^2.1.1",
@ -107,7 +110,7 @@
"@types/qrcode": "^1.5.0",
"@types/ua-parser-js": "^0.7.36",
"@types/uuid": "^9.0.0",
"@unocss/eslint-config": "^0.55.0",
"@unocss/eslint-config": "^0.57.0",
"@vitejs/plugin-vue": "^4.3.2",
"@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue/compiler-sfc": "^3.2.47",
@ -121,7 +124,7 @@
"less": "^4.1.3",
"prettier": "^3.0.0",
"typescript": "~5.2.0",
"unocss": "^0.55.0",
"unocss": "^0.57.0",
"unocss-preset-scrollbar": "^0.2.1",
"unplugin-icons": "^0.17.0",
"unplugin-vue-components": "^0.25.0",

668
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,7 @@ function toggleFavorite(event: MouseEvent) {
</script>
<template>
<c-tooltip :tooltip="isFavorite ? 'Remove from favorites' : 'Add to favorites' ">
<c-tooltip :tooltip="isFavorite ? $t('favoriteButton.remove') : $t('favoriteButton.add') ">
<c-button
variant="text"
circle

View file

@ -0,0 +1,32 @@
import { describe, expect, it } from 'vitest';
import { getMimeTypeFromBase64 } from './downloadBase64';
describe('downloadBase64', () => {
describe('getMimeTypeFromBase64', () => {
it('when the base64 string has a data URI, it returns the mime type', () => {
expect(getMimeTypeFromBase64({ base64String: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA' })).to.deep.equal({ mimeType: 'image/png' });
expect(getMimeTypeFromBase64({ base64String: 'data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA' })).to.deep.equal({ mimeType: 'image/jpg' });
});
it('when the base64 string has no data URI, it try to infer the mime type from the signature', () => {
// https://en.wikipedia.org/wiki/List_of_file_signatures
// PNG
expect(getMimeTypeFromBase64({ base64String: 'iVBORw0KGgoAAAANSUhEUgAAAAUA' })).to.deep.equal({ mimeType: 'image/png' });
// GIF
expect(getMimeTypeFromBase64({ base64String: 'R0lGODdh' })).to.deep.equal({ mimeType: 'image/gif' });
expect(getMimeTypeFromBase64({ base64String: 'R0lGODlh' })).to.deep.equal({ mimeType: 'image/gif' });
// JPG
expect(getMimeTypeFromBase64({ base64String: '/9j/' })).to.deep.equal({ mimeType: 'image/jpg' });
// PDF
expect(getMimeTypeFromBase64({ base64String: 'JVBERi0' })).to.deep.equal({ mimeType: 'application/pdf' });
});
it('when the base64 string has no data URI and no signature, it returns an undefined mimeType', () => {
expect(getMimeTypeFromBase64({ base64String: 'JVBERi' })).to.deep.equal({ mimeType: undefined });
});
});
});

View file

@ -1,32 +1,60 @@
import { extension as getExtensionFromMime } from 'mime-types';
import type { Ref } from 'vue';
import _ from 'lodash';
function getFileExtensionFromBase64({
base64String,
export { getMimeTypeFromBase64, useDownloadFileFromBase64 };
const commonMimeTypesSignatures = {
'JVBERi0': 'application/pdf',
'R0lGODdh': 'image/gif',
'R0lGODlh': 'image/gif',
'iVBORw0KGgo': 'image/png',
'/9j/': 'image/jpg',
};
function getMimeTypeFromBase64({ base64String }: { base64String: string }) {
const [,mimeTypeFromBase64] = base64String.match(/data:(.*?);base64/i) ?? [];
if (mimeTypeFromBase64) {
return { mimeType: mimeTypeFromBase64 };
}
const inferredMimeType = _.find(commonMimeTypesSignatures, (_mimeType, signature) => base64String.startsWith(signature));
if (inferredMimeType) {
return { mimeType: inferredMimeType };
}
return { mimeType: undefined };
}
function getFileExtensionFromMimeType({
mimeType,
defaultExtension = 'txt',
}: {
base64String: string
mimeType: string | undefined
defaultExtension?: string
}) {
const hasMimeType = base64String.match(/data:(.*?);base64/i);
if (hasMimeType) {
return getExtensionFromMime(hasMimeType[1]) || defaultExtension;
if (mimeType) {
return getExtensionFromMime(mimeType) ?? defaultExtension;
}
return defaultExtension;
}
export function useDownloadFileFromBase64({ source, filename }: { source: Ref<string>; filename?: string }) {
function useDownloadFileFromBase64({ source, filename }: { source: Ref<string>; filename?: string }) {
return {
download() {
const base64String = source.value;
if (base64String === '') {
if (source.value === '') {
throw new Error('Base64 string is empty');
}
const cleanFileName = filename ?? `file.${getFileExtensionFromBase64({ base64String })}`;
const { mimeType } = getMimeTypeFromBase64({ base64String: source.value });
const base64String = mimeType
? source.value
: `data:text/plain;base64,${source.value}`;
const cleanFileName = filename ?? `file.${getFileExtensionFromMimeType({ mimeType })}`;
const a = document.createElement('a');
a.href = base64String;

View file

@ -100,13 +100,13 @@ const tools = computed<ToolCategory[]>(() => [
<NIcon size="25" :component="Menu2" />
</c-button>
<c-tooltip tooltip="Home" position="bottom">
<c-tooltip :tooltip="$t('home.home')" position="bottom">
<c-button to="/" circle variant="text" :aria-label="$t('home.home')">
<NIcon size="25" :component="Home2" />
</c-button>
</c-tooltip>
<c-tooltip tooltip="UI Lib" position="bottom">
<c-tooltip :tooltip="$t('home.uiLib')" position="bottom">
<c-button v-if="config.app.env === 'development'" to="/c-lib" circle variant="text" :aria-label="$t('home.uiLib')">
<icon-mdi:brush-variant text-20px />
</c-button>
@ -120,7 +120,7 @@ const tools = computed<ToolCategory[]>(() => [
<NavbarButtons v-if="!styleStore.isSmallScreen" />
</div>
<c-tooltip position="bottom" tooltip="Support IT Tools development">
<c-tooltip position="bottom" :tooltip="$t('home.support')">
<c-button
round
href="https://www.buymeacoffee.com/cthmsst"

View file

@ -1,63 +1,9 @@
<script setup lang="ts">
import { useHead } from '@vueuse/head';
import { useTracker } from '@/modules/tracker/tracker.services';
useHead({ title: 'About - IT Tools' });
const { tracker } = useTracker();
</script>
<template>
<div mx-auto mt-50px max-w-600px>
<h1>{{ $t('about.h1') }}</h1>
<p text-justify>
{{ $t('about.h1p1') }}
<c-link href="https://github.com/CorentinTh" target="_blank" rel="noopener">
Corentin Thomasset
</c-link>{{ $t('about.h1p2') }}
</p>
<p text-justify>
{{ $t('about.h1p3') }}
<c-link
href="https://www.buymeacoffee.com/cthmsst"
rel="noopener"
target="_blank"
@click="() => tracker.trackEvent({ eventName: 'Support button clicked' })"
>
{{ $t('about.h1p4') }}
</c-link>.
</p>
<h2>{{ $t('about.h2') }}</h2>
<p text-justify>
{{ $t('about.h2p1') }}
<c-link href="https://github.com/CorentinTh/it-tools/blob/main/package.json" rel="noopener" target="_blank">
package.json
</c-link>
{{ $t('about.h2p2') }}
</p>
<h2>{{ $t('about.h3') }}</h2>
<p text-justify>
{{ $t('about.h3p1') }}
<c-link
href="https://github.com/CorentinTh/it-tools/issues/new/choose"
rel="noopener"
target="_blank"
>
{{ $t('about.h3p2') }}
</c-link>
{{ $t('about.h3p3') }}
</p>
<p text-justify>
{{ $t('about.h3p4') }}
<c-link
href="https://github.com/CorentinTh/it-tools/issues/new/choose"
rel="noopener"
target="_blank"
>
{{ $t('about.h3p5') }}
</c-link>
{{ $t('about.h3p6') }}
</p>
</div>
<c-markdown :markdown="$t('about.content')" mx-auto mt-50px max-w-600px />
</template>

View file

@ -1,12 +1,12 @@
<script setup lang="ts">
import db from 'oui/oui.json';
import db from 'oui-data';
import { macAddressValidationRules } from '@/utils/macAddress';
import { useCopy } from '@/composable/copy';
const getVendorValue = (address: string) => address.trim().replace(/[.:-]/g, '').toUpperCase().substring(0, 6);
const macAddress = ref('20:37:06:12:34:56');
const details = computed<string | undefined>(() => db[getVendorValue(macAddress.value)]);
const details = computed<string | undefined>(() => (db as Record<string, string>)[getVendorValue(macAddress.value)]);
const { copy } = useCopy({ source: () => details.value ?? '', text: 'Vendor info copied to the clipboard' });
</script>

View file

@ -1,4 +0,0 @@
declare module 'oui/oui.json' {
const db: Record<string, string>;
export default db;
}

View file

@ -1,9 +1,16 @@
tools:
token-generator:
title: Générateur de token
description: Génère une chaîne aléatoire avec les caractères que vous voulez, lettres majuscules ou minuscules, chiffres et/ou symboles.
description: >-
Génère une chaîne aléatoire avec les caractères que vous voulez, lettres
majuscules ou minuscules, chiffres et/ou symboles.
uppercase: Majuscules (ABC...)
lowercase: Minuscules (abc...)
numbers: Chiffres (123...)
symbols: Symboles (!-;...)
button:
copy: Copier
refresh: Rafraichir
copied: Le token a été copié
length: Longueur
tokenPlaceholder: Le token...

View file

@ -2,11 +2,11 @@ import { Fingerprint } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'UUIDs v4 generator',
name: 'UUIDs generator',
path: '/uuid-generator',
description:
'A Universally Unique Identifier (UUID) is a 128-bit number used to identify information in computer systems. The number of possible UUIDs is 16^32, which is 2^128 or about 3.4x10^38 (which is a lot!).',
keywords: ['uuid', 'v4', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'],
keywords: ['uuid', 'v4', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique', 'v1', 'v3', 'v5', 'nil'],
component: () => import('./uuid-generator.vue'),
icon: Fingerprint,
});

View file

@ -1,22 +1,94 @@
<script setup lang="ts">
import { v4 as generateUUID } from 'uuid';
import { v1 as generateUuidV1, v3 as generateUuidV3, v4 as generateUuidV4, v5 as generateUuidV5, NIL as nilUuid } from 'uuid';
import { useCopy } from '@/composable/copy';
import { computedRefreshable } from '@/composable/computedRefreshable';
import { withDefaultOnError } from '@/utils/defaults';
const versions = ['NIL', 'v1', 'v3', 'v4', 'v5'] as const;
const version = useStorage<typeof versions[number]>('uuid-generator:version', 'v4');
const count = useStorage('uuid-generator:quantity', 1);
const v35Args = ref({ namespace: '6ba7b811-9dad-11d1-80b4-00c04fd430c8', name: '' });
const [uuids, refreshUUIDs] = computedRefreshable(() =>
Array.from({ length: count.value }, () => generateUUID()).join('\n'),
);
const validUuidRules = [
{
message: 'Invalid UUID',
validator: (value: string) => {
if (value === nilUuid) {
return true;
}
return Boolean(value.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/));
},
},
];
const generators = {
NIL: () => nilUuid,
v1: (index: number) => generateUuidV1({
clockseq: index,
msecs: Date.now(),
nsecs: Math.floor(Math.random() * 10000),
node: Array.from({ length: 6 }, () => Math.floor(Math.random() * 256)),
}),
v3: () => generateUuidV3(v35Args.value.name, v35Args.value.namespace),
v4: () => generateUuidV4(),
v5: () => generateUuidV5(v35Args.value.name, v35Args.value.namespace),
};
const [uuids, refreshUUIDs] = computedRefreshable(() => withDefaultOnError(() =>
Array.from({ length: count.value }, (_ignored, index) => {
const generator = generators[version.value] ?? generators.NIL;
return generator(index);
}).join('\n'), ''));
const { copy } = useCopy({ source: uuids, text: 'UUIDs copied to the clipboard' });
</script>
<template>
<div>
<div flex items-center justify-center gap-3>
Quantity :
<n-input-number v-model:value="count" :min="1" :max="50" placeholder="UUID quantity" />
<c-buttons-select v-model:value="version" :options="versions" label="UUID version" label-width="100px" mb-2 />
<div mb-2 flex items-center>
<span w-100px>Quantity </span>
<n-input-number v-model:value="count" flex-1 :min="1" :max="50" placeholder="UUID quantity" />
</div>
<div v-if="version === 'v3' || version === 'v5'">
<div>
<c-buttons-select
v-model:value="v35Args.namespace"
:options="{
DNS: '6ba7b810-9dad-11d1-80b4-00c04fd430c8',
URL: '6ba7b811-9dad-11d1-80b4-00c04fd430c8',
OID: '6ba7b812-9dad-11d1-80b4-00c04fd430c8',
X500: '6ba7b814-9dad-11d1-80b4-00c04fd430c8',
}"
label="Namespace"
label-width="100px"
mb-2
/>
</div>
<div flex-1>
<c-input-text
v-model:value="v35Args.namespace"
placeholder="Namespace"
label-width="100px"
label-position="left"
label=" "
:validation-rules="validUuidRules"
mb-2
/>
</div>
<c-input-text
v-model:value="v35Args.name"
placeholder="Name"
label="Name"
label-width="100px"
label-position="left"
mb-2
/>
</div>
<c-input-text

View file

@ -4,11 +4,18 @@ const optionsA = [
{ label: 'Option B', value: 'b', tooltip: 'This is a tooltip' },
{ label: 'Option C', value: 'c' },
];
const optionB = {
'Option A': 'a',
'Option B': 'b',
'Option C': 'c',
};
const valueA = ref('a');
</script>
<template>
<c-buttons-select v-model:value="valueA" :options="optionsA" label="Label: " />
<c-buttons-select v-model:value="valueA" :options="optionsA" label="Label: " label-position="left" mt-2 />
<c-buttons-select v-model:value="valueA" :options="optionsA" label="Label: " label-position="left" mt-2 />
<c-buttons-select v-model:value="valueA" :options="optionB" label="Options object: " />
</template>

View file

@ -1,10 +1,11 @@
<script setup lang="ts" generic="T extends unknown">
import _ from 'lodash';
import type { CLabelProps } from '../c-label/c-label.types';
import type { CButtonSelectOption } from './c-buttons-select.types';
const props = withDefaults(
defineProps<{
options?: CButtonSelectOption<T>[] | string[]
options?: CButtonSelectOption<T>[] | string[] | Record<string, T>
value?: T
size?: 'small' | 'medium' | 'large'
} & CLabelProps >(),
@ -20,14 +21,18 @@ const emits = defineEmits(['update:value']);
const { options: rawOptions, size } = toRefs(props);
const options = computed(() => {
const options = computed<CButtonSelectOption<T>[]>(() => {
if (_.isArray(rawOptions.value)) {
return rawOptions.value.map((option: string | CButtonSelectOption<T>) => {
if (typeof option === 'string') {
return { label: option, value: option };
}
return option;
});
}) as CButtonSelectOption<T>[];
}
return _.map(rawOptions.value, (value, label) => ({ label, value })) as CButtonSelectOption<T>[];
});
const value = useVModel(props, 'value', emits);

View file

@ -0,0 +1,18 @@
<script setup lang="ts">
const md = `
# IT Tools
## About
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, nisl quis
mollis blandit, nunc nisl aliquam nunc, vitae aliquam nisl nunc vitae nisl.
- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- Sed euismod, nisl quis mollis blandit, nunc nisl aliquam nunc, vitae aliquam nisl nunc vitae nisl.
[it-tools](https://it-tools.tech)
`;
</script>
<template>
<c-markdown :markdown="md" />
</template>

View file

@ -0,0 +1,21 @@
<script setup lang="ts">
import { marked } from 'marked';
import DomPurify from 'dompurify';
const props = withDefaults(defineProps<{ markdown?: string }>(), { markdown: '' });
const { markdown } = toRefs(props);
marked.use({
renderer: {
link(href, title, text) {
return `<a class="text-primary transition decoration-none hover:underline" href="${href}" target="_blank" rel="noopener">${text}</a>`;
},
},
});
const html = computed(() => DomPurify.sanitize(marked(markdown.value), { ADD_ATTR: ['target'] }));
</script>
<template>
<div v-html="html" />
</template>