Merge remote-tracking branch 'CorentinTh/main' into main

This commit is contained in:
marvin-j97 2023-06-25 16:38:20 +02:00
commit 9d8bb075b2
62 changed files with 1851 additions and 690 deletions

View file

@ -288,4 +288,4 @@
"watchWithFilter": true,
"whenever": true
}
}
}

View file

@ -37,7 +37,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View file

@ -12,7 +12,7 @@ jobs:
outputs:
should_run: ${{ steps.should_run.outputs.should_run }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: print latest_commit
run: echo ${{ github.sha }}

2
.nvmrc
View file

@ -1 +1 @@
16.14.2
16.20.1

292
auto-imports.d.ts vendored
View file

@ -91,9 +91,9 @@ declare global {
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
const toRaw: typeof import('vue')['toRaw']
const toReactive: typeof import('@vueuse/core')['toReactive']
const toRef: typeof import('@vueuse/core')['toRef']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('@vueuse/core')['toValue']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
@ -381,9 +381,293 @@ declare module 'vue' {
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
readonly toRef: UnwrapRef<typeof import('@vueuse/core')['toRef']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('@vueuse/core')['toValue']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
readonly tryOnMounted: UnwrapRef<typeof import('@vueuse/core')['tryOnMounted']>
readonly tryOnScopeDispose: UnwrapRef<typeof import('@vueuse/core')['tryOnScopeDispose']>
readonly tryOnUnmounted: UnwrapRef<typeof import('@vueuse/core')['tryOnUnmounted']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
readonly useBluetooth: UnwrapRef<typeof import('@vueuse/core')['useBluetooth']>
readonly useBreakpoints: UnwrapRef<typeof import('@vueuse/core')['useBreakpoints']>
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
readonly useDebounce: UnwrapRef<typeof import('@vueuse/core')['useDebounce']>
readonly useDebounceFn: UnwrapRef<typeof import('@vueuse/core')['useDebounceFn']>
readonly useDebouncedRefHistory: UnwrapRef<typeof import('@vueuse/core')['useDebouncedRefHistory']>
readonly useDeviceMotion: UnwrapRef<typeof import('@vueuse/core')['useDeviceMotion']>
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
readonly useDialog: UnwrapRef<typeof import('naive-ui')['useDialog']>
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
readonly useElementBounding: UnwrapRef<typeof import('@vueuse/core')['useElementBounding']>
readonly useElementByPoint: UnwrapRef<typeof import('@vueuse/core')['useElementByPoint']>
readonly useElementHover: UnwrapRef<typeof import('@vueuse/core')['useElementHover']>
readonly useElementSize: UnwrapRef<typeof import('@vueuse/core')['useElementSize']>
readonly useElementVisibility: UnwrapRef<typeof import('@vueuse/core')['useElementVisibility']>
readonly useEventBus: UnwrapRef<typeof import('@vueuse/core')['useEventBus']>
readonly useEventListener: UnwrapRef<typeof import('@vueuse/core')['useEventListener']>
readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
readonly useFileDialog: UnwrapRef<typeof import('@vueuse/core')['useFileDialog']>
readonly useFileSystemAccess: UnwrapRef<typeof import('@vueuse/core')['useFileSystemAccess']>
readonly useFocus: UnwrapRef<typeof import('@vueuse/core')['useFocus']>
readonly useFocusWithin: UnwrapRef<typeof import('@vueuse/core')['useFocusWithin']>
readonly useFps: UnwrapRef<typeof import('@vueuse/core')['useFps']>
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
readonly useIntersectionObserver: UnwrapRef<typeof import('@vueuse/core')['useIntersectionObserver']>
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
readonly useLoadingBar: UnwrapRef<typeof import('naive-ui')['useLoadingBar']>
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']>
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
readonly useMessage: UnwrapRef<typeof import('naive-ui')['useMessage']>
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
readonly useNotification: UnwrapRef<typeof import('naive-ui')['useNotification']>
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
readonly useWebWorkerFn: UnwrapRef<typeof import('@vueuse/core')['useWebWorkerFn']>
readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
readonly watchThrottled: UnwrapRef<typeof import('@vueuse/core')['watchThrottled']>
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
}
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly reactiveComputed: UnwrapRef<typeof import('@vueuse/core')['reactiveComputed']>
readonly reactiveOmit: UnwrapRef<typeof import('@vueuse/core')['reactiveOmit']>
readonly reactivePick: UnwrapRef<typeof import('@vueuse/core')['reactivePick']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>
readonly throttledRef: UnwrapRef<typeof import('@vueuse/core')['throttledRef']>
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>

13
components.d.ts vendored
View file

@ -60,19 +60,24 @@ declare module '@vue/runtime-core' {
HtmlEntities: typeof import('./src/tools/html-entities/html-entities.vue')['default']
HtmlWysiwygEditor: typeof import('./src/tools/html-wysiwyg-editor/html-wysiwyg-editor.vue')['default']
HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default']
'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default']
IconMdiArrowRightBottom: typeof import('~icons/mdi/arrow-right-bottom')['default']
IconMdiCamera: typeof import('~icons/mdi/camera')['default']
IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
IconMdiClose: typeof import('~icons/mdi/close')['default']
IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
IconMdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
IconMdiDownload: typeof import('~icons/mdi/download')['default']
IconMdiEye: typeof import('~icons/mdi/eye')['default']
IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default']
IconMdiMagnify: typeof import('~icons/mdi/magnify')['default']
IconMdiPause: typeof import('~icons/mdi/pause')['default']
IconMdiPlay: typeof import('~icons/mdi/play')['default']
IconMdiRecord: typeof import('~icons/mdi/record')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconMdiSearch: typeof import('~icons/mdi/search')['default']
IconMdiSearchRound: typeof import('~icons/mdi/search-round')['default']
IconMdiTea: typeof import('~icons/mdi/tea')['default']
IconMdiVideo: typeof import('~icons/mdi/video')['default']
InputCopyable: typeof import('./src/components/InputCopyable.vue')['default']
IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default']
@ -83,6 +88,7 @@ declare module '@vue/runtime-core' {
JsonDiff: typeof import('./src/tools/json-diff/json-diff.vue')['default']
JsonMinify: typeof import('./src/tools/json-minify/json-minify.vue')['default']
JsonToCsv: typeof import('./src/tools/json-to-csv/json-to-csv.vue')['default']
JsonToToml: typeof import('./src/tools/json-to-toml/json-to-toml.vue')['default']
JsonToYaml: typeof import('./src/tools/json-to-yaml-converter/json-to-yaml.vue')['default']
JsonViewer: typeof import('./src/tools/json-viewer/json-viewer.vue')['default']
JwtParser: typeof import('./src/tools/jwt-parser/jwt-parser.vue')['default']
@ -98,7 +104,6 @@ declare module '@vue/runtime-core' {
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
NAlert: typeof import('naive-ui')['NAlert']
NAutoComplete: typeof import('naive-ui')['NAutoComplete']
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCode: typeof import('naive-ui')['NCode']
@ -140,6 +145,7 @@ declare module '@vue/runtime-core' {
NUpload: typeof import('naive-ui')['NUpload']
NUploadDragger: typeof import('naive-ui')['NUploadDragger']
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']
PercentageCalculator: typeof import('./src/tools/percentage-calculator/percentage-calculator.vue')['default']
PhoneParserAndFormatter: typeof import('./src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue')['default']
QrCodeGenerator: typeof import('./src/tools/qr-code-generator/qr-code-generator.vue')['default']
@ -149,8 +155,6 @@ declare module '@vue/runtime-core' {
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default']
SearchBar: typeof import('./src/components/SearchBar.vue')['default']
SearchBarItem: typeof import('./src/components/SearchBarItem.vue')['default']
SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default']
SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default']
@ -161,6 +165,8 @@ declare module '@vue/runtime-core' {
TextToNatoAlphabet: typeof import('./src/tools/text-to-nato-alphabet/text-to-nato-alphabet.vue')['default']
TokenDisplay: typeof import('./src/tools/otp-code-generator-and-validator/token-display.vue')['default']
'TokenGenerator.tool': typeof import('./src/tools/token-generator/token-generator.tool.vue')['default']
TomlToJson: typeof import('./src/tools/toml-to-json/toml-to-json.vue')['default']
TomlToYaml: typeof import('./src/tools/toml-to-yaml/toml-to-yaml.vue')['default']
'Tool.layout': typeof import('./src/layouts/tool.layout.vue')['default']
ToolCard: typeof import('./src/components/ToolCard.vue')['default']
UrlEncoder: typeof import('./src/tools/url-encoder/url-encoder.vue')['default']
@ -170,5 +176,6 @@ declare module '@vue/runtime-core' {
UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default']
XmlFormatter: typeof import('./src/tools/xml-formatter/xml-formatter.vue')['default']
YamlToJson: typeof import('./src/tools/yaml-to-json-converter/yaml-to-json.vue')['default']
YamlToToml: typeof import('./src/tools/yaml-to-toml/yaml-to-toml.vue')['default']
}
}

View file

@ -42,13 +42,13 @@
"@vicons/material": "^0.12.0",
"@vicons/tabler": "^0.12.0",
"@vueuse/core": "^10.1.2",
"@vueuse/head": "^0.7.13",
"@vueuse/head": "^0.9.0",
"@vueuse/router": "^9.13.0",
"bcryptjs": "^2.4.3",
"change-case": "^4.1.2",
"colord": "^2.9.3",
"composerize-ts": "^0.6.2",
"country-code-lookup": "^0.0.23",
"country-code-lookup": "^0.1.0",
"cron-validator": "^1.3.1",
"cronstrue": "^2.26.0",
"crypto-js": "^4.1.1",
@ -56,6 +56,7 @@
"figue": "^1.2.0",
"fuse.js": "^6.6.2",
"highlight.js": "^11.7.0",
"iarna-toml-esm": "^3.0.5",
"json5": "^2.2.3",
"jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.10.28",
@ -73,6 +74,7 @@
"sql-formatter": "^8.2.0",
"ts-pattern": "^4.2.2",
"ua-parser-js": "^1.0.35",
"unplugin-auto-import": "^0.16.4",
"uuid": "^8.3.2",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2",
@ -88,7 +90,7 @@
"@rushstack/eslint-patch": "^1.2.0",
"@types/bcryptjs": "^2.4.2",
"@types/crypto-js": "^4.1.1",
"@types/jsdom": "^16.2.15",
"@types/jsdom": "^21.0.0",
"@types/lodash": "^4.14.192",
"@types/mime-types": "^2.1.1",
"@types/netmask": "^2.0.0",
@ -108,23 +110,21 @@
"@vue/runtime-dom": "^3.3.4",
"@vue/test-utils": "^2.3.2",
"@vue/tsconfig": "^0.1.3",
"c8": "^7.13.0",
"c8": "^8.0.0",
"consola": "^3.0.2",
"eslint": "^8.38.0",
"jsdom": "^19.0.0",
"less": "^4.1.3",
"prettier": "^2.8.7",
"start-server-and-test": "^1.15.4",
"typescript": "~4.5.5",
"unocss": "^0.50.8",
"unplugin-auto-import": "^0.15.2",
"unocss": "^0.53.0",
"unplugin-icons": "^0.16.1",
"unplugin-vue-components": "^0.24.1",
"unplugin-vue-components": "^0.25.0",
"vite": "^2.9.15",
"vite-plugin-md": "^0.12.4",
"vite-plugin-pwa": "^0.11.13",
"vite-svg-loader": "^3.6.0",
"vitest": "^0.13.1",
"vitest": "^0.32.0",
"vue-tsc": "^0.31.4",
"workbox-window": "^6.5.4",
"zx": "^7.2.1"

1102
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,4 @@
<script setup lang="ts">
import { ChevronRight } from '@vicons/tabler';
import { useStorage } from '@vueuse/core';
import { useThemeVars } from 'naive-ui';
import { RouterLink, useRoute } from 'vue-router';
@ -47,13 +46,15 @@ const themeVars = useThemeVars();
<template>
<div v-for="{ name, tools, isCollapsed } of menuOptions" :key="name">
<n-text tag="div" depth="3" class="category-name" @click="toggleCategoryCollapse({ name })">
<n-icon :component="ChevronRight" :class="{ rotated: isCollapsed }" size="16" />
<div ml-6px mt-12px flex cursor-pointer items-center op-60 @click="toggleCategoryCollapse({ name })">
<span :class="{ 'rotate-0': isCollapsed, 'rotate-90': !isCollapsed }" text-16px lh-1 op-50 transition-transform>
<icon-mdi-chevron-right />
</span>
<span>
<span ml-8px text-13px>
{{ name }}
</span>
</n-text>
</div>
<n-collapse-transition :show="!isCollapsed">
<div class="menu-wrapper">
@ -74,26 +75,6 @@ const themeVars = useThemeVars();
</template>
<style scoped lang="less">
.category-name {
font-size: 0.93em;
padding: 12px 0 0px 0;
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
.n-icon {
transition: transform ease 0.5s;
transform: rotate(90deg);
margin: 0 10px 0 7px;
opacity: 0.5;
&.rotated {
transform: rotate(0deg);
}
}
}
.menu-wrapper {
display: flex;
flex-direction: row;

View file

@ -6,6 +6,7 @@ import jsonHljs from 'highlight.js/lib/languages/json';
import sqlHljs from 'highlight.js/lib/languages/sql';
import xmlHljs from 'highlight.js/lib/languages/xml';
import yamlHljs from 'highlight.js/lib/languages/yaml';
import iniHljs from 'highlight.js/lib/languages/ini';
const props = withDefaults(
defineProps<{
@ -27,6 +28,7 @@ hljs.registerLanguage('json', jsonHljs);
hljs.registerLanguage('html', xmlHljs);
hljs.registerLanguage('xml', xmlHljs);
hljs.registerLanguage('yaml', yamlHljs);
hljs.registerLanguage('toml', iniHljs);
const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props);
const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) };

View file

@ -1,5 +1,4 @@
<script setup lang="ts">
import { Coffee } from '@vicons/tabler';
import { useHead } from '@vueuse/head';
useHead({ title: 'Page not found - IT Tools' });
@ -7,17 +6,19 @@ useHead({ title: 'Page not found - IT Tools' });
<template>
<div mt-20 flex flex-col items-center>
<n-icon :component="Coffee" size="100" depth="3" />
<span text-90px lh-1 op-50>
<icon-mdi:kettle-steam-outline />
</span>
<n-h1 m-0 mt-3>
<h1 m-0 mt-3>
404 Not Found
</n-h1>
<n-text mt-4 block depth="3">
</h1>
<div mt-4 op-60>
Sorry, this page does not seem to exist
</n-text>
<n-text mb-8 block depth="3">
</div>
<div mb-8 op-60>
Maybe the cache is doing tricky things, try force-refreshing?
</n-text>
</div>
<c-button to="/">
Back home

View file

@ -49,7 +49,7 @@ const { t } = useI18n();
</transition>
<div v-if="toolStore.newTools.length > 0">
<n-h3>{{ t('home.categories.newestTools') }}</n-h3>
<n-h3>{{ t('home.categories.newestTools', 'Newest tools') }}</n-h3>
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
<n-gi v-for="tool in toolStore.newTools" :key="tool.name">
<ToolCard :tool="tool" />

5
src/shims.d.ts vendored
View file

@ -14,3 +14,8 @@ declare module '~icons/*' {
const component: FunctionalComponent<SVGAttributes>;
export default component;
}
declare module 'iarna-toml-esm' {
export const parse: (toml: string) => any;
export const stringify: (obj: any) => string;
}

View file

@ -70,9 +70,9 @@ async function onUpload({ file: { file } }: { file: UploadFileInfo }) {
<div mb-2>
<n-icon size="35" :depth="3" :component="Upload" />
</div>
<n-text style="font-size: 14px">
<div op-60>
Click or drag a file to this area to upload
</n-text>
</div>
</n-upload-dragger>
</n-upload>

View file

@ -10,7 +10,7 @@ const dockerRun = ref(
);
const conversionResult = computed(() =>
withDefaultOnError(() => composerize(dockerRun.value), { yaml: '', messages: [] }),
withDefaultOnError(() => composerize(dockerRun.value.trim()), { yaml: '', messages: [] }),
);
const dockerCompose = computed(() => conversionResult.value.yaml);
const notImplemented = computed(() =>
@ -30,15 +30,16 @@ const { download } = useDownloadFileFromBase64({ source: dockerComposeBase64, fi
<template>
<div>
<n-form-item label="Your docker run command:" :show-feedback="false">
<n-input
v-model:value="dockerRun"
style="font-family: monospace"
type="textarea"
placeholder="Your docker run command to convert..."
rows="3"
/>
</n-form-item>
<c-input-text
v-model:value="dockerRun"
label="Your docker run command:"
style="font-family: monospace"
multiline
raw-text
monospace
placeholder="Your docker run command to convert..."
rows="3"
/>
<n-divider />

View file

@ -19,18 +19,13 @@ const decryptOutput = computed(() =>
<template>
<c-card title="Encrypt">
<div flex gap-3>
<n-form-item label="Your text:" :show-feedback="false" flex-1>
<n-input
v-model:value="cypherInput"
type="textarea"
placeholder="The string to cypher"
:autosize="{ minRows: 4 }"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
</n-form-item>
<c-input-text
v-model:value="cypherInput"
label="Your text:"
placeholder="The string to cypher"
rows="4"
multiline raw-text monospace autosize flex-1
/>
<div flex flex-1 flex-col gap-2>
<c-input-text v-model:value="cypherSecret" label="Your secret key:" clearable raw-text />
@ -42,34 +37,23 @@ const decryptOutput = computed(() =>
</n-form-item>
</div>
</div>
<n-form-item label="Your text encrypted:" :show-feedback="false" mt-5>
<n-input
:value="cypherOutput"
type="textarea"
placeholder="Your string hash"
:autosize="{ minRows: 2 }"
readonly
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
</n-form-item>
<c-input-text
label="Your text encrypted:"
:value="cypherOutput"
rows="3"
placeholder="Your string hash"
multiline monospace readonly autosize mt-5
/>
</c-card>
<c-card title="Decrypt">
<div flex gap-3>
<n-form-item label="Your encrypted text:" :show-feedback="false" flex-1>
<n-input
v-model:value="decryptInput"
type="textarea"
placeholder="The string to cypher"
:autosize="{ minRows: 4 }"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
</n-form-item>
<c-input-text
v-model:value="decryptInput"
label="Your encrypted text:"
placeholder="The string to cypher"
rows="4"
multiline raw-text monospace autosize flex-1
/>
<div flex flex-1 flex-col gap-2>
<c-input-text v-model:value="decryptSecret" label="Your secret key:" clearable raw-text />
@ -81,18 +65,12 @@ const decryptOutput = computed(() =>
</n-form-item>
</div>
</div>
<n-form-item label="Your decrypted text:" :show-feedback="false" mt-5>
<n-input
:value="decryptOutput"
type="textarea"
placeholder="Your string hash"
:autosize="{ minRows: 2 }"
readonly
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
</n-form-item>
<c-input-text
label="Your decrypted text:"
:value="decryptOutput"
placeholder="Your string hash"
rows="3"
multiline monospace readonly autosize mt-5
/>
</c-card>
</template>

View file

@ -25,11 +25,10 @@ const endAt = computed(() =>
<template>
<div>
<n-text depth="3" style="text-align: justify; width: 100%; display: inline-block">
<div text-justify op-70>
With a concrete example, if you wash 3 plates in 5 minutes and you have 500 plates to wash, it will take you 5
hours and 10 minutes to wash them all, and if you start now, you'll end
{{ endAt }}.
</n-text>
hours and 10 minutes to wash them all.
</div>
<n-divider />
<div flex gap-2>
<n-form-item label="Amount of element to consume" flex-1>

View file

@ -37,7 +37,7 @@ const hashText = (algo: AlgoNames, value: string) => formatWithEncoding(algos[al
<template>
<div>
<c-card>
<n-input v-model:value="clearText" type="textarea" placeholder="Your string to hash..." rows="3" />
<c-input-text v-model:value="clearText" multiline raw-text placeholder="Your string to hash..." rows="3" autosize autofocus label="Your text to hash:" />
<n-divider />

View file

@ -46,13 +46,10 @@ const { copy } = useCopy({ source: hmac });
</script>
<template>
<div>
<n-form-item label="Plain text to compute the hash">
<n-input v-model:value="plainText" type="textarea" placeholder="Enter the text to compute the hash..." />
</n-form-item>
<n-form-item label="Secret key">
<n-input v-model:value="secret" placeholder="Enter the secret key..." />
</n-form-item>
<div flex flex-col gap-4>
<c-input-text v-model:value="plainText" multiline raw-text placeholder="Plain text to compute the hash..." rows="3" autosize autofocus label="Plain text to compute the hash" />
<c-input-text v-model:value="secret" raw-text placeholder="Enter the secret key..." label="Secret key" clearable />
<div flex gap-2>
<n-form-item label="Hashing function" flex-1>
<n-select
@ -86,9 +83,7 @@ const { copy } = useCopy({ source: hmac });
/>
</n-form-item>
</div>
<n-form-item label="HMAC of your text">
<n-input readonly :value="hmac" type="textarea" placeholder="The result of the HMAC..." />
</n-form-item>
<input-copyable v-model:value="hmac" type="textarea" placeholder="The result of the HMAC..." label="HMAC of your text" />
<div flex justify-center>
<c-button @click="copy()">
Copy HMAC

View file

@ -7,7 +7,7 @@ const escapeInput = ref('<title>IT Tool</title>');
const escapeOutput = computed(() => escape(escapeInput.value));
const { copy: copyEscaped } = useCopy({ source: escapeOutput });
const unescapeInput = ref('&lt;title&gt;IT Tool&lt;/title');
const unescapeInput = ref('&lt;title&gt;IT Tool&lt;/title&gt;');
const unescapeOutput = computed(() => unescape(unescapeInput.value));
const { copy: copyUnescaped } = useCopy({ source: unescapeOutput });
</script>
@ -15,21 +15,24 @@ const { copy: copyUnescaped } = useCopy({ source: unescapeOutput });
<template>
<c-card title="Escape html entities">
<n-form-item label="Your string :">
<n-input
<c-input-text
v-model:value="escapeInput"
type="textarea"
multiline
placeholder="The string to escape"
:autosize="{ minRows: 2 }"
rows="3"
autosize
raw-text
/>
</n-form-item>
<n-form-item label="Your string escaped :">
<n-input
type="textarea"
<c-input-text
multiline
readonly
placeholder="Your string escaped"
:value="escapeOutput"
:autosize="{ minRows: 2 }"
rows="3"
autosize
/>
</n-form-item>
@ -41,21 +44,24 @@ const { copy: copyUnescaped } = useCopy({ source: unescapeOutput });
</c-card>
<c-card title="Unescape html entities">
<n-form-item label="Your escaped string :">
<n-input
<c-input-text
v-model:value="unescapeInput"
type="textarea"
multiline
placeholder="The string to unescape"
:autosize="{ minRows: 2 }"
rows="3"
autosize
raw-text
/>
</n-form-item>
<n-form-item label="Your string unescaped :">
<n-input
<c-input-text
:value="unescapeOutput"
type="textarea"
multiline
readonly
placeholder="Your string unescaped"
:autosize="{ minRows: 2 }"
rows="3"
autosize
/>
</n-form-item>

View file

@ -1,5 +1,4 @@
<script setup lang="ts">
import { SearchRound } from '@vicons/material';
import { codesByCategories } from './http-status-codes.constants';
import { useFuzzySearch } from '@/composable/fuzzySearch';
@ -24,33 +23,24 @@ const codesByCategoryFiltered = computed(() => {
<template>
<div>
<n-form-item :show-label="false">
<n-input
v-model:value="search"
placeholder="Search http status..."
size="large"
autofocus
mb-10
autocomplete="off"
autocorrect="off"
autocapitalize="off"
>
<template #prefix>
<n-icon :component="SearchRound" />
</template>
</n-input>
</n-form-item>
<c-input-text
v-model:value="search"
placeholder="Search http status..."
autofocus raw-text mb-10
/>
<div v-for="{ codes, category } of codesByCategoryFiltered" :key="category" mb-8>
<n-h2> {{ category }} </n-h2>
<div mb-2 text-xl>
{{ category }}
</div>
<c-card v-for="{ code, description, name, type } of codes" :key="code" mb-2>
<n-text strong block text-lg>
<div text-lg font-bold>
{{ code }} {{ name }}
</n-text>
<n-text block depth="3">
</div>
<div op-70>
{{ description }} {{ type !== 'HTTP' ? `For ${type}.` : '' }}
</n-text>
</div>
</c-card>
</div>
</div>

View file

@ -1,6 +1,11 @@
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 passwordStrengthAnalyser } from './password-strength-analyser';
import { tool as yamlToToml } from './yaml-to-toml';
import { tool as jsonToToml } from './json-to-toml';
import { tool as tomlToYaml } from './toml-to-yaml';
import { tool as tomlToJson } from './toml-to-json';
import { tool as jsonToCsv } from './json-to-csv';
import { tool as cameraRecorder } from './camera-recorder';
import { tool as listConverter } from './list-converter';
@ -64,7 +69,7 @@ import { tool as xmlFormatter } from './xml-formatter';
export const toolsByCategory: ToolCategory[] = [
{
name: 'Crypto',
components: [tokenGenerator, hashText, bcrypt, uuidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator],
components: [tokenGenerator, hashText, bcrypt, uuidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser],
},
{
name: 'Converter',
@ -78,8 +83,12 @@ export const toolsByCategory: ToolCategory[] = [
caseConverter,
textToNatoAlphabet,
yamlToJson,
yamlToToml,
jsonToYaml,
jsonToToml,
listConverter,
tomlToJson,
tomlToYaml,
],
},
{

View file

@ -1,11 +1,8 @@
<script setup lang="ts">
import InputCopyable from '../../components/InputCopyable.vue';
import { convertBase } from './integer-base-converter.model';
import { useStyleStore } from '@/stores/style.store';
import { getErrorMessageIfThrows } from '@/utils/error';
const styleStore = useStyleStore();
const inputProps = {
'labelPosition': 'left',
'labelWidth': '170px',
@ -37,31 +34,11 @@ const error = computed(() =>
<template>
<div>
<c-card>
<div v-if="styleStore.isSmallScreen">
<n-input-group>
<n-input-group-label style="flex: 0 0 120px">
Input number:
</n-input-group-label>
<n-input v-model:value="input" w-full :status="error ? 'error' : undefined" />
</n-input-group>
<n-input-group>
<n-input-group-label style="flex: 0 0 120px">
Input base:
</n-input-group-label>
<n-input-number v-model:value="inputBase" max="64" min="2" w-full />
</n-input-group>
</div>
<c-input-text v-model:value="input" label="Input number" placeholder="Put your number here (ex: 42)" label-position="left" label-width="110px" mb-2 label-align="right" />
<n-input-group v-else>
<n-input-group-label style="flex: 0 0 120px">
Input number:
</n-input-group-label>
<n-input v-model:value="input" :status="error ? 'error' : undefined" />
<n-input-group-label style="flex: 0 0 120px">
Input base:
</n-input-group-label>
<n-input-number v-model:value="inputBase" max="64" min="2" />
</n-input-group>
<n-form-item label="Input base" label-placement="left" label-width="110" :show-feedback="false">
<n-input-number v-model:value="inputBase" max="64" min="2" placeholder="Put your input base here (ex: 10)" w-full />
</n-form-item>
<n-alert v-if="error" style="margin-top: 25px" type="error">
{{ error }}

View file

@ -105,10 +105,10 @@ function onSwitchStartEndClicked() {
title="Invalid combination of start and end IPv4 address"
type="error"
>
<n-text depth="3" my-3 block>
<div my-3 op-70>
The end IPv4 address is lower than the start IPv4 address. This is not valid and no result could be calculated.
In the most cases the solution to solve this problem is to change start and end address.
</n-text>
</div>
<c-button @click="onSwitchStartEndClicked">
<n-icon mr-2 :component="Exchange" depth="3" size="22" />

View file

@ -14,10 +14,8 @@ const testId = computed(() => _.kebabCase(label.value));
<template>
<tr>
<td>
<n-text strong>
{{ label }}
</n-text>
<td font-bold>
{{ label }}
</td>
<td :data-test-id="`${testId}.old`">
<SpanCopyable :value="oldValue" class="monospace" />

View file

@ -96,16 +96,14 @@ function switchToBlock({ count = 1 }: { count?: number }) {
<n-table>
<tbody>
<tr v-for="{ getValue, label, undefinedFallback } in sections" :key="label">
<td>
<n-text strong>
{{ label }}
</n-text>
<td font-bold>
{{ label }}
</td>
<td>
<SpanCopyable v-if="getValue(networkInfo)" :value="getValue(networkInfo)" />
<n-text v-else depth="3">
<span v-else op-70>
{{ undefinedFallback }}
</n-text>
</span>
</td>
</tr>
</tbody>

View file

@ -26,9 +26,9 @@ const showResults = computed(() => !_.isUndefined(leftJson.value) && !_.isUndefi
</div>
<c-card data-test-id="diff-result">
<n-text v-if="jsonAreTheSame" depth="3" block text-center italic>
<div v-if="jsonAreTheSame" text-center op-70>
The provided JSONs are the same
</n-text>
</div>
<DiffRootViewer v-else :diff="result" />
</c-card>
</div>

View file

@ -0,0 +1,12 @@
import { Braces } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'JSON to TOML',
path: '/json-to-toml',
description: 'Parse and convert JSON to TOML.',
keywords: ['json', 'parse', 'toml', 'convert', 'transform'],
component: () => import('./json-to-toml.vue'),
icon: Braces,
createdAt: new Date('2023-06-23'),
});

View file

@ -0,0 +1,39 @@
import { expect, test } from '@playwright/test';
test.describe('Tool - JSON to TOML', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/json-to-toml');
});
test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('JSON to TOML - IT Tools');
});
test('JSON is parsed and outputs clean TOML', async ({ page }) => {
await page.getByTestId('input').fill(`
{
"foo": "bar",
"list": {
"name": "item",
"another": {
"key": "value"
}
}
}
`.trim());
const generatedJson = await page.getByTestId('area-content').innerText();
expect(generatedJson.trim()).toEqual(
`
foo = "bar"
[list]
name = "item"
[list.another]
key = "value"
`.trim(),
);
});
});

View file

@ -0,0 +1,28 @@
<script setup lang="ts">
import { stringify as stringifyToml } from 'iarna-toml-esm';
import JSON5 from 'json5';
import { withDefaultOnError } from '../../utils/defaults';
import type { UseValidationRule } from '@/composable/validation';
const convertJsonToToml = (value: string) => [stringifyToml(JSON5.parse(value))].flat().join('\n').trim();
const transformer = (value: string) => value.trim() === '' ? '' : withDefaultOnError(() => convertJsonToToml(value), '');
const rules: UseValidationRule<string>[] = [
{
validator: (v: string) => v === '' || JSON5.parse(v),
message: 'Provided JSON is not valid.',
},
];
</script>
<template>
<format-transformer
input-label="Your JSON"
input-placeholder="Paste your JSON here..."
output-label="TOML from your JSON"
output-language="toml"
:input-validation-rules="rules"
:transformer="transformer"
/>
</template>

View file

@ -41,16 +41,17 @@ const rawJsonValidation = useValidation({
:feedback="rawJsonValidation.message"
:validation-status="rawJsonValidation.status"
>
<n-input
<c-input-text
ref="inputElement"
v-model:value="rawJson"
placeholder="Paste your raw JSON here..."
type="textarea"
rows="20"
multiline
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
monospace
/>
</n-form-item>
<n-form-item label="Prettified version of your JSON">

View file

@ -30,9 +30,7 @@ const validation = useValidation({
<template>
<c-card>
<n-form-item label="JWT to decode" :feedback="validation.message" :validation-status="validation.status">
<n-input v-model:value="rawJwt" type="textarea" placeholder="Put your token here..." rows="5" />
</n-form-item>
<c-input-text v-model:value="rawJwt" label="JWT to decode" :validation="validation" placeholder="Put your token here..." rows="5" multiline raw-text autofocus mb-3 />
<n-table v-if="validation.isValid">
<tbody>
@ -42,18 +40,18 @@ const validation = useValidation({
</th>
<tr v-for="{ claim, claimDescription, friendlyValue, value } in decodedJWT[section.key]" :key="claim + value">
<td class="claims">
<n-text strong>
<span font-bold>
{{ claim }}
</n-text>
<n-text v-if="claimDescription" depth="3" ml-2>
</span>
<span v-if="claimDescription" ml-2 op-70>
({{ claimDescription }})
</n-text>
</span>
</td>
<td>
<n-text>{{ value }}</n-text>
<n-text v-if="friendlyValue" ml-2 depth="3">
<span>{{ value }}</span>
<span v-if="friendlyValue" ml-2 op-70>
({{ friendlyValue }})
</n-text>
</span>
</td>
</tr>
</template>

View file

@ -54,13 +54,13 @@ const fields = computed(() => {
<template>
<div>
<c-card style="text-align: center; padding: 40px 0; margin-bottom: 26px">
<n-h2 v-if="event">
<c-card mb-5 text-center important:py-12>
<div v-if="event" mb-2 text-3xl>
{{ event.key }}
</n-h2>
<n-text strong depth="3">
</div>
<span lh-1 op-70>
Press the key on your keyboard you want to get info about this key
</n-text>
</span>
</c-card>
<n-input-group v-for="({ label, value, placeholder }, i) of fields" :key="i" style="margin-bottom: 5px">

View file

@ -39,7 +39,7 @@ const { copy } = useCopy({ source: loremIpsumText, text: 'Lorem ipsum copied to
<n-switch v-model:value="asHTML" />
</n-form-item>
<n-input :value="loremIpsumText" type="textarea" placeholder="Your lorem ipsum..." readonly autosize mt-5 />
<c-input-text :value="loremIpsumText" multiline placeholder="Your lorem ipsum..." readonly mt-5 rows="5" />
<div mt-5 flex justify-center>
<c-button autofocus @click="copy">

View file

@ -10,16 +10,12 @@ const result = computed(() => withDefaultOnError(() => evaluate(expression.value
<template>
<div>
<n-input
<c-input-text
v-model:value="expression"
rows="1"
type="textarea"
multiline
placeholder="Your math expression (ex: 2*sqrt(6) )..."
size="large"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
raw-text
/>
<c-card v-if="result !== ''" title="Result " mt-5>

View file

@ -0,0 +1,12 @@
import { defineTool } from '../tool';
import PasswordIcon from '~icons/mdi/form-textbox-password';
export const tool = defineTool({
name: 'Password strength analyser',
path: '/password-strength-analyser',
description: 'Discover the strength of your password with this client side only password strength analyser and crack time estimation tool.',
keywords: ['password', 'strength', 'analyser', 'and', 'crack', 'time', 'estimation', 'brute', 'force', 'attack', 'entropy', 'cracking', 'hash', 'hashing', 'algorithm', 'algorithms', 'md5', 'sha1', 'sha256', 'sha512', 'bcrypt', 'scrypt', 'argon2', 'argon2id', 'argon2i', 'argon2d'],
component: () => import('./password-strength-analyser.vue'),
icon: PasswordIcon,
createdAt: new Date('2023-06-24'),
});

View file

@ -0,0 +1,19 @@
import { expect, test } from '@playwright/test';
test.describe('Tool - Password strength analyser', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/password-strength-analyser');
});
test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('Password strength analyser - IT Tools');
});
test('Computes the brute force attack time of a password', async ({ page }) => {
await page.getByTestId('password-input').fill('ABCabc123!@#');
const crackDuration = await page.getByTestId('crack-duration').textContent();
expect(crackDuration).toEqual('15,091 milleniums, 3 centurys');
});
});

View file

@ -0,0 +1,31 @@
import { describe, expect, it } from 'vitest';
import { getCharsetLength } from './password-strength-analyser.service';
describe('password-strength-analyser-and-crack-time-estimation', () => {
describe('getCharsetLength', () => {
describe('computes the charset length of a given password', () => {
it('the charset length is 26 when the password is only lowercase characters', () => {
expect(getCharsetLength({ password: 'abcdefghijklmnopqrstuvwxyz' })).toBe(26);
});
it('the charset length is 26 when the password is only uppercase characters', () => {
expect(getCharsetLength({ password: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' })).toBe(26);
});
it('the charset length is 10 when the password is only digits', () => {
expect(getCharsetLength({ password: '0123456789' })).toBe(10);
});
it('the charset length is 32 when the password is only special characters', () => {
expect(getCharsetLength({ password: '-_(' })).toBe(32);
});
it('the charset length is 0 when the password is empty', () => {
expect(getCharsetLength({ password: '' })).toBe(0);
});
it('the charset length is 36 when the password is lowercase characters and digits', () => {
expect(getCharsetLength({ password: 'abcdefghijklmnopqrstuvwxyz0123456789' })).toBe(36);
});
it('the charset length is 95 when the password is lowercase characters, uppercase characters, digits and special characters', () => {
expect(getCharsetLength({ password: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_(' })).toBe(94);
});
});
});
});

View file

@ -0,0 +1,96 @@
import _ from 'lodash';
export { getPasswordCrackTimeEstimation, getCharsetLength };
function prettifyExponentialNotation(exponentialNotation: number) {
const [base, exponent] = exponentialNotation.toString().split('e');
const baseAsNumber = parseFloat(base);
const prettyBase = baseAsNumber % 1 === 0 ? baseAsNumber.toLocaleString() : baseAsNumber.toFixed(2);
return exponent ? `${prettyBase}e${exponent}` : prettyBase;
}
function getHumanFriendlyDuration({ seconds }: { seconds: number }) {
if (seconds <= 0.001) {
return 'Instantly';
}
if (seconds <= 1) {
return 'Less than a second';
}
const timeUnits = [
{ unit: 'millenium', secondsInUnit: 31536000000, format: prettifyExponentialNotation },
{ unit: 'century', secondsInUnit: 3153600000 },
{ unit: 'decade', secondsInUnit: 315360000 },
{ unit: 'year', secondsInUnit: 31536000 },
{ unit: 'month', secondsInUnit: 2592000 },
{ unit: 'week', secondsInUnit: 604800 },
{ unit: 'day', secondsInUnit: 86400 },
{ unit: 'hour', secondsInUnit: 3600 },
{ unit: 'minute', secondsInUnit: 60 },
{ unit: 'second', secondsInUnit: 1 },
];
return _.chain(timeUnits)
.map(({ unit, secondsInUnit, format = _.identity }) => {
const quantity = Math.floor(seconds / secondsInUnit);
seconds %= secondsInUnit;
if (quantity <= 0) {
return undefined;
}
const formattedQuantity = format(quantity);
return `${formattedQuantity} ${unit}${quantity > 1 ? 's' : ''}`;
})
.compact()
.take(2)
.join(', ')
.value();
}
function getPasswordCrackTimeEstimation({ password, guessesPerSecond = 1e9 }: { password: string; guessesPerSecond?: number }) {
const charsetLength = getCharsetLength({ password });
const passwordLength = password.length;
const entropy = password === '' ? 0 : Math.log2(charsetLength) * passwordLength;
const secondsToCrack = 2 ** entropy / guessesPerSecond;
const crackDurationFormatted = getHumanFriendlyDuration({ seconds: secondsToCrack });
const score = Math.min(entropy / 128, 1);
return {
entropy,
charsetLength,
passwordLength,
crackDurationFormatted,
secondsToCrack,
score,
};
}
function getCharsetLength({ password }: { password: string }) {
const hasLowercase = /[a-z]/.test(password);
const hasUppercase = /[A-Z]/.test(password);
const hasDigits = /\d/.test(password);
const hasSpecialChars = /\W|_/.test(password);
let charsetLength = 0;
if (hasLowercase) {
charsetLength += 26;
}
if (hasUppercase) {
charsetLength += 26;
}
if (hasDigits) {
charsetLength += 10;
}
if (hasSpecialChars) {
charsetLength += 32;
}
return charsetLength;
}

View file

@ -0,0 +1,62 @@
<script setup lang="ts">
import { getPasswordCrackTimeEstimation } from './password-strength-analyser.service';
const password = ref('');
const crackTimeEstimation = computed(() => getPasswordCrackTimeEstimation({ password: password.value }));
const details = computed(() => [
{
label: 'Password length:',
value: crackTimeEstimation.value.passwordLength,
},
{
label: 'Entropy:',
value: Math.round(crackTimeEstimation.value.entropy * 100) / 100,
},
{
label: 'Character set size:',
value: crackTimeEstimation.value.charsetLength,
},
{
label: 'Score:',
value: `${Math.round(crackTimeEstimation.value.score * 100)} / 100`,
},
]);
</script>
<template>
<div flex flex-col gap-3>
<c-input-text
v-model:value="password"
type="password"
placeholder="Enter a password..."
clearable
autofocus
raw-text
test-id="password-input"
/>
<c-card text-center>
<div op-60>
Duration to crack this password with brute force
</div>
<div text-2xl data-test-id="crack-duration">
{{ crackTimeEstimation.crackDurationFormatted }}
</div>
</c-card>
<c-card>
<div v-for="({ label, value }) of details" :key="label" flex gap-3>
<div flex-1 text-right op-60>
{{ label }}
</div>
<div flex-1 text-left>
{{ value }}
</div>
</div>
</c-card>
<div op-70>
<span font-bold>Note: </span>
The computed strength is based on the time it would take to crack the password using a brute force approach, it does not take into account the possibility of a dictionary attack.
</div>
</div>
</template>

View file

@ -100,16 +100,14 @@ const countriesOptions = getCountries().map(code => ({
<n-table v-if="parsedDetails">
<tbody>
<tr v-for="{ label, value } in parsedDetails" :key="label">
<td>
<n-text strong>
{{ label }}
</n-text>
<td font-bold>
{{ label }}
</td>
<td>
<span-copyable v-if="value" :value="value" />
<n-text v-else depth="3" italic>
<span v-else op-70>
Unknown
</n-text>
</span>
</td>
</tr>
</tbody>

View file

@ -27,15 +27,18 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
<c-card>
<n-grid x-gap="12" y-gap="12" cols="1 600:3">
<n-gi span="2">
<c-input-text
v-model:value="text"
label-position="left"
label-width="130px"
label-align="right"
label="Text:"
multiline
rows="1"
placeholder="Your link or text..."
mb-6
/>
<n-form label-width="130" label-placement="left">
<n-form-item label="Text:">
<n-input
v-model:value="text"
type="textarea"
:autosize="{ minRows: 1 }"
placeholder="Your link or text..."
/>
</n-form-item>
<n-form-item label="Foreground color:">
<n-color-picker v-model:value="foreground" :modes="['hex']" />
</n-form-item>

View file

@ -10,18 +10,9 @@ const { copy } = useCopy({ source: slug, text: 'Slug copied to clipboard' });
<template>
<div>
<n-form-item label="Your string to slugify">
<n-input v-model:value="input" type="textarea" placeholder="Put your string here (ex: My file path)" />
</n-form-item>
<c-input-text v-model:value="input" multiline placeholder="Put your string here (ex: My file path)" label="Your string to slugify" autofocus raw-text mb-5 />
<n-form-item label="Your slug">
<n-input
:value="slug"
type="textarea"
readonly
placeholder="You slug will be generated here (ex: my-file-path)"
/>
</n-form-item>
<c-input-text :value="slug" multiline readonly placeholder="You slug will be generated here (ex: my-file-path)" label="Your slug" mb-5 />
<div flex justify-center>
<c-button :disabled="slug.length === 0" @click="copy">

View file

@ -7,7 +7,7 @@ const text = ref('');
<template>
<c-card>
<n-input v-model:value="text" type="textarea" placeholder="Your text..." rows="5" />
<c-input-text v-model:value="text" multiline placeholder="Your text..." rows="5" />
<div mt-5 flex>
<n-statistic label="Character count" :value="text.length" flex-1 />

View file

@ -18,9 +18,9 @@ const { copy } = useCopy({ source: natoText, text: 'NATO alphabet string copied.
/>
<div v-if="natoText">
<n-text mb-1 block>
<div mb-2>
Your text in NATO phonetic alphabet
</n-text>
</div>
<c-card>
{{ natoText }}
</c-card>

View file

@ -55,17 +55,14 @@ const { copy } = useCopy({ source: token, text: 'Token copied to the clipboard'
<n-slider v-model:value="length" :step="1" :min="1" :max="512" />
</n-form-item>
<n-input
<c-input-text
v-model:value="token"
style="text-align: center"
type="textarea"
multiline
placeholder="The token..."
:autosize="{ minRows: 1 }"
readonly
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
rows="3"
autosize
class="token-display"
/>
<div mt-5 flex justify-center gap-3>
@ -79,3 +76,11 @@ const { copy } = useCopy({ source: token, text: 'Token copied to the clipboard'
</c-card>
</div>
</template>
<style scoped lang="less">
::v-deep(.token-display) {
textarea {
text-align: center;
}
}
</style>

View file

@ -0,0 +1,13 @@
import { defineTool } from '../tool';
import BracketIcon from '~icons/mdi/code-brackets';
export const tool = defineTool({
name: 'TOML to JSON',
path: '/toml-to-json',
description: 'Parse and convert TOML to JSON.',
keywords: ['toml', 'json', 'convert', 'online', 'transform', 'parser'],
component: () => import('./toml-to-json.vue'),
icon: BracketIcon,
createdAt: new Date('2023-06-23'),
});

View file

@ -0,0 +1,40 @@
import { expect, test } from '@playwright/test';
test.describe('Tool - TOML to JSON', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/toml-to-json');
});
test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('TOML to JSON - IT Tools');
});
test('TOML is parsed and outputs clean JSON', async ({ page }) => {
await page.getByTestId('input').fill(`
foo = "bar"
# This is a comment
[list]
name = "item"
[list.another]
key = "value"
`.trim());
const generatedJson = await page.getByTestId('area-content').innerText();
expect(generatedJson.trim()).toEqual(
`
{
"foo": "bar",
"list": {
"name": "item",
"another": {
"key": "value"
}
}
}
`.trim(),
);
});
});

View file

@ -0,0 +1,26 @@
<script setup lang="ts">
import { parse as parseToml } from 'iarna-toml-esm';
import { withDefaultOnError } from '../../utils/defaults';
import { isValidToml } from './toml.services';
import type { UseValidationRule } from '@/composable/validation';
const transformer = (value: string) => value === '' ? '' : withDefaultOnError(() => JSON.stringify(parseToml(value), null, 3), '');
const rules: UseValidationRule<string>[] = [
{
validator: isValidToml,
message: 'Provided TOML is not valid.',
},
];
</script>
<template>
<format-transformer
input-label="Your TOML"
input-placeholder="Paste your TOML here..."
output-label="JSON from your TOML"
output-language="json"
:input-validation-rules="rules"
:transformer="transformer"
/>
</template>

View file

@ -0,0 +1,8 @@
import { parse as parseToml } from 'iarna-toml-esm';
import { isNotThrowing } from '../../utils/boolean';
export { isValidToml };
function isValidToml(toml: string): boolean {
return isNotThrowing(() => parseToml(toml));
}

View file

@ -0,0 +1,12 @@
import { defineTool } from '../tool';
import BracketIcon from '~icons/mdi/code-brackets';
export const tool = defineTool({
name: 'TOML to YAML',
path: '/toml-to-yaml',
description: 'Parse and convert TOML to YAML.',
keywords: ['toml', 'yaml', 'convert', 'online', 'transform', 'parse'],
component: () => import('./toml-to-yaml.vue'),
icon: BracketIcon,
createdAt: new Date('2023-06-23'),
});

View file

@ -0,0 +1,36 @@
import { expect, test } from '@playwright/test';
test.describe('Tool - TOML to YAML', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/toml-to-yaml');
});
test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('TOML to YAML - IT Tools');
});
test('TOML is parsed and outputs clean YAML', async ({ page }) => {
await page.getByTestId('input').fill(`
foo = "bar"
# This is a comment
[list]
name = "item"
[list.another]
key = "value"
`.trim());
const generatedJson = await page.getByTestId('area-content').innerText();
expect(generatedJson.trim()).toEqual(
`
foo: bar
list:
name: item
another:
key: value
`.trim(),
);
});
});

View file

@ -0,0 +1,27 @@
<script setup lang="ts">
import { parse as parseToml } from 'iarna-toml-esm';
import { stringify as stringifyToYaml } from 'yaml';
import { withDefaultOnError } from '../../utils/defaults';
import { isValidToml } from '../toml-to-json/toml.services';
import type { UseValidationRule } from '@/composable/validation';
const transformer = (value: string) => value.trim() === '' ? '' : withDefaultOnError(() => stringifyToYaml(parseToml(value)), '');
const rules: UseValidationRule<string>[] = [
{
validator: isValidToml,
message: 'Provided TOML is not valid.',
},
];
</script>
<template>
<format-transformer
input-label="Your TOML"
input-placeholder="Paste your TOML here..."
output-label="YAML from your TOML"
output-language="yaml"
:input-validation-rules="rules"
:transformer="transformer"
/>
</template>

View file

@ -37,28 +37,27 @@ const { copy: copyDecoded } = useCopy({ source: decodeOutput, text: 'Decoded str
<template>
<c-card title="Encode">
<n-form-item
<c-input-text
v-model:value="encodeInput"
label="Your string :"
:feedback="encodedValidation.message"
:validation-status="encodedValidation.status"
>
<n-input
v-model:value="encodeInput"
type="textarea"
placeholder="The string to encode"
:autosize="{ minRows: 2 }"
/>
</n-form-item>
:validation="encodedValidation"
multiline
autosize
placeholder="The string to encode"
rows="2"
mb-3
/>
<n-form-item label="Your string encoded :">
<n-input
:value="encodeOutput"
type="textarea"
readonly
placeholder="Your string encoded"
:autosize="{ minRows: 2 }"
/>
</n-form-item>
<c-input-text
label="Your string encoded :"
:value="encodeOutput"
multiline
autosize
readonly
placeholder="Your string encoded"
rows="2"
mb-3
/>
<div flex justify-center>
<c-button @click="copyEncoded">
@ -67,28 +66,27 @@ const { copy: copyDecoded } = useCopy({ source: decodeOutput, text: 'Decoded str
</div>
</c-card>
<c-card title="Decode">
<n-form-item
<c-input-text
v-model:value="decodeInput"
label="Your encoded string :"
:feedback="decodeValidation.message"
:validation-status="decodeValidation.status"
>
<n-input
v-model:value="decodeInput"
type="textarea"
placeholder="The string to decode"
:autosize="{ minRows: 2 }"
/>
</n-form-item>
:validation="decodeValidation"
multiline
autosize
placeholder="The string to decode"
rows="2"
mb-3
/>
<n-form-item label="Your string decoded :">
<n-input
:value="decodeOutput"
type="textarea"
readonly
placeholder="Your string decoded"
:autosize="{ minRows: 2 }"
/>
</n-form-item>
<c-input-text
label="Your string decoded :"
:value="decodeOutput"
multiline
autosize
readonly
placeholder="Your string decoded"
rows="2"
mb-3
/>
<div flex justify-center>
<c-button @click="copyDecoded">

View file

@ -103,15 +103,18 @@ const sections: UserAgentResultSection[] = [
<template>
<div>
<n-form-item label="User agent string">
<n-input
v-model:value="ua"
type="textarea"
placeholder="Put your user-agent here..."
clearable
:autosize="{ minRows: 2 }"
/>
</n-form-item>
<c-input-text
v-model:value="ua"
label="User agent string"
multiline
placeholder="Put your user-agent here..."
clearable
raw-text
rows="2"
autosize
monospace
mb-3
/>
<UserAgentResultCards :user-agent-info="userAgentInfo" :sections="sections" />
</div>

View file

@ -37,7 +37,7 @@ const { userAgentInfo, sections } = toRefs(props);
</div>
<div flex flex-col>
<span v-for="{ label, getValue, undefinedFallback } in content" :key="label">
<n-text v-if="getValue(userAgentInfo) === undefined" depth="3">{{ undefinedFallback }}</n-text>
<span v-if="getValue(userAgentInfo) === undefined" op-70>{{ undefinedFallback }}</span>
</span>
</div>
</c-card>

View file

@ -19,18 +19,18 @@ const { copy } = useCopy({ source: uuids, text: 'UUIDs copied to the clipboard'
<n-input-number v-model:value="count" :min="1" :max="50" placeholder="UUID quantity" />
</div>
<n-input
<c-input-text
style="text-align: center; font-family: monospace"
:value="uuids"
type="textarea"
multiline
placeholder="Your uuids"
:autosize="{ minRows: 1 }"
autosize
rows="1"
readonly
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
raw-text
monospace
my-3
class="uuid-display"
/>
<div flex justify-center gap-3>
@ -43,3 +43,11 @@ const { copy } = useCopy({ source: uuids, text: 'UUIDs copied to the clipboard'
</div>
</div>
</template>
<style scoped lang="less">
::v-deep(.uuid-display) {
textarea {
text-align: center;
}
}
</style>

View file

@ -0,0 +1,12 @@
import { AlignJustified } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'YAML to TOML',
path: '/yaml-to-toml',
description: 'Parse and convert YAML to TOML.',
keywords: ['yaml', 'to', 'toml', 'convert', 'transform'],
component: () => import('./yaml-to-toml.vue'),
icon: AlignJustified,
createdAt: new Date('2023-06-23'),
});

View file

@ -0,0 +1,37 @@
import { expect, test } from '@playwright/test';
test.describe('Tool - YAML to TOML', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/yaml-to-toml');
});
test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('YAML to TOML - IT Tools');
});
test('JSON is parsed and outputs clean TOML', async ({ page }) => {
await page.getByTestId('input').fill(`
foo: bar
list:
name: item
another:
key: value
number: 1
`.trim());
const generatedJson = await page.getByTestId('area-content').innerText();
expect(generatedJson.trim()).toEqual(
`
foo = "bar"
[list]
name = "item"
[list.another]
key = "value"
number = 1
`.trim(),
);
});
});

View file

@ -0,0 +1,28 @@
<script setup lang="ts">
import { stringify as stringifyToml } from 'iarna-toml-esm';
import { parse as parseYaml } from 'yaml';
import { withDefaultOnError } from '../../utils/defaults';
import type { UseValidationRule } from '@/composable/validation';
const convertYamlToToml = (value: string) => [stringifyToml(parseYaml(value))].flat().join('\n').trim();
const transformer = (value: string) => value.trim() === '' ? '' : withDefaultOnError(() => convertYamlToToml(value), '');
const rules: UseValidationRule<string>[] = [
{
validator: (v: string) => v === '' || parseYaml(v),
message: 'Provided JSON is not valid.',
},
];
</script>
<template>
<format-transformer
input-label="Your YAML"
input-placeholder="Paste your YAML here..."
output-label="TOML from your YAML"
output-language="toml"
:input-validation-rules="rules"
:transformer="transformer"
/>
</template>

View file

@ -66,11 +66,14 @@ const validation = useValidation({
mb-2
/>
<c-input-text v-model:value="valueLong" multiline autosize mb-2 rows="5" />
<c-input-text
value="Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum, est modi iusto repellendus fuga accusantium atque at magnam aliquam eum explicabo vero quia, nobis quasi quis! Earum amet quam a?"
multiline
clearable
/>
<h2>Autosize</h2>
<c-input-text v-model:value="value" label="Autosize" rows="1" multiline autosize mb-2 />
<c-input-text v-model:value="valueLong" label="Autosize monospace" rows="1" multiline autosize monospace mb-2 />
</template>

View file

@ -82,12 +82,12 @@ const inputRef = ref<HTMLInputElement>();
const inputWrapperRef = ref<HTMLElement>();
watch(
value,
() => {
[value, autosize, multiline, inputWrapperRef, textareaRef],
() => nextTick(() => {
if (props.multiline && autosize.value) {
resizeTextarea();
}
},
}),
{ immediate: true },
);
@ -96,9 +96,9 @@ function resizeTextarea() {
return;
}
const { scrollHeight } = textareaRef.value;
const scrollHeight = textareaRef.value.scrollHeight + 2;
inputWrapperRef.value.style.height = `${scrollHeight + 2}px`;
inputWrapperRef.value.style.height = `${scrollHeight}px`;
}
const htmlInputType = computed(() => {