From 74f16bfd0db5c1eee8cb0af523406dd23a1d385d Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Tue, 2 Jun 2026 10:47:59 +0100 Subject: [PATCH] Fix for pasting --- frontend/src/js/raw.js | 753 +++++++++++++++++++----------------- frontend/src/js/terminal.js | 2 +- frontend/src/js/utils.js | 2 +- 3 files changed, 404 insertions(+), 353 deletions(-) diff --git a/frontend/src/js/raw.js b/frontend/src/js/raw.js index 77b74b7..a31571e 100644 --- a/frontend/src/js/raw.js +++ b/frontend/src/js/raw.js @@ -1,30 +1,31 @@ -(function() { -/** - * Nexus Terminal — RAW TCP data viewer. - * Provides text, hex, and split display modes for raw socket data, - * along with a send bar, pause/resume, and capture saving. - */ +(function () { + /** + * Nexus Terminal — RAW TCP data viewer. + * Provides text, hex, and split display modes for raw socket data, + * along with a send bar, pause/resume, and capture saving. + */ -const { createElement, escapeHtml, formatTimestamp, formatBytes } = window.NexusTerminal; + const { createElement, escapeHtml, formatTimestamp, formatBytes } = + window.NexusTerminal; -/** - * Maximum number of data entries retained in the scrollback buffer. - * @type {number} - */ -const MAX_BUFFER_ENTRIES = 5000; + /** + * Maximum number of data entries retained in the scrollback buffer. + * @type {number} + */ + const MAX_BUFFER_ENTRIES = 5000; -class RawViewer { + class RawViewer { /** * @param {App} app */ constructor(app) { - this.app = app; + this.app = app; - /** - * Map of sessionId → viewer state. - * @type {Map} - */ - this.viewers = new Map(); + /** + * Map of sessionId → viewer state. + * @type {Map} + */ + this.viewers = new Map(); } // ── Public API ─────────────────────────────────────────────────── @@ -36,71 +37,85 @@ class RawViewer { * @param {Object} profile */ createViewer(sessionId, containerId, profile) { - const container = document.getElementById(containerId); - if (!container) return; + const container = document.getElementById(containerId); + if (!container) return; - const state = { - mode: 'text', // text | hex | split - paused: false, - buffer: [], - totalBytes: 0, - sendFormat: 'text', // text | hex | escaped - }; + const state = { + mode: "text", // text | hex | split + paused: false, + buffer: [], + totalBytes: 0, + sendFormat: "text", // text | hex | escaped + }; - // ── Toolbar ────────────────────────────────────────────── - const toolbar = createElement('div', 'raw-toolbar'); + // ── Toolbar ────────────────────────────────────────────── + const toolbar = createElement("div", "raw-toolbar"); - // Mode toggle - const modeGroup = createElement('div', 'raw-mode-group'); - for (const mode of ['text', 'hex', 'split']) { - const btn = createElement('button', `raw-mode-btn ${mode === state.mode ? 'active' : ''}`, { - textContent: mode.charAt(0).toUpperCase() + mode.slice(1), - dataset: { mode }, - }); - btn.addEventListener('click', () => this._setMode(sessionId, mode)); - modeGroup.appendChild(btn); - } - toolbar.appendChild(modeGroup); + // Mode toggle + const modeGroup = createElement("div", "raw-mode-group"); + for (const mode of ["text", "hex", "split"]) { + const btn = createElement( + "button", + `raw-mode-btn ${mode === state.mode ? "active" : ""}`, + { + textContent: mode.charAt(0).toUpperCase() + mode.slice(1), + dataset: { mode }, + }, + ); + btn.addEventListener("click", () => this._setMode(sessionId, mode)); + modeGroup.appendChild(btn); + } + toolbar.appendChild(modeGroup); - // Controls - const controls = createElement('div', 'raw-controls'); + // Controls + const controls = createElement("div", "raw-controls"); - const pauseBtn = createElement('button', 'btn btn-sm btn-outline raw-pause', { textContent: '⏸ Pause' }); - pauseBtn.addEventListener('click', () => this.togglePause(sessionId)); - controls.appendChild(pauseBtn); + const pauseBtn = createElement( + "button", + "btn btn-sm btn-outline raw-pause", + { textContent: "⏸ Pause" }, + ); + pauseBtn.addEventListener("click", () => this.togglePause(sessionId)); + controls.appendChild(pauseBtn); - const clearBtn = createElement('button', 'btn btn-sm btn-outline', { textContent: '✕ Clear' }); - clearBtn.addEventListener('click', () => this.clearBuffer(sessionId)); - controls.appendChild(clearBtn); + const clearBtn = createElement("button", "btn btn-sm btn-outline", { + textContent: "✕ Clear", + }); + clearBtn.addEventListener("click", () => this.clearBuffer(sessionId)); + controls.appendChild(clearBtn); - const saveBtn = createElement('button', 'btn btn-sm btn-outline', { textContent: '💾 Save' }); - saveBtn.addEventListener('click', () => this.saveCapture(sessionId)); - controls.appendChild(saveBtn); + const saveBtn = createElement("button", "btn btn-sm btn-outline", { + textContent: "💾 Save", + }); + saveBtn.addEventListener("click", () => this.saveCapture(sessionId)); + controls.appendChild(saveBtn); - // Byte counter - const counter = createElement('span', 'raw-counter', { textContent: '0 B' }); - controls.appendChild(counter); + // Byte counter + const counter = createElement("span", "raw-counter", { + textContent: "0 B", + }); + controls.appendChild(counter); - toolbar.appendChild(controls); - container.appendChild(toolbar); + toolbar.appendChild(controls); + container.appendChild(toolbar); - // ── Data display ───────────────────────────────────────── - const display = createElement('div', 'raw-display'); - container.appendChild(display); + // ── Data display ───────────────────────────────────────── + const display = createElement("div", "raw-display"); + container.appendChild(display); - // ── Send bar ───────────────────────────────────────────── - const sendBar = this._renderSendBar(sessionId, profile); - container.appendChild(sendBar); + // ── Send bar ───────────────────────────────────────────── + const sendBar = this._renderSendBar(sessionId, profile); + container.appendChild(sendBar); - this.viewers.set(sessionId, { - ...state, - container, - display, - toolbar, - pauseBtn, - counter, - profile, - }); + this.viewers.set(sessionId, { + ...state, + container, + display, + toolbar, + pauseBtn, + counter, + profile, + }); } /** @@ -108,45 +123,45 @@ class RawViewer { * @param {Object} payload - { sessionID, data, direction, timestamp } */ handleData(payload) { - const viewer = this.viewers.get(payload.sessionID); - if (!viewer) return; + const viewer = this.viewers.get(payload.sessionID); + if (!viewer) return; - // Decode base64 payload - let rawStr; - try { - rawStr = atob(payload.data); - } catch (_) { - rawStr = payload.data || ''; - } + // Decode base64 payload + let rawStr; + try { + rawStr = atob(payload.data); + } catch (_) { + rawStr = payload.data || ""; + } - const bytes = new Uint8Array(rawStr.length); - for (let i = 0; i < rawStr.length; i++) { - bytes[i] = rawStr.charCodeAt(i); - } + const bytes = new Uint8Array(rawStr.length); + for (let i = 0; i < rawStr.length; i++) { + bytes[i] = rawStr.charCodeAt(i); + } - const entry = { - direction: payload.direction || 'recv', // 'send' | 'recv' - timestamp: payload.timestamp ? new Date(payload.timestamp) : new Date(), - data: rawStr, - bytes, - }; + const entry = { + direction: payload.direction || "recv", // 'send' | 'recv' + timestamp: payload.timestamp ? new Date(payload.timestamp) : new Date(), + data: rawStr, + bytes, + }; - viewer.buffer.push(entry); - viewer.totalBytes += bytes.length; + viewer.buffer.push(entry); + viewer.totalBytes += bytes.length; - // Trim buffer if it exceeds the maximum - if (viewer.buffer.length > MAX_BUFFER_ENTRIES) { - viewer.buffer.splice(0, viewer.buffer.length - MAX_BUFFER_ENTRIES); - } + // Trim buffer if it exceeds the maximum + if (viewer.buffer.length > MAX_BUFFER_ENTRIES) { + viewer.buffer.splice(0, viewer.buffer.length - MAX_BUFFER_ENTRIES); + } - // Update counter - viewer.counter.textContent = formatBytes(viewer.totalBytes); + // Update counter + viewer.counter.textContent = formatBytes(viewer.totalBytes); - // Render unless paused - if (!viewer.paused) { - this._renderEntry(viewer, entry); - this._scrollToBottom(viewer); - } + // Render unless paused + if (!viewer.paused) { + this._renderEntry(viewer, entry); + this._scrollToBottom(viewer); + } } /** @@ -154,17 +169,17 @@ class RawViewer { * @param {string} sessionId */ togglePause(sessionId) { - const viewer = this.viewers.get(sessionId); - if (!viewer) return; + const viewer = this.viewers.get(sessionId); + if (!viewer) return; - viewer.paused = !viewer.paused; - viewer.pauseBtn.textContent = viewer.paused ? '▶ Resume' : '⏸ Pause'; - viewer.pauseBtn.classList.toggle('active', viewer.paused); + viewer.paused = !viewer.paused; + viewer.pauseBtn.textContent = viewer.paused ? "▶ Resume" : "⏸ Pause"; + viewer.pauseBtn.classList.toggle("active", viewer.paused); - // On resume, flush any buffered entries - if (!viewer.paused) { - this._renderAllEntries(viewer); - } + // On resume, flush any buffered entries + if (!viewer.paused) { + this._renderAllEntries(viewer); + } } /** @@ -172,13 +187,13 @@ class RawViewer { * @param {string} sessionId */ clearBuffer(sessionId) { - const viewer = this.viewers.get(sessionId); - if (!viewer) return; + const viewer = this.viewers.get(sessionId); + if (!viewer) return; - viewer.buffer = []; - viewer.totalBytes = 0; - viewer.display.innerHTML = ''; - viewer.counter.textContent = '0 B'; + viewer.buffer = []; + viewer.totalBytes = 0; + viewer.display.innerHTML = ""; + viewer.counter.textContent = "0 B"; } /** @@ -186,24 +201,24 @@ class RawViewer { * @param {string} sessionId */ async saveCapture(sessionId) { - const viewer = this.viewers.get(sessionId); - if (!viewer || viewer.buffer.length === 0) { - this.app.notifications.warning('Nothing to save'); - return; - } + const viewer = this.viewers.get(sessionId); + if (!viewer || viewer.buffer.length === 0) { + this.app.notifications.warning("Nothing to save"); + return; + } - try { - const lines = viewer.buffer.map((e) => { - const ts = formatTimestamp(e.timestamp); - const dir = e.direction === 'send' ? 'TX' : 'RX'; - return `[${ts}] [${dir}] ${e.data}`; - }); - const content = lines.join('\n'); - await window.go.api.Service.SaveSessionCapture(sessionId, content); - this.app.notifications.success('Capture saved'); - } catch (err) { - this.app.notifications.error('Save failed: ' + err); - } + try { + const lines = viewer.buffer.map((e) => { + const ts = formatTimestamp(e.timestamp); + const dir = e.direction === "send" ? "TX" : "RX"; + return `[${ts}] [${dir}] ${e.data}`; + }); + const content = lines.join("\n"); + await window.go.api.Service.SaveSessionCapture(sessionId, content); + this.app.notifications.success("Capture saved"); + } catch (err) { + this.app.notifications.error("Save failed: " + err); + } } /** @@ -211,49 +226,50 @@ class RawViewer { * @param {string} sessionId */ async send(sessionId) { - const viewer = this.viewers.get(sessionId); - if (!viewer) return; + const viewer = this.viewers.get(sessionId); + if (!viewer) return; - const input = viewer.container.querySelector('.raw-send-input'); - const formatSelect = viewer.container.querySelector('.raw-send-format'); - if (!input) return; + const input = viewer.container.querySelector(".raw-send-input"); + const formatSelect = viewer.container.querySelector(".raw-send-format"); + if (!input) return; - const rawValue = input.value; - if (!rawValue) return; + const rawValue = input.value; + if (!rawValue) return; - const format = formatSelect?.value || 'text'; - let dataToSend; + const format = formatSelect?.value || "text"; + let dataToSend; - switch (format) { - case 'hex': - dataToSend = this._hexToString(rawValue); - break; - case 'escaped': - dataToSend = this._unescapeString(rawValue); - break; - default: - dataToSend = rawValue; - } + switch (format) { + case "hex": + dataToSend = this._hexToString(rawValue); + break; + case "escaped": + dataToSend = this._unescapeString(rawValue); + break; + default: + dataToSend = rawValue; + } - try { - // For hex/escaped, data is already raw bytes (0-255) so btoa is correct. - // For text mode, UTF-8 encode so non-ASCII characters are transmitted properly. - const b64 = (format === 'hex' || format === 'escaped') - ? btoa(dataToSend) - : window.NexusTerminal.utf8ToBase64(dataToSend); - await window.go.api.Service.WriteSession(sessionId, b64); - input.value = ''; + try { + // For hex/escaped, data is already raw bytes (0-255) so btoa is correct. + // For text mode, UTF-8 encode so non-ASCII characters are transmitted properly. + const b64 = + format === "hex" || format === "escaped" + ? btoa(dataToSend) + : window.NexusTerminal.utf8ToBase64(dataToSend); + await window.go.api.Service.WriteSession(sessionId, b64); + input.value = ""; - // Also show in viewer as sent data - this.handleData({ - sessionID: sessionId, - data: b64, - direction: 'send', - timestamp: new Date().toISOString(), - }); - } catch (err) { - this.app.notifications.error('Send failed: ' + err); - } + // Also show in viewer as sent data + this.handleData({ + sessionID: sessionId, + data: b64, + direction: "send", + timestamp: new Date().toISOString(), + }); + } catch (err) { + this.app.notifications.error("Send failed: " + err); + } } /** @@ -261,7 +277,7 @@ class RawViewer { * @param {string} sessionId */ destroyViewer(sessionId) { - this.viewers.delete(sessionId); + this.viewers.delete(sessionId); } // ── Rendering ──────────────────────────────────────────────────── @@ -274,76 +290,97 @@ class RawViewer { * @private */ _renderSendBar(sessionId, profile) { - const bar = createElement('div', 'raw-send-bar'); + const bar = createElement("div", "raw-send-bar"); - // Format selector - const formatSelect = createElement('select', 'form-select form-select-sm raw-send-format'); - for (const [value, label] of [['text', 'Text'], ['hex', 'Hex'], ['escaped', 'Escaped']]) { - const opt = createElement('option', '', { value, textContent: label }); - formatSelect.appendChild(opt); - } - bar.appendChild(formatSelect); + // Format selector + const formatSelect = createElement( + "select", + "form-select form-select-sm raw-send-format", + ); + for (const [value, label] of [ + ["text", "Text"], + ["hex", "Hex"], + ["escaped", "Escaped"], + ]) { + const opt = createElement("option", "", { value, textContent: label }); + formatSelect.appendChild(opt); + } + bar.appendChild(formatSelect); - // Input - const input = createElement('input', 'form-input raw-send-input', { - type: 'text', - placeholder: 'Type data to send…', + // Input + const input = createElement("input", "form-input raw-send-input", { + type: "text", + placeholder: "Type data to send…", + }); + input.addEventListener("keydown", (e) => { + if (e.key === "Enter") this.send(sessionId); + }); + bar.appendChild(input); + + // Quick-insert buttons for common control bytes + const quickBtns = createElement("div", "raw-quick-btns"); + const controlBytes = [ + { label: "NUL", value: "\\x00" }, + { label: "CR", value: "\\r" }, + { label: "LF", value: "\\n" }, + { label: "ETX", value: "\\x03" }, + { label: "EOT", value: "\\x04" }, + ]; + for (const cb of controlBytes) { + const btn = createElement("button", "btn btn-xs btn-outline", { + textContent: cb.label, + title: cb.value, }); - input.addEventListener('keydown', (e) => { - if (e.key === 'Enter') this.send(sessionId); + btn.addEventListener("click", () => { + input.value += cb.value; + input.focus(); }); - bar.appendChild(input); + quickBtns.appendChild(btn); + } + bar.appendChild(quickBtns); - // Quick-insert buttons for common control bytes - const quickBtns = createElement('div', 'raw-quick-btns'); - const controlBytes = [ - { label: 'NUL', value: '\\x00' }, - { label: 'CR', value: '\\r' }, - { label: 'LF', value: '\\n' }, - { label: 'ETX', value: '\\x03' }, - { label: 'EOT', value: '\\x04' }, - ]; - for (const cb of controlBytes) { - const btn = createElement('button', 'btn btn-xs btn-outline', { textContent: cb.label, title: cb.value }); - btn.addEventListener('click', () => { - input.value += cb.value; - input.focus(); - }); - quickBtns.appendChild(btn); + // Payload preset dropdown + const presets = profile?.raw?.payloadPresets || []; + if (presets.length > 0) { + const presetSelect = createElement( + "select", + "form-select form-select-sm raw-preset-select", + ); + const defaultOpt = createElement("option", "", { + value: "", + textContent: "Presets…", + }); + presetSelect.appendChild(defaultOpt); + for (const ps of presets) { + const opt = createElement("option", "", { + value: ps.payload, + textContent: ps.name, + }); + opt.dataset.format = ps.format; + presetSelect.appendChild(opt); } - bar.appendChild(quickBtns); - - // Payload preset dropdown - const presets = profile?.raw?.payloadPresets || []; - if (presets.length > 0) { - const presetSelect = createElement('select', 'form-select form-select-sm raw-preset-select'); - const defaultOpt = createElement('option', '', { value: '', textContent: 'Presets…' }); - presetSelect.appendChild(defaultOpt); - for (const ps of presets) { - const opt = createElement('option', '', { value: ps.payload, textContent: ps.name }); - opt.dataset.format = ps.format; - presetSelect.appendChild(opt); + presetSelect.addEventListener("change", () => { + if (presetSelect.value) { + input.value = presetSelect.value; + const selOpt = presetSelect.selectedOptions[0]; + if (selOpt?.dataset.format) { + formatSelect.value = selOpt.dataset.format; } - presetSelect.addEventListener('change', () => { - if (presetSelect.value) { - input.value = presetSelect.value; - const selOpt = presetSelect.selectedOptions[0]; - if (selOpt?.dataset.format) { - formatSelect.value = selOpt.dataset.format; - } - presetSelect.value = ''; - input.focus(); - } - }); - bar.appendChild(presetSelect); - } + presetSelect.value = ""; + input.focus(); + } + }); + bar.appendChild(presetSelect); + } - // Send button - const sendBtn = createElement('button', 'btn btn-sm btn-primary', { textContent: 'Send' }); - sendBtn.addEventListener('click', () => this.send(sessionId)); - bar.appendChild(sendBtn); + // Send button + const sendBtn = createElement("button", "btn btn-sm btn-primary", { + textContent: "Send", + }); + sendBtn.addEventListener("click", () => this.send(sessionId)); + bar.appendChild(sendBtn); - return bar; + return bar; } /** @@ -353,18 +390,18 @@ class RawViewer { * @private */ _renderEntry(viewer, entry) { - let el; - switch (viewer.mode) { - case 'hex': - el = this._renderHexView(entry); - break; - case 'split': - el = this._renderSplitView(entry, viewer.profile); - break; - default: - el = this._renderTextView(entry, viewer.profile); - } - viewer.display.appendChild(el); + let el; + switch (viewer.mode) { + case "hex": + el = this._renderHexView(entry); + break; + case "split": + el = this._renderSplitView(entry, viewer.profile); + break; + default: + el = this._renderTextView(entry, viewer.profile); + } + viewer.display.appendChild(el); } /** @@ -373,11 +410,11 @@ class RawViewer { * @private */ _renderAllEntries(viewer) { - viewer.display.innerHTML = ''; - for (const entry of viewer.buffer) { - this._renderEntry(viewer, entry); - } - this._scrollToBottom(viewer); + viewer.display.innerHTML = ""; + for (const entry of viewer.buffer) { + this._renderEntry(viewer, entry); + } + this._scrollToBottom(viewer); } /** @@ -388,21 +425,24 @@ class RawViewer { * @private */ _renderTextView(entry, profile) { - const row = createElement('div', `raw-entry raw-entry-${entry.direction}`); - const raw = profile?.raw || {}; + const row = createElement( + "div", + `raw-entry raw-entry-${entry.direction}`, + ); + const raw = profile?.raw || {}; - let prefix = ''; - if (raw.showTimestamps) { - prefix += `[${formatTimestamp(entry.timestamp)}] `; - } - if (raw.showDirection) { - prefix += entry.direction === 'send' ? '[TX] ' : '[RX] '; - } + let prefix = ""; + if (raw.showTimestamps) { + prefix += `[${formatTimestamp(entry.timestamp)}] `; + } + if (raw.showDirection) { + prefix += entry.direction === "send" ? "[TX] " : "[RX] "; + } - const pre = createElement('pre', 'raw-text'); - pre.textContent = prefix + entry.data; - row.appendChild(pre); - return row; + const pre = createElement("pre", "raw-text"); + pre.textContent = prefix + entry.data; + row.appendChild(pre); + return row; } /** @@ -412,11 +452,14 @@ class RawViewer { * @private */ _renderHexView(entry) { - const row = createElement('div', `raw-entry raw-entry-${entry.direction}`); - const pre = createElement('pre', 'raw-hex'); - pre.textContent = this.formatHexDump(entry.bytes); - row.appendChild(pre); - return row; + const row = createElement( + "div", + `raw-entry raw-entry-${entry.direction}`, + ); + const pre = createElement("pre", "raw-hex"); + pre.textContent = this.formatHexDump(entry.bytes); + row.appendChild(pre); + return row; } /** @@ -427,30 +470,34 @@ class RawViewer { * @private */ _renderSplitView(entry, profile) { - const row = createElement('div', `raw-entry raw-entry-split raw-entry-${entry.direction}`); - const raw = profile?.raw || {}; + const row = createElement( + "div", + `raw-entry raw-entry-split raw-entry-${entry.direction}`, + ); + const raw = profile?.raw || {}; - // Direction/timestamp header - const header = createElement('div', 'raw-split-header'); - let headerText = ''; - if (raw.showTimestamps) headerText += formatTimestamp(entry.timestamp) + ' '; - headerText += entry.direction === 'send' ? '▲ TX' : '▼ RX'; - header.textContent = headerText; - row.appendChild(header); + // Direction/timestamp header + const header = createElement("div", "raw-split-header"); + let headerText = ""; + if (raw.showTimestamps) + headerText += formatTimestamp(entry.timestamp) + " "; + headerText += entry.direction === "send" ? "▲ TX" : "▼ RX"; + header.textContent = headerText; + row.appendChild(header); - // Split panes - const panes = createElement('div', 'raw-split-panes'); + // Split panes + const panes = createElement("div", "raw-split-panes"); - const textPane = createElement('pre', 'raw-text raw-split-text'); - textPane.textContent = entry.data; - panes.appendChild(textPane); + const textPane = createElement("pre", "raw-text raw-split-text"); + textPane.textContent = entry.data; + panes.appendChild(textPane); - const hexPane = createElement('pre', 'raw-hex raw-split-hex'); - hexPane.textContent = this.formatHexDump(entry.bytes); - panes.appendChild(hexPane); + const hexPane = createElement("pre", "raw-hex raw-split-hex"); + hexPane.textContent = this.formatHexDump(entry.bytes); + panes.appendChild(hexPane); - row.appendChild(panes); - return row; + row.appendChild(panes); + return row; } /** @@ -460,39 +507,39 @@ class RawViewer { * @returns {string} */ formatHexDump(bytes) { - const lines = []; - const bytesPerLine = 16; + const lines = []; + const bytesPerLine = 16; - for (let offset = 0; offset < bytes.length; offset += bytesPerLine) { - const chunk = bytes.slice(offset, offset + bytesPerLine); + for (let offset = 0; offset < bytes.length; offset += bytesPerLine) { + const chunk = bytes.slice(offset, offset + bytesPerLine); - // Offset column - const offsetStr = offset.toString(16).padStart(8, '0'); + // Offset column + const offsetStr = offset.toString(16).padStart(8, "0"); - // Hex column - const hexParts = []; - for (let i = 0; i < bytesPerLine; i++) { - if (i < chunk.length) { - hexParts.push(chunk[i].toString(16).padStart(2, '0')); - } else { - hexParts.push(' '); - } - // Extra space after 8th byte for readability - if (i === 7) hexParts.push(''); - } - const hexStr = hexParts.join(' '); + // Hex column + const hexParts = []; + for (let i = 0; i < bytesPerLine; i++) { + if (i < chunk.length) { + hexParts.push(chunk[i].toString(16).padStart(2, "0")); + } else { + hexParts.push(" "); + } + // Extra space after 8th byte for readability + if (i === 7) hexParts.push(""); + } + const hexStr = hexParts.join(" "); - // ASCII column - let ascii = ''; - for (let i = 0; i < chunk.length; i++) { - const b = chunk[i]; - ascii += (b >= 0x20 && b <= 0x7e) ? String.fromCharCode(b) : '.'; - } - - lines.push(`${offsetStr} ${hexStr} |${ascii}|`); + // ASCII column + let ascii = ""; + for (let i = 0; i < chunk.length; i++) { + const b = chunk[i]; + ascii += b >= 0x20 && b <= 0x7e ? String.fromCharCode(b) : "."; } - return lines.join('\n'); + lines.push(`${offsetStr} ${hexStr} |${ascii}|`); + } + + return lines.join("\n"); } // ── Internal helpers ───────────────────────────────────────────── @@ -504,17 +551,17 @@ class RawViewer { * @private */ _setMode(sessionId, mode) { - const viewer = this.viewers.get(sessionId); - if (!viewer) return; + const viewer = this.viewers.get(sessionId); + if (!viewer) return; - viewer.mode = mode; + viewer.mode = mode; - // Update active button - viewer.toolbar.querySelectorAll('.raw-mode-btn').forEach((btn) => { - btn.classList.toggle('active', btn.dataset.mode === mode); - }); + // Update active button + viewer.toolbar.querySelectorAll(".raw-mode-btn").forEach((btn) => { + btn.classList.toggle("active", btn.dataset.mode === mode); + }); - this._renderAllEntries(viewer); + this._renderAllEntries(viewer); } /** @@ -523,9 +570,9 @@ class RawViewer { * @private */ _scrollToBottom(viewer) { - requestAnimationFrame(() => { - viewer.display.scrollTop = viewer.display.scrollHeight; - }); + requestAnimationFrame(() => { + viewer.display.scrollTop = viewer.display.scrollHeight; + }); } /** @@ -535,12 +582,12 @@ class RawViewer { * @private */ _hexToString(hex) { - const cleaned = hex.replace(/\s+/g, ''); - let str = ''; - for (let i = 0; i < cleaned.length; i += 2) { - str += String.fromCharCode(parseInt(cleaned.substring(i, i + 2), 16)); - } - return str; + const cleaned = hex.replace(/\s+/g, ""); + let str = ""; + for (let i = 0; i < cleaned.length; i += 2) { + str += String.fromCharCode(parseInt(cleaned.substring(i, i + 2), 16)); + } + return str; } /** @@ -550,25 +597,29 @@ class RawViewer { * @private */ _unescapeString(str) { - return str.replace(/\\(n|r|t|0|x[0-9a-fA-F]{2}|\\)/g, (match, seq) => { - switch (seq) { - case 'n': return '\n'; - case 'r': return '\r'; - case 't': return '\t'; - case '0': return '\0'; - case '\\': return '\\'; - default: - // \\xHH - if (seq.startsWith('x')) { - return String.fromCharCode(parseInt(seq.slice(1), 16)); - } - return match; + return str.replace(/\\(n|r|t|0|x[0-9a-fA-F]{2}|\\)/g, (match, seq) => { + switch (seq) { + case "n": + return "\n"; + case "r": + return "\r"; + case "t": + return "\t"; + case "0": + return "\0"; + case "\\": + return "\\"; + default: + // \\xHH + if (seq.startsWith("x")) { + return String.fromCharCode(parseInt(seq.slice(1), 16)); } - }); + return match; + } + }); } -} - -window.NexusTerminal = window.NexusTerminal || {}; -window.NexusTerminal.RawViewer = RawViewer; + } + window.NexusTerminal = window.NexusTerminal || {}; + window.NexusTerminal.RawViewer = RawViewer; })(); diff --git a/frontend/src/js/terminal.js b/frontend/src/js/terminal.js index 3ef6a9f..0b411bc 100644 --- a/frontend/src/js/terminal.js +++ b/frontend/src/js/terminal.js @@ -14,7 +14,7 @@ */ function utf8ToBase64(str) { const bytes = new TextEncoder().encode(str); - let binary = ''; + let binary = ""; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); } diff --git a/frontend/src/js/utils.js b/frontend/src/js/utils.js index 3afaaaa..ff7f526 100644 --- a/frontend/src/js/utils.js +++ b/frontend/src/js/utils.js @@ -171,7 +171,7 @@ */ function utf8ToBase64(str) { const bytes = new TextEncoder().encode(str); - let binary = ''; + let binary = ""; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); }