mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-08 15:15:02 -04:00
Merge remote-tracking branch 'CorentinTh/main' into main
This commit is contained in:
commit
9d8bb075b2
62 changed files with 1851 additions and 690 deletions
|
@ -288,4 +288,4 @@
|
|||
"watchWithFilter": true,
|
||||
"whenever": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/docker-nightly-release.yml
vendored
2
.github/workflows/docker-nightly-release.yml
vendored
|
@ -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
2
.nvmrc
|
@ -1 +1 @@
|
|||
16.14.2
|
||||
16.20.1
|
||||
|
|
292
auto-imports.d.ts
vendored
292
auto-imports.d.ts
vendored
|
@ -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
13
components.d.ts
vendored
|
@ -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']
|
||||
}
|
||||
}
|
||||
|
|
18
package.json
18
package.json
|
@ -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
1102
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
|
|
|
@ -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) };
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
5
src/shims.d.ts
vendored
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 />
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 />
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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('<title>IT Tool</title');
|
||||
const unescapeInput = ref('<title>IT Tool</title>');
|
||||
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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
12
src/tools/json-to-toml/index.ts
Normal file
12
src/tools/json-to-toml/index.ts
Normal 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'),
|
||||
});
|
39
src/tools/json-to-toml/json-to-toml.e2e.spec.ts
Normal file
39
src/tools/json-to-toml/json-to-toml.e2e.spec.ts
Normal 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(),
|
||||
);
|
||||
});
|
||||
});
|
28
src/tools/json-to-toml/json-to-toml.vue
Normal file
28
src/tools/json-to-toml/json-to-toml.vue
Normal 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>
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
12
src/tools/password-strength-analyser/index.ts
Normal file
12
src/tools/password-strength-analyser/index.ts
Normal 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'),
|
||||
});
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
13
src/tools/toml-to-json/index.ts
Normal file
13
src/tools/toml-to-json/index.ts
Normal 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'),
|
||||
});
|
40
src/tools/toml-to-json/toml-to-json.e2e.spec.ts
Normal file
40
src/tools/toml-to-json/toml-to-json.e2e.spec.ts
Normal 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(),
|
||||
);
|
||||
});
|
||||
});
|
26
src/tools/toml-to-json/toml-to-json.vue
Normal file
26
src/tools/toml-to-json/toml-to-json.vue
Normal 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>
|
8
src/tools/toml-to-json/toml.services.ts
Normal file
8
src/tools/toml-to-json/toml.services.ts
Normal 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));
|
||||
}
|
12
src/tools/toml-to-yaml/index.ts
Normal file
12
src/tools/toml-to-yaml/index.ts
Normal 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'),
|
||||
});
|
36
src/tools/toml-to-yaml/toml-to-yaml.e2e.spec.ts
Normal file
36
src/tools/toml-to-yaml/toml-to-yaml.e2e.spec.ts
Normal 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(),
|
||||
);
|
||||
});
|
||||
});
|
27
src/tools/toml-to-yaml/toml-to-yaml.vue
Normal file
27
src/tools/toml-to-yaml/toml-to-yaml.vue
Normal 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>
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
12
src/tools/yaml-to-toml/index.ts
Normal file
12
src/tools/yaml-to-toml/index.ts
Normal 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'),
|
||||
});
|
37
src/tools/yaml-to-toml/yaml-to-toml.e2e.spec.ts
Normal file
37
src/tools/yaml-to-toml/yaml-to-toml.e2e.spec.ts
Normal 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(),
|
||||
);
|
||||
});
|
||||
});
|
28
src/tools/yaml-to-toml/yaml-to-toml.vue
Normal file
28
src/tools/yaml-to-toml/yaml-to-toml.vue
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue