akhaliq HF Staff commited on
Commit
4c41870
Β·
1 Parent(s): db0634f

Add live web preview: code bars with copy + sandboxed iframe preview for HTML/CSS/JS

Browse files
Files changed (1) hide show
  1. index.html +223 -2
index.html CHANGED
@@ -327,10 +327,11 @@
327
  background: #050505 !important;
328
  border: 1px solid var(--border) !important;
329
  border-left: 2px solid var(--green) !important;
330
- border-radius: 6px !important;
331
  padding: 14px 16px !important;
332
  overflow-x: auto;
333
- margin: 12px 0 !important;
 
334
  }
335
 
336
  .msg-body pre code {
@@ -342,6 +343,115 @@
342
  line-height: 1.7 !important;
343
  }
344
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  .msg-body blockquote {
346
  border-left: 2px solid var(--green);
347
  padding-left: 14px;
@@ -911,12 +1021,123 @@ function makeRenderer(el) {
911
  finish() {
912
  el.id = "";
913
  el.innerHTML = marked.parse(buf);
 
914
  scrollBot();
915
  return buf;
916
  }
917
  };
918
  }
919
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
920
  /* ── Send ── */
921
  window.handleSend = async function() {
922
  if (isGenerating) return;
 
327
  background: #050505 !important;
328
  border: 1px solid var(--border) !important;
329
  border-left: 2px solid var(--green) !important;
330
+ border-radius: 6px 6px 0 0 !important;
331
  padding: 14px 16px !important;
332
  overflow-x: auto;
333
+ margin: 12px 0 0 !important;
334
+ position: relative;
335
  }
336
 
337
  .msg-body pre code {
 
343
  line-height: 1.7 !important;
344
  }
345
 
346
+ /* Code block action bar */
347
+ .code-bar {
348
+ display: flex;
349
+ align-items: center;
350
+ justify-content: space-between;
351
+ background: #080808;
352
+ border: 1px solid var(--border);
353
+ border-top: none;
354
+ border-radius: 0 0 6px 6px;
355
+ padding: 5px 12px;
356
+ margin: 0 0 12px;
357
+ gap: 8px;
358
+ }
359
+
360
+ .code-lang {
361
+ font-size: 10px;
362
+ color: var(--text-muted);
363
+ letter-spacing: 0.06em;
364
+ text-transform: uppercase;
365
+ font-family: var(--mono);
366
+ }
367
+
368
+ .code-actions { display: flex; gap: 6px; }
369
+
370
+ .code-btn {
371
+ background: transparent;
372
+ border: 1px solid var(--border);
373
+ border-radius: 4px;
374
+ color: var(--text-dim);
375
+ font-family: var(--mono);
376
+ font-size: 10px;
377
+ padding: 3px 9px;
378
+ cursor: pointer;
379
+ transition: all 0.15s;
380
+ letter-spacing: 0.04em;
381
+ }
382
+ .code-btn:hover {
383
+ border-color: var(--border-hi);
384
+ color: var(--green);
385
+ background: var(--green-dim);
386
+ }
387
+ .code-btn.active {
388
+ border-color: var(--green);
389
+ color: var(--green);
390
+ background: var(--green-dim);
391
+ }
392
+
393
+ /* Live preview iframe panel */
394
+ .preview-panel {
395
+ display: none;
396
+ border: 1px solid var(--border-hi);
397
+ border-top: 2px solid var(--green);
398
+ border-radius: 0 0 8px 8px;
399
+ margin: -12px 0 12px;
400
+ overflow: hidden;
401
+ background: #fff;
402
+ position: relative;
403
+ }
404
+ .preview-panel.open { display: block; }
405
+
406
+ .preview-topbar {
407
+ display: flex;
408
+ align-items: center;
409
+ justify-content: space-between;
410
+ padding: 5px 12px;
411
+ background: #0d0d0d;
412
+ border-bottom: 1px solid var(--border);
413
+ }
414
+
415
+ .preview-url {
416
+ font-size: 10px;
417
+ color: var(--text-muted);
418
+ font-family: var(--mono);
419
+ letter-spacing: 0.03em;
420
+ }
421
+
422
+ .preview-resize {
423
+ display: flex;
424
+ gap: 4px;
425
+ }
426
+
427
+ .resize-btn {
428
+ background: transparent;
429
+ border: 1px solid var(--border);
430
+ border-radius: 3px;
431
+ color: var(--text-muted);
432
+ font-family: var(--mono);
433
+ font-size: 9px;
434
+ padding: 2px 7px;
435
+ cursor: pointer;
436
+ transition: all 0.15s;
437
+ }
438
+ .resize-btn:hover { color: var(--green); border-color: var(--border-hi); }
439
+ .resize-btn.active { color: var(--green); border-color: var(--green); background: var(--green-dim); }
440
+
441
+ .preview-frame-wrap {
442
+ width: 100%;
443
+ overflow: hidden;
444
+ transition: height 0.3s ease;
445
+ }
446
+
447
+ .preview-iframe {
448
+ width: 100%;
449
+ height: 100%;
450
+ border: none;
451
+ display: block;
452
+ background: #fff;
453
+ }
454
+
455
  .msg-body blockquote {
456
  border-left: 2px solid var(--green);
457
  padding-left: 14px;
 
1021
  finish() {
1022
  el.id = "";
1023
  el.innerHTML = marked.parse(buf);
1024
+ injectCodeBars(el);
1025
  scrollBot();
1026
  return buf;
1027
  }
1028
  };
1029
  }
1030
 
1031
+ /* ── Code bar + live preview injector ── */
1032
+ function injectCodeBars(container) {
1033
+ const pres = container.querySelectorAll("pre");
1034
+ pres.forEach(pre => {
1035
+ // Avoid double-processing
1036
+ if (pre.nextElementSibling && pre.nextElementSibling.classList.contains("code-bar")) return;
1037
+
1038
+ const codeEl = pre.querySelector("code");
1039
+ const rawCode = codeEl ? codeEl.textContent : pre.textContent;
1040
+
1041
+ // Detect language from class (e.g. language-html)
1042
+ const cls = codeEl ? codeEl.className : "";
1043
+ const langMatch = cls.match(/language-(\w+)/);
1044
+ const lang = langMatch ? langMatch[1].toLowerCase() : "";
1045
+
1046
+ // Determine if this is a web-previewable block
1047
+ const isHtml = lang === "html" || lang === "svg";
1048
+ const isPreviewable = isHtml ||
1049
+ (!lang && rawCode.trim().startsWith("<") && rawCode.includes("</"));
1050
+
1051
+ // Build action bar
1052
+ const bar = document.createElement("div");
1053
+ bar.className = "code-bar";
1054
+
1055
+ let previewBtn = "";
1056
+ if (isPreviewable || lang === "css" || lang === "js" || lang === "javascript") {
1057
+ previewBtn = `<button class="code-btn preview-toggle" title="Render preview">β–Ά Preview</button>`;
1058
+ }
1059
+
1060
+ bar.innerHTML = `
1061
+ <span class="code-lang">${lang || "code"}</span>
1062
+ <div class="code-actions">
1063
+ ${previewBtn}
1064
+ <button class="code-btn copy-btn" title="Copy">Copy</button>
1065
+ </div>
1066
+ `;
1067
+
1068
+ pre.after(bar);
1069
+
1070
+ // Copy button
1071
+ const copyBtn = bar.querySelector(".copy-btn");
1072
+ copyBtn.addEventListener("click", () => {
1073
+ navigator.clipboard.writeText(rawCode).then(() => {
1074
+ copyBtn.textContent = "Copied!";
1075
+ copyBtn.classList.add("active");
1076
+ setTimeout(() => { copyBtn.textContent = "Copy"; copyBtn.classList.remove("active"); }, 1800);
1077
+ });
1078
+ });
1079
+
1080
+ // Preview toggle
1081
+ const pvBtn = bar.querySelector(".preview-toggle");
1082
+ if (!pvBtn) return;
1083
+
1084
+ // Build preview panel (hidden by default)
1085
+ const panel = document.createElement("div");
1086
+ panel.className = "preview-panel";
1087
+
1088
+ // Compose the HTML to render
1089
+ let renderSrc = rawCode;
1090
+ if (lang === "css") {
1091
+ renderSrc = `<!DOCTYPE html><html><head><style>${rawCode}</style></head><body><p style='font-family:sans-serif;padding:20px;color:#333'>CSS preview β€” add HTML to see layout</p></body></html>`;
1092
+ } else if (lang === "js" || lang === "javascript") {
1093
+ renderSrc = `<!DOCTYPE html><html><body><script>${rawCode}<\/script></body></html>`;
1094
+ }
1095
+
1096
+ panel.innerHTML = `
1097
+ <div class="preview-topbar">
1098
+ <span class="preview-url">β–£ live preview β€” sandbox</span>
1099
+ <div class="preview-resize">
1100
+ <button class="resize-btn" data-h="260">S</button>
1101
+ <button class="resize-btn active" data-h="420">M</button>
1102
+ <button class="resize-btn" data-h="620">L</button>
1103
+ <button class="resize-btn" data-h="900">XL</button>
1104
+ </div>
1105
+ </div>
1106
+ <div class="preview-frame-wrap" style="height:420px">
1107
+ <iframe class="preview-iframe"
1108
+ sandbox="allow-scripts allow-forms allow-modals"
1109
+ srcdoc=""
1110
+ ></iframe>
1111
+ </div>
1112
+ `;
1113
+
1114
+ bar.after(panel);
1115
+
1116
+ // Resize buttons
1117
+ panel.querySelectorAll(".resize-btn").forEach(rb => {
1118
+ rb.addEventListener("click", () => {
1119
+ panel.querySelectorAll(".resize-btn").forEach(b => b.classList.remove("active"));
1120
+ rb.classList.add("active");
1121
+ panel.querySelector(".preview-frame-wrap").style.height = rb.dataset.h + "px";
1122
+ });
1123
+ });
1124
+
1125
+ let loaded = false;
1126
+ pvBtn.addEventListener("click", () => {
1127
+ const isOpen = panel.classList.toggle("open");
1128
+ pvBtn.textContent = isOpen ? "β–Ό Hide" : "β–Ά Preview";
1129
+ pvBtn.classList.toggle("active", isOpen);
1130
+
1131
+ if (isOpen && !loaded) {
1132
+ loaded = true;
1133
+ const iframe = panel.querySelector(".preview-iframe");
1134
+ iframe.srcdoc = renderSrc;
1135
+ }
1136
+ scrollBot();
1137
+ });
1138
+ });
1139
+ }
1140
+
1141
  /* ── Send ── */
1142
  window.handleSend = async function() {
1143
  if (isGenerating) return;