Alyafeai commited on
Commit
4f2765c
·
1 Parent(s): 3cc4b4e

feat(ui): pretty-print and syntax-highlight JSON gold answers

Browse files
Files changed (1) hide show
  1. frontend/leaderboard.html +132 -5
frontend/leaderboard.html CHANGED
@@ -620,6 +620,78 @@
620
  .modal-content {
621
  animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
622
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623
  </style>
624
 
625
  <!-- Header & Search -->
@@ -1018,11 +1090,26 @@
1018
  const s = String(v).trim();
1019
  return s ? s : "Unknown";
1020
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1021
  const formatDetailValue = (v, emptyAsUnknown = true) => {
1022
  if (v === undefined || v === null) return emptyAsUnknown ? "Unknown" : "";
1023
  if (typeof v === "string") {
1024
  const s = v.trim();
1025
- return s ? s : (emptyAsUnknown ? "Unknown" : "");
 
1026
  }
1027
  if (Array.isArray(v)) {
1028
  if (!v.length) return emptyAsUnknown ? "Unknown" : "";
@@ -1057,17 +1144,57 @@
1057
  .replace(/\"/g, "&quot;")
1058
  .replace(/'/g, "&#039;");
1059
  const DETAIL_CELL_LIMIT = 260;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1060
  const expandableTextHtml = (value, idBase, limit = DETAIL_CELL_LIMIT) => {
1061
  const raw = String(value ?? "");
1062
  if (!raw) return "";
 
 
 
 
1063
  if (raw.length <= limit) {
1064
- return `<div class="whitespace-pre-wrap break-words">${escapeHtml(raw)}</div>`;
1065
  }
1066
  const short = `${raw.slice(0, limit).trimEnd()}...`;
1067
  return `
1068
  <div>
1069
- <div id="${idBase}-short" class="whitespace-pre-wrap break-words">${escapeHtml(short)}</div>
1070
- <div id="${idBase}-full" class="hidden whitespace-pre-wrap break-words">${escapeHtml(raw)}</div>
1071
  <button type="button" onclick="window.toggleExpandText('${idBase}', this)" class="mt-2 inline-flex items-center gap-1 px-2 py-1 rounded-md border border-indigo-300 dark:border-indigo-600 bg-indigo-50 dark:bg-indigo-900/30 text-xs font-bold text-indigo-700 dark:text-indigo-300 hover:bg-indigo-100 dark:hover:bg-indigo-900/50 transition-colors">Expand</button>
1072
  </div>
1073
  `;
@@ -2286,4 +2413,4 @@ window.toggleExpandText = function (idBase, btn) {
2286
 
2287
  </body>
2288
 
2289
- </html>
 
620
  .modal-content {
621
  animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
622
  }
623
+
624
+ .json-render-block {
625
+ white-space: pre-wrap;
626
+ word-break: break-word;
627
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
628
+ font-size: 11px;
629
+ line-height: 1.55;
630
+ border-radius: 0.75rem;
631
+ border: 1px solid #dbe4f0;
632
+ background:
633
+ linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(241, 245, 249, 0.96)),
634
+ linear-gradient(135deg, rgba(14, 165, 233, 0.05), rgba(16, 185, 129, 0.04));
635
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
636
+ padding: 0.65rem 0.8rem;
637
+ }
638
+
639
+ .dark .json-render-block {
640
+ border-color: #243047;
641
+ background:
642
+ linear-gradient(180deg, rgba(15, 23, 42, 0.96), rgba(2, 6, 23, 0.98)),
643
+ linear-gradient(135deg, rgba(56, 189, 248, 0.08), rgba(52, 211, 153, 0.05));
644
+ box-shadow: inset 0 1px 0 rgba(148, 163, 184, 0.08);
645
+ }
646
+
647
+ .json-token-punctuation {
648
+ color: #64748b;
649
+ }
650
+
651
+ .json-token-key {
652
+ color: #0f766e;
653
+ }
654
+
655
+ .json-token-string {
656
+ color: #b45309;
657
+ }
658
+
659
+ .json-token-number {
660
+ color: #1d4ed8;
661
+ }
662
+
663
+ .json-token-boolean {
664
+ color: #7c3aed;
665
+ }
666
+
667
+ .json-token-null {
668
+ color: #be185d;
669
+ font-style: italic;
670
+ }
671
+
672
+ .dark .json-token-punctuation {
673
+ color: #94a3b8;
674
+ }
675
+
676
+ .dark .json-token-key {
677
+ color: #5eead4;
678
+ }
679
+
680
+ .dark .json-token-string {
681
+ color: #fdba74;
682
+ }
683
+
684
+ .dark .json-token-number {
685
+ color: #93c5fd;
686
+ }
687
+
688
+ .dark .json-token-boolean {
689
+ color: #c4b5fd;
690
+ }
691
+
692
+ .dark .json-token-null {
693
+ color: #f9a8d4;
694
+ }
695
  </style>
696
 
697
  <!-- Header & Search -->
 
1090
  const s = String(v).trim();
1091
  return s ? s : "Unknown";
1092
  };
1093
+ const tryFormatJsonString = (value) => {
1094
+ const s = String(value ?? "").trim();
1095
+ if (!s) return null;
1096
+ const looksLikeJson = (
1097
+ (s.startsWith("{") && s.endsWith("}")) ||
1098
+ (s.startsWith("[") && s.endsWith("]"))
1099
+ );
1100
+ if (!looksLikeJson) return null;
1101
+ try {
1102
+ return JSON.stringify(JSON.parse(s), null, 2);
1103
+ } catch {
1104
+ return null;
1105
+ }
1106
+ };
1107
  const formatDetailValue = (v, emptyAsUnknown = true) => {
1108
  if (v === undefined || v === null) return emptyAsUnknown ? "Unknown" : "";
1109
  if (typeof v === "string") {
1110
  const s = v.trim();
1111
+ if (!s) return emptyAsUnknown ? "Unknown" : "";
1112
+ return tryFormatJsonString(s) || s;
1113
  }
1114
  if (Array.isArray(v)) {
1115
  if (!v.length) return emptyAsUnknown ? "Unknown" : "";
 
1144
  .replace(/\"/g, "&quot;")
1145
  .replace(/'/g, "&#039;");
1146
  const DETAIL_CELL_LIMIT = 260;
1147
+ const JSON_TOKEN_RE = /"(?:\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*"(?=\s*:)|"(?:\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*"|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?|[{}\[\],:]/g;
1148
+ const isPrettyJsonText = (value) => {
1149
+ const s = String(value ?? "").trim();
1150
+ if (!s) return false;
1151
+ return (
1152
+ (s.startsWith("{") && s.endsWith("}")) ||
1153
+ (s.startsWith("[") && s.endsWith("]"))
1154
+ );
1155
+ };
1156
+ const syntaxHighlightJson = (text) => {
1157
+ let html = "";
1158
+ let lastIndex = 0;
1159
+ for (const match of text.matchAll(JSON_TOKEN_RE)) {
1160
+ const token = match[0];
1161
+ const idx = match.index ?? 0;
1162
+ html += escapeHtml(text.slice(lastIndex, idx));
1163
+ let cls = "json-token-punctuation";
1164
+ if (token.startsWith('"')) {
1165
+ const nextChar = text.slice(idx + token.length).trimStart().charAt(0);
1166
+ cls = nextChar === ":" ? "json-token-key" : "json-token-string";
1167
+ } else if (token === "true" || token === "false") {
1168
+ cls = "json-token-boolean";
1169
+ } else if (token === "null") {
1170
+ cls = "json-token-null";
1171
+ } else if (/^-?\d/.test(token)) {
1172
+ cls = "json-token-number";
1173
+ }
1174
+ html += `<span class="${cls}">${escapeHtml(token)}</span>`;
1175
+ lastIndex = idx + token.length;
1176
+ }
1177
+ html += escapeHtml(text.slice(lastIndex));
1178
+ return html;
1179
+ };
1180
+ const renderDetailText = (text, isJson) => (
1181
+ isJson ? syntaxHighlightJson(text) : escapeHtml(text)
1182
+ );
1183
  const expandableTextHtml = (value, idBase, limit = DETAIL_CELL_LIMIT) => {
1184
  const raw = String(value ?? "");
1185
  if (!raw) return "";
1186
+ const isJson = isPrettyJsonText(raw);
1187
+ const contentClass = isJson
1188
+ ? "json-render-block"
1189
+ : "whitespace-pre-wrap break-words";
1190
  if (raw.length <= limit) {
1191
+ return `<div class="${contentClass}">${renderDetailText(raw, isJson)}</div>`;
1192
  }
1193
  const short = `${raw.slice(0, limit).trimEnd()}...`;
1194
  return `
1195
  <div>
1196
+ <div id="${idBase}-short" class="${contentClass}">${renderDetailText(short, isJson)}</div>
1197
+ <div id="${idBase}-full" class="hidden ${contentClass}">${renderDetailText(raw, isJson)}</div>
1198
  <button type="button" onclick="window.toggleExpandText('${idBase}', this)" class="mt-2 inline-flex items-center gap-1 px-2 py-1 rounded-md border border-indigo-300 dark:border-indigo-600 bg-indigo-50 dark:bg-indigo-900/30 text-xs font-bold text-indigo-700 dark:text-indigo-300 hover:bg-indigo-100 dark:hover:bg-indigo-900/50 transition-colors">Expand</button>
1199
  </div>
1200
  `;
 
2413
 
2414
  </body>
2415
 
2416
+ </html>