@quazardous/qdadm 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/editors/VanillaJsonEditor.vue +42 -1
- package/src/components/editors/jsonValidator.ts +44 -0
- package/src/generated/managers/testManager.ts +0 -46
- package/src/i18n/I18n.ts +3 -1
- package/src/i18n/__tests__/I18n.test.ts +31 -0
- package/src/i18n/types.ts +8 -0
package/package.json
CHANGED
|
@@ -6,10 +6,17 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* <VanillaJsonEditor v-model="jsonData" mode="tree" />
|
|
9
|
+
*
|
|
10
|
+
* Inline JSON Schema validation (errors highlighted live in the editor):
|
|
11
|
+
* <VanillaJsonEditor v-model="jsonData" :schema="mySchema" />
|
|
12
|
+
*
|
|
13
|
+
* Or a raw validator for advanced cases (takes precedence over `schema`):
|
|
14
|
+
* <VanillaJsonEditor v-model="jsonData" :validator="myValidator" />
|
|
9
15
|
*/
|
|
10
16
|
|
|
11
17
|
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
|
12
18
|
import { JSONEditor, type Content, type ContentErrors, type OnChangeStatus, type Mode } from 'vanilla-jsoneditor'
|
|
19
|
+
import { buildJsonValidator, type Validator, type JSONSchema, type JsonValidatorOptions } from './jsonValidator'
|
|
13
20
|
|
|
14
21
|
type JsonValue = Record<string, unknown> | unknown[] | string | null
|
|
15
22
|
|
|
@@ -21,6 +28,14 @@ interface Props {
|
|
|
21
28
|
mainMenuBar?: boolean
|
|
22
29
|
navigationBar?: boolean
|
|
23
30
|
statusBar?: boolean
|
|
31
|
+
/** JSON Schema for inline Ajv validation (errors highlighted in the editor). */
|
|
32
|
+
schema?: JSONSchema
|
|
33
|
+
/** Raw validator function — takes precedence over `schema`. */
|
|
34
|
+
validator?: Validator
|
|
35
|
+
/** Optional `$ref` definitions passed through to the Ajv validator. */
|
|
36
|
+
schemaDefinitions?: JsonValidatorOptions['schemaDefinitions']
|
|
37
|
+
/** Optional Ajv options passed through to the Ajv validator. */
|
|
38
|
+
ajvOptions?: JsonValidatorOptions['ajvOptions']
|
|
24
39
|
}
|
|
25
40
|
|
|
26
41
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -30,9 +45,23 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
30
45
|
readOnly: false,
|
|
31
46
|
mainMenuBar: true,
|
|
32
47
|
navigationBar: true,
|
|
33
|
-
statusBar: true
|
|
48
|
+
statusBar: true,
|
|
49
|
+
schema: undefined,
|
|
50
|
+
validator: undefined,
|
|
51
|
+
schemaDefinitions: undefined,
|
|
52
|
+
ajvOptions: undefined
|
|
34
53
|
})
|
|
35
54
|
|
|
55
|
+
// Resolve the effective validator from props (raw validator > schema > none).
|
|
56
|
+
function resolveValidator(): Validator | undefined {
|
|
57
|
+
return buildJsonValidator({
|
|
58
|
+
validator: props.validator,
|
|
59
|
+
schema: props.schema,
|
|
60
|
+
schemaDefinitions: props.schemaDefinitions,
|
|
61
|
+
ajvOptions: props.ajvOptions
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
36
65
|
const emit = defineEmits<{
|
|
37
66
|
'update:modelValue': [value: JsonValue]
|
|
38
67
|
'change': [value: JsonValue]
|
|
@@ -76,6 +105,7 @@ onMounted(() => {
|
|
|
76
105
|
mainMenuBar: props.mainMenuBar,
|
|
77
106
|
navigationBar: props.navigationBar,
|
|
78
107
|
statusBar: props.statusBar,
|
|
108
|
+
validator: resolveValidator(),
|
|
79
109
|
onChange: (updatedContent: Content, _previousContent: Content, { contentErrors }: OnChangeStatus) => {
|
|
80
110
|
// Skip if this change came from a programmatic prop update
|
|
81
111
|
if (updatingFromProp.value) return
|
|
@@ -144,6 +174,17 @@ watch(() => props.readOnly, (newReadOnly: boolean) => {
|
|
|
144
174
|
}
|
|
145
175
|
})
|
|
146
176
|
|
|
177
|
+
// Watch validator/schema changes — rebuild and re-apply so edits re-validate live
|
|
178
|
+
watch(
|
|
179
|
+
() => [props.validator, props.schema, props.schemaDefinitions, props.ajvOptions],
|
|
180
|
+
() => {
|
|
181
|
+
if (editor) {
|
|
182
|
+
editor.updateProps({ validator: resolveValidator() })
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{ deep: true }
|
|
186
|
+
)
|
|
187
|
+
|
|
147
188
|
// Cleanup
|
|
148
189
|
onUnmounted(() => {
|
|
149
190
|
if (editor) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jsonValidator - build a vanilla-jsoneditor `Validator` from wrapper props.
|
|
3
|
+
*
|
|
4
|
+
* Pure helper extracted from VanillaJsonEditor so the resolution logic
|
|
5
|
+
* (raw validator vs JSON Schema vs none) is unit-testable without mounting the
|
|
6
|
+
* Svelte editor. See qdadm #1050.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
createAjvValidator,
|
|
10
|
+
type Validator,
|
|
11
|
+
type JSONSchema,
|
|
12
|
+
type AjvValidatorOptions,
|
|
13
|
+
} from 'vanilla-jsoneditor'
|
|
14
|
+
|
|
15
|
+
export type { Validator, JSONSchema }
|
|
16
|
+
|
|
17
|
+
export interface JsonValidatorOptions {
|
|
18
|
+
/** Raw validator function — takes precedence over `schema` when both are set. */
|
|
19
|
+
validator?: Validator
|
|
20
|
+
/** JSON Schema; compiled to a validator via Ajv (bundled in vanilla-jsoneditor). */
|
|
21
|
+
schema?: JSONSchema
|
|
22
|
+
/** Optional `$ref` definitions passed through to `createAjvValidator`. */
|
|
23
|
+
schemaDefinitions?: AjvValidatorOptions['schemaDefinitions']
|
|
24
|
+
/** Optional Ajv options passed through to `createAjvValidator`. */
|
|
25
|
+
ajvOptions?: AjvValidatorOptions['ajvOptions']
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the effective validator:
|
|
30
|
+
* - an explicit `validator` wins;
|
|
31
|
+
* - otherwise a `schema` is compiled with `createAjvValidator`;
|
|
32
|
+
* - otherwise `undefined` (no validation — the wrapper's default behavior).
|
|
33
|
+
*/
|
|
34
|
+
export function buildJsonValidator(options: JsonValidatorOptions): Validator | undefined {
|
|
35
|
+
if (options.validator) return options.validator
|
|
36
|
+
if (options.schema) {
|
|
37
|
+
return createAjvValidator({
|
|
38
|
+
schema: options.schema,
|
|
39
|
+
schemaDefinitions: options.schemaDefinitions,
|
|
40
|
+
ajvOptions: options.ajvOptions,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
return undefined
|
|
44
|
+
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TestManager - Auto-generated EntityManager
|
|
3
|
-
*
|
|
4
|
-
* Generated by qdadm/gen/generateManagers
|
|
5
|
-
* DO NOT EDIT MANUALLY - Changes will be overwritten
|
|
6
|
-
*
|
|
7
|
-
* Entity: test
|
|
8
|
-
* Endpoint: /test
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { EntityManager } from '@quazardous/qdadm'
|
|
12
|
-
import type { EntityRecord } from '@quazardous/qdadm'
|
|
13
|
-
import { ApiStorage } from '@quazardous/qdadm'
|
|
14
|
-
|
|
15
|
-
export interface TestEntity extends EntityRecord {
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Schema definition for test
|
|
20
|
-
*/
|
|
21
|
-
export const testSchema = {
|
|
22
|
-
name: "test",
|
|
23
|
-
endpoint: "/test",
|
|
24
|
-
fields: {}
|
|
25
|
-
} as const
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Storage options for test
|
|
29
|
-
*/
|
|
30
|
-
const storageOptions = {
|
|
31
|
-
endpoint: "/test"
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* TestManager instance
|
|
36
|
-
*
|
|
37
|
-
* Provides CRUD operations for test entity.
|
|
38
|
-
*/
|
|
39
|
-
export const testManager = new EntityManager<TestEntity>({
|
|
40
|
-
...{
|
|
41
|
-
name: "test",
|
|
42
|
-
idField: "id",
|
|
43
|
-
fields: {}
|
|
44
|
-
},
|
|
45
|
-
storage: new ApiStorage<TestEntity>(storageOptions)
|
|
46
|
-
})
|
package/src/i18n/I18n.ts
CHANGED
|
@@ -59,6 +59,7 @@ export class I18n {
|
|
|
59
59
|
private _appAliases: AliasPattern[]
|
|
60
60
|
private _entityToModule: Map<string, string> = new Map()
|
|
61
61
|
private _signals: I18nDeps['signals']
|
|
62
|
+
private _emitMissing: boolean
|
|
62
63
|
private _defaultLocale: string
|
|
63
64
|
private _fallbackLocale: string
|
|
64
65
|
private _loadedLocales: Set<string> = new Set()
|
|
@@ -76,6 +77,7 @@ export class I18n {
|
|
|
76
77
|
this._strategyName = options.keyStrategy ?? 'global'
|
|
77
78
|
this._appAliases = options.aliases ? [...options.aliases] : []
|
|
78
79
|
this._signals = deps.signals ?? null
|
|
80
|
+
this._emitMissing = options.emitMissing ?? true
|
|
79
81
|
|
|
80
82
|
this._registry = new MessagesRegistry()
|
|
81
83
|
this.inline = new InlineTranslationProvider()
|
|
@@ -99,7 +101,7 @@ export class I18n {
|
|
|
99
101
|
fallbackLocale: this._fallbackLocale,
|
|
100
102
|
globalAliases: this._currentStrategyAliases(),
|
|
101
103
|
onMissing: (key, locale) => {
|
|
102
|
-
this._signals?.emit('i18n:missing', { key, locale })
|
|
104
|
+
if (this._emitMissing) this._signals?.emit('i18n:missing', { key, locale })
|
|
103
105
|
},
|
|
104
106
|
})
|
|
105
107
|
|
|
@@ -105,6 +105,37 @@ describe('I18n — locale change', () => {
|
|
|
105
105
|
})
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
+
describe('I18n — emitMissing flag', () => {
|
|
109
|
+
function makeSignals() {
|
|
110
|
+
const events: Array<{ signal: string; data: unknown }> = []
|
|
111
|
+
return {
|
|
112
|
+
events,
|
|
113
|
+
signals: {
|
|
114
|
+
emit: (signal: string, data: unknown) => {
|
|
115
|
+
events.push({ signal, data })
|
|
116
|
+
},
|
|
117
|
+
on: () => () => {},
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
it('emits i18n:missing on an unresolved key by default', async () => {
|
|
123
|
+
const { events, signals } = makeSignals()
|
|
124
|
+
const i18n = new I18n({ disableDefaultCoreBundle: true }, { signals })
|
|
125
|
+
await i18n.bootstrap()
|
|
126
|
+
i18n.t('totally.unknown.key')
|
|
127
|
+
expect(events.some((e) => e.signal === 'i18n:missing')).toBe(true)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('does not emit i18n:missing when emitMissing is false', async () => {
|
|
131
|
+
const { events, signals } = makeSignals()
|
|
132
|
+
const i18n = new I18n({ disableDefaultCoreBundle: true, emitMissing: false }, { signals })
|
|
133
|
+
await i18n.bootstrap()
|
|
134
|
+
i18n.t('totally.unknown.key')
|
|
135
|
+
expect(events.some((e) => e.signal === 'i18n:missing')).toBe(false)
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
108
139
|
describe('I18n — providers', () => {
|
|
109
140
|
it('loads from a custom TranslationProvider during bootstrap', async () => {
|
|
110
141
|
const provider: TranslationProvider = {
|
package/src/i18n/types.ts
CHANGED
|
@@ -31,4 +31,12 @@ export type {
|
|
|
31
31
|
export interface I18nOptions extends BaseI18nOptions {
|
|
32
32
|
/** Disable shipping qdadm's default core.* bundles (EN + FR). Default: false. */
|
|
33
33
|
disableDefaultCoreBundle?: boolean
|
|
34
|
+
/**
|
|
35
|
+
* Emit the `i18n:missing` debug signal when a key cannot be resolved.
|
|
36
|
+
* Default: `true`. Set to `false` to silence the missing-key stream (e.g. an
|
|
37
|
+
* untranslated app where every key would otherwise fire it) — the debug
|
|
38
|
+
* panel's "Missing" section then stays empty. No functional impact: nothing
|
|
39
|
+
* but the debug collector consumes this signal.
|
|
40
|
+
*/
|
|
41
|
+
emitMissing?: boolean
|
|
34
42
|
}
|