lutaml-jsonschema 0.1.10 → 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 +21 -0
- data/frontend/src/components/DetailPanel.vue +62 -3
- data/frontend/src/components/SchemaBuilder.vue +77 -28
- data/frontend/src/components/SearchModal.vue +10 -0
- data/frontend/src/composables/useClipboard.ts +21 -0
- data/frontend/src/style.css +31 -0
- data/frontend/src/views/HomeView.vue +30 -5
- 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,26 @@
|
|
|
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
|
+
|
|
14
|
+
## [0.1.11] - 2026-05-09
|
|
15
|
+
|
|
16
|
+
### Redoc-style UX improvements (round 5)
|
|
17
|
+
|
|
18
|
+
- Dark JSON preview panel with themed syntax colors in SchemaBuilder
|
|
19
|
+
- Select-on-click for source viewer and builder JSON blocks (double-click to select all)
|
|
20
|
+
- Print stylesheet: hides sidebar, header, controls; content prints full-width
|
|
21
|
+
- Format badges display with angle brackets (`<email>`) and accent color
|
|
22
|
+
- Responsive builder layout: stacks vertically on mobile (<768px)
|
|
23
|
+
|
|
3
24
|
## [0.1.10] - 2026-05-09
|
|
4
25
|
|
|
5
26
|
### Redoc-style UX improvements
|
|
@@ -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,7 +16,8 @@
|
|
|
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.
|
|
19
|
+
<span v-if="field.prop.title && field.prop.title !== field.prop.name" class="field-title-badge">{{ field.prop.title }}</span>
|
|
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>
|
|
22
23
|
<span v-if="field.prop.const != null" class="const-badge font-mono">const: {{ field.prop.const }}</span>
|
|
@@ -206,17 +207,17 @@
|
|
|
206
207
|
<div class="builder-preview">
|
|
207
208
|
<div class="preview-inner">
|
|
208
209
|
<div class="preview-toolbar">
|
|
209
|
-
<span class="toolbar-label
|
|
210
|
+
<span class="toolbar-label">JSON Preview</span>
|
|
210
211
|
<div class="toolbar-actions">
|
|
211
|
-
<button class="btn btn-
|
|
212
|
-
<button class="btn btn-
|
|
213
|
-
<button class="btn btn-
|
|
212
|
+
<button class="btn btn-sm btn-dark-panel" @click="expandAllJson">Expand all</button>
|
|
213
|
+
<button class="btn btn-sm btn-dark-panel" @click="collapseAllJson">Collapse all</button>
|
|
214
|
+
<button class="btn btn-sm btn-dark-panel copy-btn-wrap" @click="copyJson">
|
|
214
215
|
Copy
|
|
215
216
|
<span v-if="copied" class="copy-tooltip">Copied!</span>
|
|
216
217
|
</button>
|
|
217
218
|
</div>
|
|
218
219
|
</div>
|
|
219
|
-
<pre ref="jsonBlockRef" class="json-block" @click="handleJsonClick" v-html="highlightedJson"></pre>
|
|
220
|
+
<pre ref="jsonBlockRef" class="json-block" @click="handleJsonClick" @dblclick="selectJsonBlock" v-html="highlightedJson"></pre>
|
|
220
221
|
</div>
|
|
221
222
|
</div>
|
|
222
223
|
</div>
|
|
@@ -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()
|
|
@@ -391,6 +393,18 @@ function handleJsonClick(event: MouseEvent) {
|
|
|
391
393
|
}
|
|
392
394
|
}
|
|
393
395
|
|
|
396
|
+
function selectJsonBlock() {
|
|
397
|
+
const el = jsonBlockRef.value
|
|
398
|
+
if (!el) return
|
|
399
|
+
const range = document.createRange()
|
|
400
|
+
range.selectNodeContents(el)
|
|
401
|
+
const selection = window.getSelection()
|
|
402
|
+
if (selection) {
|
|
403
|
+
selection.removeAllRanges()
|
|
404
|
+
selection.addRange(range)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
394
408
|
const MAX_PATTERN_LEN = 45
|
|
395
409
|
|
|
396
410
|
function typeBadgeClass(prop: SpaProperty): string {
|
|
@@ -425,11 +439,11 @@ function togglePattern(pattern: string) {
|
|
|
425
439
|
}
|
|
426
440
|
|
|
427
441
|
async function copyJson() {
|
|
428
|
-
|
|
429
|
-
|
|
442
|
+
const ok = await copyToClipboard(outputJson.value)
|
|
443
|
+
if (ok) {
|
|
430
444
|
copied.value = true
|
|
431
445
|
setTimeout(() => { copied.value = false }, 2000)
|
|
432
|
-
}
|
|
446
|
+
}
|
|
433
447
|
}
|
|
434
448
|
</script>
|
|
435
449
|
|
|
@@ -551,11 +565,19 @@ async function copyJson() {
|
|
|
551
565
|
|
|
552
566
|
.field-format-badge {
|
|
553
567
|
font-size: 10px;
|
|
554
|
-
color: var(--
|
|
555
|
-
background: var(--
|
|
568
|
+
color: var(--color-accent);
|
|
569
|
+
background: var(--color-accent-alpha);
|
|
556
570
|
padding: 1px 5px;
|
|
557
571
|
border-radius: var(--radius-sm);
|
|
558
572
|
font-family: var(--font-mono);
|
|
573
|
+
font-weight: 500;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.field-title-badge {
|
|
577
|
+
font-size: 10px;
|
|
578
|
+
font-style: italic;
|
|
579
|
+
color: var(--text-secondary);
|
|
580
|
+
flex-shrink: 0;
|
|
559
581
|
}
|
|
560
582
|
|
|
561
583
|
.deprecated-badge {
|
|
@@ -988,6 +1010,16 @@ async function copyJson() {
|
|
|
988
1010
|
.builder-preview {
|
|
989
1011
|
position: sticky;
|
|
990
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;
|
|
991
1023
|
}
|
|
992
1024
|
|
|
993
1025
|
.preview-inner {
|
|
@@ -1000,6 +1032,7 @@ async function copyJson() {
|
|
|
1000
1032
|
display: flex;
|
|
1001
1033
|
align-items: center;
|
|
1002
1034
|
justify-content: space-between;
|
|
1035
|
+
color: var(--panel-dark-muted);
|
|
1003
1036
|
}
|
|
1004
1037
|
|
|
1005
1038
|
.toolbar-label {
|
|
@@ -1011,9 +1044,18 @@ async function copyJson() {
|
|
|
1011
1044
|
padding: var(--space-1) var(--space-2);
|
|
1012
1045
|
}
|
|
1013
1046
|
|
|
1047
|
+
.btn-dark-panel {
|
|
1048
|
+
color: var(--panel-dark-muted);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
.btn-dark-panel:hover {
|
|
1052
|
+
color: var(--panel-dark-text);
|
|
1053
|
+
background: rgba(255, 255, 255, 0.08);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1014
1056
|
.json-block {
|
|
1015
|
-
background: var(--bg
|
|
1016
|
-
border: 1px solid var(--
|
|
1057
|
+
background: var(--panel-dark-bg);
|
|
1058
|
+
border: 1px solid var(--panel-dark-bg);
|
|
1017
1059
|
border-radius: var(--radius-md);
|
|
1018
1060
|
padding: var(--space-4);
|
|
1019
1061
|
overflow-x: auto;
|
|
@@ -1023,7 +1065,7 @@ async function copyJson() {
|
|
|
1023
1065
|
max-height: 70vh;
|
|
1024
1066
|
overflow-y: auto;
|
|
1025
1067
|
font-family: var(--font-mono);
|
|
1026
|
-
color: var(--text
|
|
1068
|
+
color: var(--panel-dark-text);
|
|
1027
1069
|
}
|
|
1028
1070
|
|
|
1029
1071
|
.json-block :deep(ul) {
|
|
@@ -1038,7 +1080,7 @@ async function copyJson() {
|
|
|
1038
1080
|
|
|
1039
1081
|
.json-block :deep(.jv-toggle) {
|
|
1040
1082
|
background: none;
|
|
1041
|
-
border: 1px solid var(--
|
|
1083
|
+
border: 1px solid var(--panel-dark-muted);
|
|
1042
1084
|
border-radius: 2px;
|
|
1043
1085
|
cursor: pointer;
|
|
1044
1086
|
width: 14px;
|
|
@@ -1064,7 +1106,7 @@ async function copyJson() {
|
|
|
1064
1106
|
|
|
1065
1107
|
.json-block :deep(.jv-ellipsis) {
|
|
1066
1108
|
display: none;
|
|
1067
|
-
color: var(--
|
|
1109
|
+
color: var(--panel-dark-muted);
|
|
1068
1110
|
font-size: var(--text-xs);
|
|
1069
1111
|
font-style: italic;
|
|
1070
1112
|
margin-left: 4px;
|
|
@@ -1079,43 +1121,40 @@ async function copyJson() {
|
|
|
1079
1121
|
}
|
|
1080
1122
|
|
|
1081
1123
|
.json-block :deep(.jv-key) {
|
|
1082
|
-
color: var(--
|
|
1124
|
+
color: var(--panel-dark-key);
|
|
1083
1125
|
}
|
|
1084
1126
|
|
|
1085
1127
|
.json-block :deep(.jv-punct) {
|
|
1086
|
-
color: var(--
|
|
1128
|
+
color: var(--panel-dark-muted);
|
|
1087
1129
|
}
|
|
1088
1130
|
|
|
1089
1131
|
.json-block :deep(.jv-string) {
|
|
1090
|
-
color: var(--
|
|
1132
|
+
color: var(--panel-dark-string);
|
|
1091
1133
|
}
|
|
1092
1134
|
|
|
1093
1135
|
.json-block :deep(.jv-number) {
|
|
1094
|
-
color: var(--
|
|
1136
|
+
color: var(--panel-dark-number);
|
|
1095
1137
|
}
|
|
1096
1138
|
|
|
1097
1139
|
.json-block :deep(.jv-boolean) {
|
|
1098
|
-
color: var(--
|
|
1140
|
+
color: var(--panel-dark-boolean);
|
|
1099
1141
|
}
|
|
1100
1142
|
|
|
1101
1143
|
.json-block :deep(.jv-null) {
|
|
1102
|
-
color: var(--
|
|
1144
|
+
color: var(--panel-dark-null);
|
|
1103
1145
|
font-style: italic;
|
|
1104
1146
|
}
|
|
1105
1147
|
|
|
1106
1148
|
.json-block :deep(.jv-link) {
|
|
1107
|
-
color: var(--
|
|
1149
|
+
color: var(--panel-dark-string);
|
|
1108
1150
|
text-decoration: underline;
|
|
1109
1151
|
}
|
|
1110
1152
|
|
|
1111
1153
|
.json-block :deep(.jv-row:hover) {
|
|
1112
|
-
background:
|
|
1154
|
+
background: rgba(255, 255, 255, 0.06);
|
|
1113
1155
|
border-radius: 2px;
|
|
1114
1156
|
}
|
|
1115
1157
|
|
|
1116
|
-
:root[data-theme="dark"] .json-block :deep(.jv-key) { color: var(--color-primary-light); }
|
|
1117
|
-
:root[data-theme="dark"] .json-block :deep(.jv-string) { color: var(--color-teal); }
|
|
1118
|
-
|
|
1119
1158
|
.toolbar-actions {
|
|
1120
1159
|
display: flex;
|
|
1121
1160
|
gap: var(--space-1);
|
|
@@ -1128,7 +1167,7 @@ async function copyJson() {
|
|
|
1128
1167
|
|
|
1129
1168
|
.copy-tooltip {
|
|
1130
1169
|
position: absolute;
|
|
1131
|
-
bottom: calc(100% +
|
|
1170
|
+
bottom: calc(100% + 8px);
|
|
1132
1171
|
left: 50%;
|
|
1133
1172
|
transform: translateX(-50%);
|
|
1134
1173
|
background: var(--bg-elevated);
|
|
@@ -1143,6 +1182,16 @@ async function copyJson() {
|
|
|
1143
1182
|
animation: tooltipFade var(--transition-slow);
|
|
1144
1183
|
}
|
|
1145
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
|
+
|
|
1146
1195
|
@keyframes tooltipFade {
|
|
1147
1196
|
from { opacity: 0; transform: translateX(-50%) translateY(2px); }
|
|
1148
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
|
+
}
|
data/frontend/src/style.css
CHANGED
|
@@ -96,6 +96,15 @@
|
|
|
96
96
|
--type-array-bg: rgba(139, 92, 246, 0.1);
|
|
97
97
|
--type-null: #A8A29E;
|
|
98
98
|
--type-null-bg: rgba(168, 162, 158, 0.1);
|
|
99
|
+
|
|
100
|
+
--panel-dark-bg: #263238;
|
|
101
|
+
--panel-dark-text: #e8edf4;
|
|
102
|
+
--panel-dark-muted: #8b9db5;
|
|
103
|
+
--panel-dark-key: #80cbc4;
|
|
104
|
+
--panel-dark-string: #a5d6a7;
|
|
105
|
+
--panel-dark-number: #ffcc80;
|
|
106
|
+
--panel-dark-boolean: #90caf9;
|
|
107
|
+
--panel-dark-null: #8b9db5;
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
:root[data-theme="dark"] {
|
|
@@ -143,6 +152,15 @@
|
|
|
143
152
|
--type-array-bg: rgba(167, 139, 250, 0.15);
|
|
144
153
|
--type-null: #6b7a8f;
|
|
145
154
|
--type-null-bg: rgba(107, 122, 143, 0.15);
|
|
155
|
+
|
|
156
|
+
--panel-dark-bg: #1e2a35;
|
|
157
|
+
--panel-dark-text: #dce4f0;
|
|
158
|
+
--panel-dark-muted: #7b8fa8;
|
|
159
|
+
--panel-dark-key: #4db6ac;
|
|
160
|
+
--panel-dark-string: #81c784;
|
|
161
|
+
--panel-dark-number: #ffb74d;
|
|
162
|
+
--panel-dark-boolean: #64b5f6;
|
|
163
|
+
--panel-dark-null: #7b8fa8;
|
|
146
164
|
}
|
|
147
165
|
|
|
148
166
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -233,6 +251,19 @@ button { font-family: inherit; font-size: inherit; cursor: pointer; border: none
|
|
|
233
251
|
}
|
|
234
252
|
}
|
|
235
253
|
|
|
254
|
+
@media print {
|
|
255
|
+
.sidebar, .sidebar-overlay, .app-header,
|
|
256
|
+
.view-toggle, .schema-actions,
|
|
257
|
+
.ctrl-expand, .field-check,
|
|
258
|
+
.source-toolbar, .copy-btn-wrap,
|
|
259
|
+
.section-actions { display: none !important; }
|
|
260
|
+
.app-layout { grid-template-columns: 1fr !important; }
|
|
261
|
+
.app-main { padding: 0 !important; max-width: 100% !important; }
|
|
262
|
+
.card, .def-card { break-inside: avoid; box-shadow: none; border: 1px solid #ccc; }
|
|
263
|
+
.field-row { break-inside: avoid; }
|
|
264
|
+
body { color: #000; background: #fff; }
|
|
265
|
+
}
|
|
266
|
+
|
|
236
267
|
.text-muted { color: var(--text-muted); }
|
|
237
268
|
.text-secondary { color: var(--text-secondary); }
|
|
238
269
|
.font-mono { font-family: var(--font-mono); }
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
</div>
|
|
144
144
|
<div class="source-code-wrapper">
|
|
145
145
|
<div class="source-lines" aria-hidden="true"><span v-for="n in sourceLineCount" :key="n">{{ n }}</span></div>
|
|
146
|
-
<pre class="source-pre"><code v-html="highlightedSource"></code></pre>
|
|
146
|
+
<pre class="source-pre" @dblclick="selectSourceBlock"><code v-html="highlightedSource"></code></pre>
|
|
147
147
|
</div>
|
|
148
148
|
</div>
|
|
149
149
|
<div v-else class="source-empty">
|
|
@@ -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
|
|
|
@@ -285,6 +288,18 @@ function syntaxHighlight(json: string): string {
|
|
|
285
288
|
})
|
|
286
289
|
}
|
|
287
290
|
|
|
291
|
+
function selectSourceBlock() {
|
|
292
|
+
const el = document.querySelector('.source-pre') as HTMLElement | null
|
|
293
|
+
if (!el) return
|
|
294
|
+
const range = document.createRange()
|
|
295
|
+
range.selectNodeContents(el)
|
|
296
|
+
const selection = window.getSelection()
|
|
297
|
+
if (selection) {
|
|
298
|
+
selection.removeAllRanges()
|
|
299
|
+
selection.addRange(range)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
288
303
|
function selectSchema(name: string) {
|
|
289
304
|
schemaStore.selectSchema(name)
|
|
290
305
|
}
|
|
@@ -600,7 +615,7 @@ watch(() => schemaStore.selectedItemKey, (key) => {
|
|
|
600
615
|
|
|
601
616
|
.copy-tooltip {
|
|
602
617
|
position: absolute;
|
|
603
|
-
bottom: calc(100% +
|
|
618
|
+
bottom: calc(100% + 8px);
|
|
604
619
|
left: 50%;
|
|
605
620
|
transform: translateX(-50%);
|
|
606
621
|
background: var(--bg-elevated);
|
|
@@ -615,6 +630,16 @@ watch(() => schemaStore.selectedItemKey, (key) => {
|
|
|
615
630
|
animation: tooltipFade var(--transition-slow);
|
|
616
631
|
}
|
|
617
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
|
+
|
|
618
643
|
@keyframes tooltipFade {
|
|
619
644
|
from { opacity: 0; transform: translateX(-50%) translateY(2px); }
|
|
620
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
|