lutaml-jsonschema 0.1.11 → 0.1.12
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/frontend/src/components/DetailPanel.vue +62 -3
- data/frontend/src/components/SchemaBuilder.vue +33 -4
- data/frontend/src/components/SearchModal.vue +10 -0
- data/frontend/src/composables/useClipboard.ts +21 -0
- data/frontend/src/views/HomeView.vue +17 -4
- data/lib/lutaml/jsonschema/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ad24a6671a3bcf4e811c7588dbd4ca4fff96d589d625a7e6dd2507a74a115d4
|
|
4
|
+
data.tar.gz: 947415a532ef91f5287564a63a422f9c5021e01b5ec43a174d8e68b53e81dcca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d76f7513a5de8fe76529844d0a110f1901f3f9fc4968213f3ae872505370aec7ffd71ab6d349745a9af383b03d6d32ce19f972eccf585c6efd91281cc3d1f6e6
|
|
7
|
+
data.tar.gz: 72a9b35c92992f4ac0fa82f63b477b362bb358b752d1deb773c3613a29e07ea0b4cf144dbaf11fd43b1c1b5eec3b5719ad72e2d3064bea17e112f1dafe55fc0a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.12] - 2026-05-10
|
|
4
|
+
|
|
5
|
+
### Redoc-style UX improvements (round 6)
|
|
6
|
+
|
|
7
|
+
- Property title display in type row when it differs from name (Redoc TypeTitle pattern)
|
|
8
|
+
- Copy fallback using `execCommand('copy')` for HTTP contexts where clipboard API unavailable
|
|
9
|
+
- Dark panel full-height background via BackgroundStub `::before` pattern
|
|
10
|
+
- Search result description snippets in search modal
|
|
11
|
+
- Tooltip with directional CSS arrow on copy buttons
|
|
12
|
+
- DetailPanel properties table: shows title, format with angle brackets, default value, enum count, inline examples
|
|
13
|
+
|
|
3
14
|
## [0.1.11] - 2026-05-09
|
|
4
15
|
|
|
5
16
|
### Redoc-style UX improvements (round 5)
|
|
@@ -221,18 +221,28 @@
|
|
|
221
221
|
<tr v-for="prop in properties" :key="prop.name">
|
|
222
222
|
<td>
|
|
223
223
|
<span class="font-mono">{{ prop.name }}</span>
|
|
224
|
+
<span v-if="prop.title && prop.title !== prop.name" class="prop-title">({{ prop.title }})</span>
|
|
224
225
|
<span v-if="prop.deprecated" class="badge badge-deprecated-sm">deprecated</span>
|
|
225
226
|
</td>
|
|
226
227
|
<td>
|
|
227
228
|
<span class="prop-type">{{ prop.type || 'any' }}</span>
|
|
228
|
-
<span v-if="prop.format" class="prop-format"
|
|
229
|
+
<span v-if="prop.format" class="prop-format"><{{ prop.format }}></span>
|
|
229
230
|
<span v-if="prop.itemsType" class="prop-format">[{{ prop.itemsType }}]</span>
|
|
231
|
+
<span v-if="prop.default != null" class="prop-default">default: {{ prop.default }}</span>
|
|
232
|
+
<span v-if="prop.enum?.length" class="prop-enum">{{ prop.enum.length }} values</span>
|
|
230
233
|
</td>
|
|
231
234
|
<td>
|
|
232
235
|
<span v-if="prop.required" class="badge badge-required-sm">yes</span>
|
|
233
236
|
<span v-else class="text-muted">no</span>
|
|
234
237
|
</td>
|
|
235
|
-
<td
|
|
238
|
+
<td>
|
|
239
|
+
<span class="text-secondary">{{ prop.description || '—' }}</span>
|
|
240
|
+
<div v-if="prop.examples?.length" class="prop-examples">
|
|
241
|
+
<span class="prop-examples-label">Examples:</span>
|
|
242
|
+
<span v-for="(ex, i) in prop.examples.slice(0, 3)" :key="i" class="prop-example-chip">{{ typeof ex === 'object' ? JSON.stringify(ex) : ex }}</span>
|
|
243
|
+
<span v-if="prop.examples.length > 3" class="text-muted">+{{ prop.examples.length - 3 }} more</span>
|
|
244
|
+
</div>
|
|
245
|
+
</td>
|
|
236
246
|
</tr>
|
|
237
247
|
</tbody>
|
|
238
248
|
</table>
|
|
@@ -564,8 +574,57 @@ const definitionItem = computed(() => {
|
|
|
564
574
|
|
|
565
575
|
.prop-format {
|
|
566
576
|
font-size: var(--text-xs);
|
|
567
|
-
color: var(--
|
|
577
|
+
color: var(--color-accent);
|
|
568
578
|
margin-left: var(--space-1);
|
|
579
|
+
font-family: var(--font-mono);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.prop-title {
|
|
583
|
+
display: block;
|
|
584
|
+
font-size: var(--text-xs);
|
|
585
|
+
color: var(--text-secondary);
|
|
586
|
+
font-style: italic;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.prop-default {
|
|
590
|
+
display: block;
|
|
591
|
+
font-size: 10px;
|
|
592
|
+
color: var(--text-muted);
|
|
593
|
+
font-family: var(--font-mono);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.prop-enum {
|
|
597
|
+
display: block;
|
|
598
|
+
font-size: 10px;
|
|
599
|
+
color: var(--color-teal);
|
|
600
|
+
background: var(--color-teal-alpha);
|
|
601
|
+
padding: 1px 4px;
|
|
602
|
+
border-radius: 2px;
|
|
603
|
+
display: inline-block;
|
|
604
|
+
margin-top: 2px;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.prop-examples {
|
|
608
|
+
margin-top: var(--space-1);
|
|
609
|
+
display: flex;
|
|
610
|
+
flex-wrap: wrap;
|
|
611
|
+
gap: 4px;
|
|
612
|
+
align-items: center;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.prop-examples-label {
|
|
616
|
+
font-size: 10px;
|
|
617
|
+
color: var(--text-muted);
|
|
618
|
+
font-weight: 500;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.prop-example-chip {
|
|
622
|
+
font-size: 10px;
|
|
623
|
+
font-family: var(--font-mono);
|
|
624
|
+
background: var(--bg-secondary);
|
|
625
|
+
padding: 1px 4px;
|
|
626
|
+
border-radius: 2px;
|
|
627
|
+
color: var(--text-secondary);
|
|
569
628
|
}
|
|
570
629
|
|
|
571
630
|
.badge-type {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
<span class="font-mono">{{ field.prop.name }}</span>
|
|
17
17
|
</button>
|
|
18
18
|
<span class="field-type-badge" :class="typeBadgeClass(field.prop)">{{ displayType(field.prop, field.resolvedDef?.title || field.resolvedDef?.name) }}</span>
|
|
19
|
+
<span v-if="field.prop.title && field.prop.title !== field.prop.name" class="field-title-badge">{{ field.prop.title }}</span>
|
|
19
20
|
<span v-if="field.prop.format" class="field-format-badge"><{{ field.prop.format }}></span>
|
|
20
21
|
<span v-if="field.prop.contentMediaType" class="field-format-badge">content-type: {{ field.prop.contentMediaType }}</span>
|
|
21
22
|
<span v-if="field.prop.contentEncoding" class="field-format-badge">encoding: {{ field.prop.contentEncoding }}</span>
|
|
@@ -246,6 +247,7 @@ import {
|
|
|
246
247
|
import { renderInlineMarkdown } from '../composables/useMarkdownLite'
|
|
247
248
|
import { jsonToCollapsibleHtml } from '../composables/useJsonViewer'
|
|
248
249
|
import type { BuilderField } from '../composables/useBuilderField'
|
|
250
|
+
import { copyToClipboard } from '../composables/useClipboard'
|
|
249
251
|
|
|
250
252
|
const schemaStore = useSchemaStore()
|
|
251
253
|
const uiStore = useUiStore()
|
|
@@ -437,11 +439,11 @@ function togglePattern(pattern: string) {
|
|
|
437
439
|
}
|
|
438
440
|
|
|
439
441
|
async function copyJson() {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
+
const ok = await copyToClipboard(outputJson.value)
|
|
443
|
+
if (ok) {
|
|
442
444
|
copied.value = true
|
|
443
445
|
setTimeout(() => { copied.value = false }, 2000)
|
|
444
|
-
}
|
|
446
|
+
}
|
|
445
447
|
}
|
|
446
448
|
</script>
|
|
447
449
|
|
|
@@ -571,6 +573,13 @@ async function copyJson() {
|
|
|
571
573
|
font-weight: 500;
|
|
572
574
|
}
|
|
573
575
|
|
|
576
|
+
.field-title-badge {
|
|
577
|
+
font-size: 10px;
|
|
578
|
+
font-style: italic;
|
|
579
|
+
color: var(--text-secondary);
|
|
580
|
+
flex-shrink: 0;
|
|
581
|
+
}
|
|
582
|
+
|
|
574
583
|
.deprecated-badge {
|
|
575
584
|
font-size: 10px;
|
|
576
585
|
font-weight: 600;
|
|
@@ -1001,6 +1010,16 @@ async function copyJson() {
|
|
|
1001
1010
|
.builder-preview {
|
|
1002
1011
|
position: sticky;
|
|
1003
1012
|
top: var(--space-4);
|
|
1013
|
+
z-index: 0;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
.builder-preview::before {
|
|
1017
|
+
content: '';
|
|
1018
|
+
position: absolute;
|
|
1019
|
+
inset: 0;
|
|
1020
|
+
background: var(--panel-dark-bg);
|
|
1021
|
+
border-radius: var(--radius-md);
|
|
1022
|
+
z-index: -1;
|
|
1004
1023
|
}
|
|
1005
1024
|
|
|
1006
1025
|
.preview-inner {
|
|
@@ -1148,7 +1167,7 @@ async function copyJson() {
|
|
|
1148
1167
|
|
|
1149
1168
|
.copy-tooltip {
|
|
1150
1169
|
position: absolute;
|
|
1151
|
-
bottom: calc(100% +
|
|
1170
|
+
bottom: calc(100% + 8px);
|
|
1152
1171
|
left: 50%;
|
|
1153
1172
|
transform: translateX(-50%);
|
|
1154
1173
|
background: var(--bg-elevated);
|
|
@@ -1163,6 +1182,16 @@ async function copyJson() {
|
|
|
1163
1182
|
animation: tooltipFade var(--transition-slow);
|
|
1164
1183
|
}
|
|
1165
1184
|
|
|
1185
|
+
.copy-tooltip::after {
|
|
1186
|
+
content: '';
|
|
1187
|
+
position: absolute;
|
|
1188
|
+
top: 100%;
|
|
1189
|
+
left: 50%;
|
|
1190
|
+
margin-left: -4px;
|
|
1191
|
+
border: 4px solid transparent;
|
|
1192
|
+
border-top-color: var(--bg-elevated);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1166
1195
|
@keyframes tooltipFade {
|
|
1167
1196
|
from { opacity: 0; transform: translateX(-50%) translateY(2px); }
|
|
1168
1197
|
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
<div class="result-content">
|
|
53
53
|
<span class="result-name" v-html="highlightMatch(result.name)"></span>
|
|
54
54
|
<span class="result-schema">{{ result.schemaName }}</span>
|
|
55
|
+
<span v-if="result.description" class="result-desc">{{ result.description }}</span>
|
|
55
56
|
</div>
|
|
56
57
|
<span :class="['badge', `badge-${result.type}`]">{{ result.type }}</span>
|
|
57
58
|
</div>
|
|
@@ -235,6 +236,15 @@ function highlightMatch(text: string): string {
|
|
|
235
236
|
color: var(--text-muted);
|
|
236
237
|
}
|
|
237
238
|
|
|
239
|
+
.result-desc {
|
|
240
|
+
font-size: var(--text-xs);
|
|
241
|
+
color: var(--text-muted);
|
|
242
|
+
display: -webkit-box;
|
|
243
|
+
-webkit-line-clamp: 1;
|
|
244
|
+
-webkit-box-orient: vertical;
|
|
245
|
+
overflow: hidden;
|
|
246
|
+
}
|
|
247
|
+
|
|
238
248
|
.badge-schema {
|
|
239
249
|
background: var(--badge-schema-bg);
|
|
240
250
|
color: var(--badge-schema);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function fallbackCopy(text: string): boolean {
|
|
2
|
+
const ta = document.createElement('textarea')
|
|
3
|
+
ta.value = text
|
|
4
|
+
ta.style.cssText = 'position:fixed;top:0;left:0;width:2em;height:2em;padding:0;border:none;outline:none;box-shadow:none;background:transparent'
|
|
5
|
+
document.body.appendChild(ta)
|
|
6
|
+
ta.select()
|
|
7
|
+
let ok = false
|
|
8
|
+
try { ok = document.execCommand('copy') } catch { /* noop */ }
|
|
9
|
+
document.body.removeChild(ta)
|
|
10
|
+
return ok
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function copyToClipboard(text: string): Promise<boolean> {
|
|
14
|
+
if (navigator.clipboard?.writeText) {
|
|
15
|
+
try {
|
|
16
|
+
await navigator.clipboard.writeText(text)
|
|
17
|
+
return true
|
|
18
|
+
} catch { /* fall through */ }
|
|
19
|
+
}
|
|
20
|
+
return fallbackCopy(text)
|
|
21
|
+
}
|
|
@@ -221,6 +221,7 @@ import { useSchemaStore } from '../stores/schemaStore'
|
|
|
221
221
|
import SchemaBuilder from '../components/SchemaBuilder.vue'
|
|
222
222
|
import { downloadFile } from '../composables/useDownload'
|
|
223
223
|
import { renderInlineMarkdown } from '../composables/useMarkdownLite'
|
|
224
|
+
import { copyToClipboard } from '../composables/useClipboard'
|
|
224
225
|
import type { SpaSchema } from '../types'
|
|
225
226
|
|
|
226
227
|
const schemaStore = useSchemaStore()
|
|
@@ -264,9 +265,11 @@ const sourceLineCount = computed(() => {
|
|
|
264
265
|
function copySource() {
|
|
265
266
|
const src = schemaStore.selectedSchema?.sourceJson
|
|
266
267
|
if (!src) return
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
268
|
+
copyToClipboard(src).then(ok => {
|
|
269
|
+
if (ok) {
|
|
270
|
+
sourceCopied.value = true
|
|
271
|
+
setTimeout(() => { sourceCopied.value = false }, 2000)
|
|
272
|
+
}
|
|
270
273
|
})
|
|
271
274
|
}
|
|
272
275
|
|
|
@@ -612,7 +615,7 @@ watch(() => schemaStore.selectedItemKey, (key) => {
|
|
|
612
615
|
|
|
613
616
|
.copy-tooltip {
|
|
614
617
|
position: absolute;
|
|
615
|
-
bottom: calc(100% +
|
|
618
|
+
bottom: calc(100% + 8px);
|
|
616
619
|
left: 50%;
|
|
617
620
|
transform: translateX(-50%);
|
|
618
621
|
background: var(--bg-elevated);
|
|
@@ -627,6 +630,16 @@ watch(() => schemaStore.selectedItemKey, (key) => {
|
|
|
627
630
|
animation: tooltipFade var(--transition-slow);
|
|
628
631
|
}
|
|
629
632
|
|
|
633
|
+
.copy-tooltip::after {
|
|
634
|
+
content: '';
|
|
635
|
+
position: absolute;
|
|
636
|
+
top: 100%;
|
|
637
|
+
left: 50%;
|
|
638
|
+
margin-left: -4px;
|
|
639
|
+
border: 4px solid transparent;
|
|
640
|
+
border-top-color: var(--bg-elevated);
|
|
641
|
+
}
|
|
642
|
+
|
|
630
643
|
@keyframes tooltipFade {
|
|
631
644
|
from { opacity: 0; transform: translateX(-50%) translateY(2px); }
|
|
632
645
|
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-jsonschema
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|
|
@@ -106,6 +106,7 @@ files:
|
|
|
106
106
|
- frontend/src/components/SchemaBuilder.vue
|
|
107
107
|
- frontend/src/components/SearchModal.vue
|
|
108
108
|
- frontend/src/composables/useBuilderField.ts
|
|
109
|
+
- frontend/src/composables/useClipboard.ts
|
|
109
110
|
- frontend/src/composables/useDefinitionResolver.ts
|
|
110
111
|
- frontend/src/composables/useDownload.ts
|
|
111
112
|
- frontend/src/composables/useJsonViewer.ts
|