Merge branch 'main' into main

This commit is contained in:
Mark Townsend 2023-09-04 13:37:49 +01:00 committed by GitHub
commit 542f358b91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 87 additions and 44 deletions

1
components.d.ts vendored
View file

@ -33,6 +33,7 @@ declare module '@vue/runtime-core' {
CInputText: typeof import('./src/ui/c-input-text/c-input-text.vue')['default'] CInputText: typeof import('./src/ui/c-input-text/c-input-text.vue')['default']
'CInputText.demo': typeof import('./src/ui/c-input-text/c-input-text.demo.vue')['default'] 'CInputText.demo': typeof import('./src/ui/c-input-text/c-input-text.demo.vue')['default']
CKeyValueList: typeof import('./src/ui/c-key-value-list/c-key-value-list.vue')['default'] CKeyValueList: typeof import('./src/ui/c-key-value-list/c-key-value-list.vue')['default']
CKeyValueListItem: typeof import('./src/ui/c-key-value-list/c-key-value-list-item.vue')['default']
CLabel: typeof import('./src/ui/c-label/c-label.vue')['default'] CLabel: typeof import('./src/ui/c-label/c-label.vue')['default']
CLink: typeof import('./src/ui/c-link/c-link.vue')['default'] CLink: typeof import('./src/ui/c-link/c-link.vue')['default']
'CLink.demo': typeof import('./src/ui/c-link/c-link.demo.vue')['default'] 'CLink.demo': typeof import('./src/ui/c-link/c-link.demo.vue')['default']

View file

@ -75,7 +75,7 @@
"plausible-tracker": "^0.3.8", "plausible-tracker": "^0.3.8",
"qrcode": "^1.5.1", "qrcode": "^1.5.1",
"randombytes": "^2.1.0", "randombytes": "^2.1.0",
"sql-formatter": "^12.0.0", "sql-formatter": "^13.0.0",
"ua-parser-js": "^1.0.35", "ua-parser-js": "^1.0.35",
"unicode-emoji-json": "^0.4.0", "unicode-emoji-json": "^0.4.0",
"unplugin-auto-import": "^0.16.4", "unplugin-auto-import": "^0.16.4",

16
pnpm-lock.yaml generated
View file

@ -129,8 +129,8 @@ dependencies:
specifier: ^2.1.0 specifier: ^2.1.0
version: 2.1.0 version: 2.1.0
sql-formatter: sql-formatter:
specifier: ^12.0.0 specifier: ^13.0.0
version: 12.0.0 version: 13.0.0
ua-parser-js: ua-parser-js:
specifier: ^1.0.35 specifier: ^1.0.35
version: 1.0.35 version: 1.0.35
@ -5565,6 +5565,11 @@ packages:
resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==}
dev: true dev: true
/get-stdin@8.0.0:
resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==}
engines: {node: '>=10'}
dev: false
/get-stream@6.0.1: /get-stream@6.0.1:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -6713,6 +6718,7 @@ packages:
/nearley@2.20.1: /nearley@2.20.1:
resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==} resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==}
hasBin: true
dependencies: dependencies:
commander: 2.20.3 commander: 2.20.3
moo: 0.5.2 moo: 0.5.2
@ -7820,10 +7826,12 @@ packages:
resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==}
dev: false dev: false
/sql-formatter@12.0.0: /sql-formatter@13.0.0:
resolution: {integrity: sha512-LR2m7BEvkyNAPzmcSCZ2b4Qzm5ySiiXS9Juc73VguTqCWIbYv7ZFV4LaDM7jNNZqHPfrqFssO7WWpITsAuLOuQ==} resolution: {integrity: sha512-V21cVvge4rhn9Fa7K/fTKcmPM+x1yee6Vhq8ZwgaWh3VPBqApgsaoFB5kLAhiqRo5AmSaRyLU7LIdgnNwH01/w==}
hasBin: true
dependencies: dependencies:
argparse: 2.0.1 argparse: 2.0.1
get-stdin: 8.0.0
nearley: 2.20.1 nearley: 2.20.1
dev: false dev: false

View file

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

View file

@ -1,6 +1,22 @@
import type { App } from 'vue'; import type { Plugin } from 'vue';
import { createI18n } from 'vue-i18n'; import { createI18n } from 'vue-i18n';
import messages from '@intlify/unplugin-vue-i18n/messages'; import baseMessages from '@intlify/unplugin-vue-i18n/messages';
import _ from 'lodash';
import { parse as parseYaml } from 'yaml';
const i18nFiles = import.meta.glob('../tools/*/locales/**.yml', { as: 'raw' });
const messagesByTools = await Promise.all(_.map(i18nFiles, async (fileDescriptor, path) => {
const [, locale] = path.match(/\.\/tools\/.*?\/locales\/(.*)\.ya?ml$/i) ?? [];
const content = parseYaml(await fileDescriptor());
return { [locale]: content };
}));
const messages = _.merge(
baseMessages,
_.merge({}, ...messagesByTools),
);
const i18n = createI18n({ const i18n = createI18n({
legacy: false, legacy: false,
@ -8,8 +24,8 @@ const i18n = createI18n({
messages, messages,
}); });
export const i18nPlugin = { export const i18nPlugin: Plugin = {
install: (app: App) => { install: (app) => {
app.use(i18n); app.use(i18n);
}, },
}; };

View file

@ -1,14 +1,15 @@
import { type Page, expect, test } from '@playwright/test'; import { type Page, expect, test } from '@playwright/test';
import _ from 'lodash';
async function extractIbanInfo({ page }: { page: Page }) { async function extractIbanInfo({ page }: { page: Page }) {
const tdHandles = await page.locator('table tr td').elementHandles(); const itemsLines = await page
const tdTextContents = await Promise.all(tdHandles.map(el => el.textContent())); .locator('.c-key-value-list__item').all();
return _.chain(tdTextContents) return await Promise.all(
.map(tdTextContent => tdTextContent?.trim().replace(' Copy to clipboard', '')) itemsLines.map(async item => [
.chunk(2) (await item.locator('.c-key-value-list__key').textContent() ?? '').trim(),
.value(); (await item.locator('.c-key-value-list__value').textContent() ?? '').trim(),
]),
);
} }
test.describe('Tool - Iban validator and parser', () => { test.describe('Tool - Iban validator and parser', () => {
@ -41,7 +42,7 @@ test.describe('Tool - Iban validator and parser', () => {
expect(ibanInfo).toEqual([ expect(ibanInfo).toEqual([
['Is IBAN valid ?', 'No'], ['Is IBAN valid ?', 'No'],
['IBAN errors', 'Wrong account bank branch checksumWrong IBAN checksum Copy to clipboard'], ['IBAN errors', 'Wrong account bank branch checksum Wrong IBAN checksum'],
['Is IBAN a QR-IBAN ?', 'No'], ['Is IBAN a QR-IBAN ?', 'No'],
['Country code', 'N/A'], ['Country code', 'N/A'],
['BBAN', 'N/A'], ['BBAN', 'N/A'],

View file

@ -60,7 +60,7 @@ const ibanExamples = [
<div> <div>
<c-input-text v-model:value="rawIban" placeholder="Enter an IBAN to check for validity..." test-id="iban-input" /> <c-input-text v-model:value="rawIban" placeholder="Enter an IBAN to check for validity..." test-id="iban-input" />
<c-key-value-list :items="ibanInfo" my-5 /> <c-key-value-list :items="ibanInfo" my-5 data-test-id="iban-info" />
<c-card title="Valid IBAN examples"> <c-card title="Valid IBAN examples">
<div v-for="iban in ibanExamples" :key="iban"> <div v-for="iban in ibanExamples" :key="iban">

View file

@ -0,0 +1,27 @@
<script lang="ts" setup>
import _ from 'lodash';
import type { CKeyValueListItem } from './c-key-value-list.types';
const props = defineProps<{ item: CKeyValueListItem }>();
const { item } = toRefs(props);
</script>
<template>
<div v-if="_.isArray(item.value)">
<div v-for="value in item.value" :key="value">
<c-text-copyable :value="value" :show-icon="item.showCopyButton ?? true" />
</div>
</div>
<div v-else-if="_.isBoolean(item.value)">
<c-text-copyable :value="item.value ? 'true' : 'false'" :displayed-value="item.value ? 'Yes' : 'No'" :show-icon="item.showCopyButton ?? true" />
</div>
<div v-else-if="_.isNumber(item.value)" font-mono>
<c-text-copyable :value="String(item.value)" :show-icon="item.showCopyButton ?? true" />
</div>
<div v-else-if="_.isNil(item.value) || item.value === ''" op-70>
{{ item.placeholder ?? 'N/A' }}
</div>
<div v-else>
<c-text-copyable :value="item.value" :show-icon="item.showCopyButton ?? true" />
</div>
</template>

View file

@ -9,29 +9,13 @@ const formattedItems = computed(() => items.value.filter(item => !_.isNil(item.v
</script> </script>
<template> <template>
<table border-collapse table-fixed> <div my-5>
<tr v-for="item in formattedItems" :key="item.label"> <div v-for="item in formattedItems" :key="item.label" flex gap-2 py-1 class="c-key-value-list__item">
<td py-1 pr-2 text-right font-bold> <div flex-basis-180px text-right font-bold class="c-key-value-list__key">
{{ item.label }} {{ item.label }}
</td>
<td v-if="_.isArray(item.value)">
<div v-for="value in item.value" :key="value">
<c-text-copyable :value="value" :show-icon="item.showCopyButton ?? true" />
</div> </div>
</td>
<td v-else-if="_.isBoolean(item.value)"> <c-key-value-list-item :item="item" class="c-key-value-list__value" />
<c-text-copyable :value="item.value ? 'true' : 'false'" :displayed-value="item.value ? 'Yes' : 'No'" :show-icon="item.showCopyButton ?? true" /> </div>
</td> </div>
<td v-else-if="_.isNumber(item.value)" font-mono>
<c-text-copyable :value="String(item.value)" :show-icon="item.showCopyButton ?? true" />
</td>
<td v-else-if="_.isNil(item.value) || item.value === ''" op-70>
{{ item.placeholder ?? 'N/A' }}
</td>
<td v-else>
<c-text-copyable :value="item.value" :show-icon="item.showCopyButton ?? true" />
</td>
</tr>
</table>
</template> </template>

View file

@ -19,7 +19,10 @@ const isTargetHovered = useElementHover(targetRef);
'op-100 scale-100': isTargetHovered, 'op-100 scale-100': isTargetHovered,
}" }"
> >
<slot name="tooltip"> <slot
v-if="isTargetHovered"
name="tooltip"
>
{{ tooltip }} {{ tooltip }}
</slot> </slot>
</div> </div>

View file

@ -25,7 +25,7 @@ export default defineConfig({
runtimeOnly: true, runtimeOnly: true,
compositionOnly: true, compositionOnly: true,
fullInstall: true, fullInstall: true,
include: [resolve(__dirname, 'locales/**'), resolve(__dirname, 'src/tools/*/locales/**')], include: [resolve(__dirname, 'locales/**')],
}), }),
AutoImport({ AutoImport({
imports: [ imports: [
@ -106,4 +106,7 @@ export default defineConfig({
test: { test: {
exclude: [...configDefaults.exclude, '**/*.e2e.spec.ts'], exclude: [...configDefaults.exclude, '**/*.e2e.spec.ts'],
}, },
build: {
target: 'esnext',
},
}); });