const DEFAULT_LIGHT_THEME = "vendor/highlightjs/styles/github.min.css"; const DEFAULT_DARK_THEME = "vendor/highlightjs/styles/github-dark.min.css"; const INDENT_SIZE = 2; const MAX_STRING_LENGTH = 120; const THEME_FILES = [ "vendor/highlightjs/styles/1c-light.min.css", "vendor/highlightjs/styles/a11y-dark.min.css", "vendor/highlightjs/styles/a11y-light.min.css", "vendor/highlightjs/styles/agate.min.css", "vendor/highlightjs/styles/androidstudio.min.css", "vendor/highlightjs/styles/an-old-hope.min.css", "vendor/highlightjs/styles/arduino-light.min.css", "vendor/highlightjs/styles/arta.min.css", "vendor/highlightjs/styles/ascetic.min.css", "vendor/highlightjs/styles/atom-one-dark-reasonable.min.css", "vendor/highlightjs/styles/atom-one-dark.min.css", "vendor/highlightjs/styles/atom-one-light.min.css", "vendor/highlightjs/styles/brown-paper.min.css", "vendor/highlightjs/styles/codepen-embed.min.css", "vendor/highlightjs/styles/color-brewer.min.css", "vendor/highlightjs/styles/cybertopia-cherry.min.css", "vendor/highlightjs/styles/cybertopia-dimmer.min.css", "vendor/highlightjs/styles/cybertopia-icecap.min.css", "vendor/highlightjs/styles/cybertopia-saturated.min.css", "vendor/highlightjs/styles/dark.min.css", "vendor/highlightjs/styles/default.min.css", "vendor/highlightjs/styles/devibeans.min.css", "vendor/highlightjs/styles/docco.min.css", "vendor/highlightjs/styles/far.min.css", "vendor/highlightjs/styles/felipec.min.css", "vendor/highlightjs/styles/foundation.min.css", "vendor/highlightjs/styles/github-dark-dimmed.min.css", "vendor/highlightjs/styles/github-dark.min.css", "vendor/highlightjs/styles/github.min.css", "vendor/highlightjs/styles/gml.min.css", "vendor/highlightjs/styles/googlecode.min.css", "vendor/highlightjs/styles/gradient-dark.min.css", "vendor/highlightjs/styles/gradient-light.min.css", "vendor/highlightjs/styles/grayscale.min.css", "vendor/highlightjs/styles/hybrid.min.css", "vendor/highlightjs/styles/idea.min.css", "vendor/highlightjs/styles/intellij-light.min.css", "vendor/highlightjs/styles/ir-black.min.css", "vendor/highlightjs/styles/isbl-editor-dark.min.css", "vendor/highlightjs/styles/isbl-editor-light.min.css", "vendor/highlightjs/styles/kimbie-dark.min.css", "vendor/highlightjs/styles/kimbie-light.min.css", "vendor/highlightjs/styles/lightfair.min.css", "vendor/highlightjs/styles/lioshi.min.css", "vendor/highlightjs/styles/magula.min.css", "vendor/highlightjs/styles/mono-blue.min.css", "vendor/highlightjs/styles/monokai-sublime.min.css", "vendor/highlightjs/styles/monokai.min.css", "vendor/highlightjs/styles/night-owl.min.css", "vendor/highlightjs/styles/nnfx-dark.min.css", "vendor/highlightjs/styles/nnfx-light.min.css", "vendor/highlightjs/styles/nord.min.css", "vendor/highlightjs/styles/obsidian.min.css", "vendor/highlightjs/styles/panda-syntax-dark.min.css", "vendor/highlightjs/styles/panda-syntax-light.min.css", "vendor/highlightjs/styles/paraiso-dark.min.css", "vendor/highlightjs/styles/paraiso-light.min.css", "vendor/highlightjs/styles/pojoaque.min.css", "vendor/highlightjs/styles/purebasic.min.css", "vendor/highlightjs/styles/qtcreator-dark.min.css", "vendor/highlightjs/styles/qtcreator-light.min.css", "vendor/highlightjs/styles/rainbow.min.css", "vendor/highlightjs/styles/rose-pine-dawn.min.css", "vendor/highlightjs/styles/rose-pine-moon.min.css", "vendor/highlightjs/styles/rose-pine.min.css", "vendor/highlightjs/styles/routeros.min.css", "vendor/highlightjs/styles/school-book.min.css", "vendor/highlightjs/styles/shades-of-purple.min.css", "vendor/highlightjs/styles/srcery.min.css", "vendor/highlightjs/styles/stackoverflow-dark.min.css", "vendor/highlightjs/styles/stackoverflow-light.min.css", "vendor/highlightjs/styles/sunburst.min.css", "vendor/highlightjs/styles/tokyo-night-dark.min.css", "vendor/highlightjs/styles/tokyo-night-light.min.css", "vendor/highlightjs/styles/tomorrow-night-blue.min.css", "vendor/highlightjs/styles/tomorrow-night-bright.min.css", "vendor/highlightjs/styles/vs.min.css", "vendor/highlightjs/styles/vs2015.min.css", "vendor/highlightjs/styles/xcode.min.css", "vendor/highlightjs/styles/xt256.min.css", "vendor/highlightjs/styles/base16/3024.min.css", "vendor/highlightjs/styles/base16/apathy.min.css", "vendor/highlightjs/styles/base16/apprentice.min.css", "vendor/highlightjs/styles/base16/ashes.min.css", "vendor/highlightjs/styles/base16/atelier-cave-light.min.css", "vendor/highlightjs/styles/base16/atelier-cave.min.css", "vendor/highlightjs/styles/base16/atelier-dune-light.min.css", "vendor/highlightjs/styles/base16/atelier-dune.min.css", "vendor/highlightjs/styles/base16/atelier-estuary-light.min.css", "vendor/highlightjs/styles/base16/atelier-estuary.min.css", "vendor/highlightjs/styles/base16/atelier-forest-light.min.css", "vendor/highlightjs/styles/base16/atelier-forest.min.css", "vendor/highlightjs/styles/base16/atelier-heath-light.min.css", "vendor/highlightjs/styles/base16/atelier-heath.min.css", "vendor/highlightjs/styles/base16/atelier-lakeside-light.min.css", "vendor/highlightjs/styles/base16/atelier-lakeside.min.css", "vendor/highlightjs/styles/base16/atelier-plateau-light.min.css", "vendor/highlightjs/styles/base16/atelier-plateau.min.css", "vendor/highlightjs/styles/base16/atelier-savanna-light.min.css", "vendor/highlightjs/styles/base16/atelier-savanna.min.css", "vendor/highlightjs/styles/base16/atelier-seaside-light.min.css", "vendor/highlightjs/styles/base16/atelier-seaside.min.css", "vendor/highlightjs/styles/base16/atelier-sulphurpool-light.min.css", "vendor/highlightjs/styles/base16/atelier-sulphurpool.min.css", "vendor/highlightjs/styles/base16/atlas.min.css", "vendor/highlightjs/styles/base16/bespin.min.css", "vendor/highlightjs/styles/base16/black-metal-bathory.min.css", "vendor/highlightjs/styles/base16/black-metal-burzum.min.css", "vendor/highlightjs/styles/base16/black-metal-dark-funeral.min.css", "vendor/highlightjs/styles/base16/black-metal-gorgoroth.min.css", "vendor/highlightjs/styles/base16/black-metal-immortal.min.css", "vendor/highlightjs/styles/base16/black-metal-khold.min.css", "vendor/highlightjs/styles/base16/black-metal-marduk.min.css", "vendor/highlightjs/styles/base16/black-metal-mayhem.min.css", "vendor/highlightjs/styles/base16/black-metal-nile.min.css", "vendor/highlightjs/styles/base16/black-metal-venom.min.css", "vendor/highlightjs/styles/base16/black-metal.min.css", "vendor/highlightjs/styles/base16/bright.min.css", "vendor/highlightjs/styles/base16/brewer.min.css", "vendor/highlightjs/styles/base16/brogrammer.min.css", "vendor/highlightjs/styles/base16/brush-trees-dark.min.css", "vendor/highlightjs/styles/base16/brush-trees.min.css", "vendor/highlightjs/styles/base16/chalk.min.css", "vendor/highlightjs/styles/base16/circus.min.css", "vendor/highlightjs/styles/base16/classic-dark.min.css", "vendor/highlightjs/styles/base16/classic-light.min.css", "vendor/highlightjs/styles/base16/codeschool.min.css", "vendor/highlightjs/styles/base16/colors.min.css", "vendor/highlightjs/styles/base16/cupcake.min.css", "vendor/highlightjs/styles/base16/cupertino.min.css", "vendor/highlightjs/styles/base16/danqing.min.css", "vendor/highlightjs/styles/base16/darcula.min.css", "vendor/highlightjs/styles/base16/dark-violet.min.css", "vendor/highlightjs/styles/base16/darkmoss.min.css", "vendor/highlightjs/styles/base16/darktooth.min.css", "vendor/highlightjs/styles/base16/decaf.min.css", "vendor/highlightjs/styles/base16/default-dark.min.css", "vendor/highlightjs/styles/base16/default-light.min.css", "vendor/highlightjs/styles/base16/dirtysea.min.css", "vendor/highlightjs/styles/base16/dracula.min.css", "vendor/highlightjs/styles/base16/edge-dark.min.css", "vendor/highlightjs/styles/base16/edge-light.min.css", "vendor/highlightjs/styles/base16/eighties.min.css", "vendor/highlightjs/styles/base16/embers.min.css", "vendor/highlightjs/styles/base16/equilibrium-dark.min.css", "vendor/highlightjs/styles/base16/equilibrium-gray-dark.min.css", "vendor/highlightjs/styles/base16/equilibrium-gray-light.min.css", "vendor/highlightjs/styles/base16/equilibrium-light.min.css", "vendor/highlightjs/styles/base16/espresso.min.css", "vendor/highlightjs/styles/base16/eva-dim.min.css", "vendor/highlightjs/styles/base16/eva.min.css", "vendor/highlightjs/styles/base16/flat.min.css", "vendor/highlightjs/styles/base16/framer.min.css", "vendor/highlightjs/styles/base16/fruit-soda.min.css", "vendor/highlightjs/styles/base16/gigavolt.min.css", "vendor/highlightjs/styles/base16/google-dark.min.css", "vendor/highlightjs/styles/base16/google-light.min.css", "vendor/highlightjs/styles/base16/green-screen.min.css", "vendor/highlightjs/styles/base16/grayscale-dark.min.css", "vendor/highlightjs/styles/base16/grayscale-light.min.css", "vendor/highlightjs/styles/base16/gruvbox-dark-hard.min.css", "vendor/highlightjs/styles/base16/gruvbox-dark-medium.min.css", "vendor/highlightjs/styles/base16/gruvbox-dark-pale.min.css", "vendor/highlightjs/styles/base16/gruvbox-dark-soft.min.css", "vendor/highlightjs/styles/base16/gruvbox-light-hard.min.css", "vendor/highlightjs/styles/base16/gruvbox-light-medium.min.css", "vendor/highlightjs/styles/base16/gruvbox-light-soft.min.css", "vendor/highlightjs/styles/base16/hardcore.min.css", "vendor/highlightjs/styles/base16/harmonic16-dark.min.css", "vendor/highlightjs/styles/base16/harmonic16-light.min.css", "vendor/highlightjs/styles/base16/heetch-dark.min.css", "vendor/highlightjs/styles/base16/heetch-light.min.css", "vendor/highlightjs/styles/base16/helios.min.css", "vendor/highlightjs/styles/base16/hopscotch.min.css", "vendor/highlightjs/styles/base16/horizon-dark.min.css", "vendor/highlightjs/styles/base16/horizon-light.min.css", "vendor/highlightjs/styles/base16/humanoid-dark.min.css", "vendor/highlightjs/styles/base16/humanoid-light.min.css", "vendor/highlightjs/styles/base16/ia-dark.min.css", "vendor/highlightjs/styles/base16/ia-light.min.css", "vendor/highlightjs/styles/base16/icy-dark.min.css", "vendor/highlightjs/styles/base16/ir-black.min.css", "vendor/highlightjs/styles/base16/isotope.min.css", "vendor/highlightjs/styles/base16/kimber.min.css", "vendor/highlightjs/styles/base16/london-tube.min.css", "vendor/highlightjs/styles/base16/macintosh.min.css", "vendor/highlightjs/styles/base16/marrakesh.min.css", "vendor/highlightjs/styles/base16/material-darker.min.css", "vendor/highlightjs/styles/base16/material-lighter.min.css", "vendor/highlightjs/styles/base16/material-palenight.min.css", "vendor/highlightjs/styles/base16/material-vivid.min.css", "vendor/highlightjs/styles/base16/material.min.css", "vendor/highlightjs/styles/base16/materia.min.css", "vendor/highlightjs/styles/base16/mellow-purple.min.css", "vendor/highlightjs/styles/base16/mexico-light.min.css", "vendor/highlightjs/styles/base16/mocha.min.css", "vendor/highlightjs/styles/base16/monokai.min.css", "vendor/highlightjs/styles/base16/nebula.min.css", "vendor/highlightjs/styles/base16/nord.min.css", "vendor/highlightjs/styles/base16/nova.min.css", "vendor/highlightjs/styles/base16/ocean.min.css", "vendor/highlightjs/styles/base16/oceanicnext.min.css", "vendor/highlightjs/styles/base16/onedark.min.css", "vendor/highlightjs/styles/base16/one-light.min.css", "vendor/highlightjs/styles/base16/outrun-dark.min.css", "vendor/highlightjs/styles/base16/papercolor-dark.min.css", "vendor/highlightjs/styles/base16/papercolor-light.min.css", "vendor/highlightjs/styles/base16/paraiso.min.css", "vendor/highlightjs/styles/base16/pasque.min.css", "vendor/highlightjs/styles/base16/phd.min.css", "vendor/highlightjs/styles/base16/pico.min.css", "vendor/highlightjs/styles/base16/pop.min.css", "vendor/highlightjs/styles/base16/porple.min.css", "vendor/highlightjs/styles/base16/qualia.min.css", "vendor/highlightjs/styles/base16/railscasts.min.css", "vendor/highlightjs/styles/base16/rebecca.min.css", "vendor/highlightjs/styles/base16/ros-pine-dawn.min.css", "vendor/highlightjs/styles/base16/ros-pine-moon.min.css", "vendor/highlightjs/styles/base16/ros-pine.min.css", "vendor/highlightjs/styles/base16/sagelight.min.css", "vendor/highlightjs/styles/base16/sandcastle.min.css", "vendor/highlightjs/styles/base16/seti-ui.min.css", "vendor/highlightjs/styles/base16/shapeshifter.min.css", "vendor/highlightjs/styles/base16/silk-dark.min.css", "vendor/highlightjs/styles/base16/silk-light.min.css", "vendor/highlightjs/styles/base16/snazzy.min.css", "vendor/highlightjs/styles/base16/solar-flare-light.min.css", "vendor/highlightjs/styles/base16/solar-flare.min.css", "vendor/highlightjs/styles/base16/solarized-dark.min.css", "vendor/highlightjs/styles/base16/solarized-light.min.css", "vendor/highlightjs/styles/base16/spacemacs.min.css", "vendor/highlightjs/styles/base16/summercamp.min.css", "vendor/highlightjs/styles/base16/summerfruit-dark.min.css", "vendor/highlightjs/styles/base16/summerfruit-light.min.css", "vendor/highlightjs/styles/base16/synth-midnight-terminal-dark.min.css", "vendor/highlightjs/styles/base16/synth-midnight-terminal-light.min.css", "vendor/highlightjs/styles/base16/tango.min.css", "vendor/highlightjs/styles/base16/tender.min.css", "vendor/highlightjs/styles/base16/tomorrow-night.min.css", "vendor/highlightjs/styles/base16/tomorrow.min.css", "vendor/highlightjs/styles/base16/twilight.min.css", "vendor/highlightjs/styles/base16/unikitty-dark.min.css", "vendor/highlightjs/styles/base16/unikitty-light.min.css", "vendor/highlightjs/styles/base16/vulcan.min.css", "vendor/highlightjs/styles/base16/windows-10-light.min.css", "vendor/highlightjs/styles/base16/windows-10.min.css", "vendor/highlightjs/styles/base16/windows-95-light.min.css", "vendor/highlightjs/styles/base16/windows-95.min.css", "vendor/highlightjs/styles/base16/windows-high-contrast-light.min.css", "vendor/highlightjs/styles/base16/windows-high-contrast.min.css", "vendor/highlightjs/styles/base16/windows-nt-light.min.css", "vendor/highlightjs/styles/base16/windows-nt.min.css", "vendor/highlightjs/styles/base16/woodland.min.css", "vendor/highlightjs/styles/base16/xcode-dusk.min.css", "vendor/highlightjs/styles/base16/zenburn.min.css" ]; function ensureBaseStyles() { if (document.getElementById("json-formatter-style")) return; const style = document.createElement("style"); style.id = "json-formatter-style"; style.textContent = ` :root { --json-bg: #f6f8fa; --json-header-bg: #f6f8fa; --json-header-text: #1f2328; --json-muted: #57606a; --json-border: #d0d7de; --json-control-bg: #ffffff; --json-control-text: #1f2328; --json-line-number: #8c959f; --json-line-number-border: #d8dee4; --json-toggle-bg: #ffffff; --json-toggle-text: #57606a; --json-toggle-border: #d0d7de; --json-toggle-hover: #f3f4f6; } :root[data-json-theme="dark"] { --json-bg: #0d1117; --json-header-bg: #0d1117; --json-header-text: #c9d1d9; --json-muted: #8b949e; --json-border: #30363d; --json-control-bg: #161b22; --json-control-text: #c9d1d9; --json-line-number: #6e7681; --json-line-number-border: #30363d; --json-toggle-bg: #161b22; --json-toggle-text: #8b949e; --json-toggle-border: #30363d; --json-toggle-hover: #21262d; } * { box-sizing: border-box; } body { margin: 0; padding: 0; background: var(--json-bg); --json-ln-width: 0px; --json-toggle-width: 16px; } body.show-line-numbers { --json-ln-width: 48px; } .json-header { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 12px; background: var(--json-header-bg); color: var(--json-header-text); border-bottom: 1px solid var(--json-border); } .json-title { font-size: 12px; font-weight: 600; letter-spacing: 0.3px; text-transform: uppercase; color: var(--json-muted); } .json-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } .json-control { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--json-muted); } .json-control select { font: inherit; color: var(--json-control-text); background: var(--json-control-bg); border: 1px solid var(--json-border); border-radius: 6px; padding: 4px 8px; } .json-control input[type="checkbox"] { accent-color: var(--json-control-text); } .json-content { padding: 0 12px 16px; overflow-x: auto; } pre { margin: 0; white-space: pre; word-break: normal; overflow: visible; } pre code.hljs { display: block; font-size: 13px; line-height: 1.5; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; overflow-x: visible; padding: 0; } .line { position: relative; display: block; padding-left: calc(var(--json-ln-width) + var(--json-toggle-width) + 12px); white-space: pre; } body:not(.show-line-numbers) .line { padding-left: calc(var(--json-toggle-width) + 12px); } body.show-line-numbers .line::before { content: attr(data-line); position: absolute; left: 0; width: var(--json-ln-width); text-align: right; padding-right: 8px; color: var(--json-line-number); border-right: 1px solid var(--json-line-number-border); user-select: none; } .json-toggle { position: absolute; left: calc(var(--json-ln-width) + 4px); top: 2px; width: var(--json-toggle-width); height: var(--json-toggle-width); border: 1px solid var(--json-toggle-border); border-radius: 4px; background: var(--json-toggle-bg); color: var(--json-toggle-text); font-size: 11px; line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; padding: 0; } body:not(.show-line-numbers) .json-toggle { left: 4px; } .json-toggle:hover { background: var(--json-toggle-hover); } .json-code { white-space: pre; } .json-string { white-space: pre-wrap; word-break: break-word; } .json-block-summary { color: var(--json-muted); } a.json-link { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; } a.json-link:hover { text-decoration-thickness: 2px; filter: brightness(1.1); } `; const container = document.head || document.documentElement || document.body; if (container) container.appendChild(style); } function themeLabel(file) { const trimmed = file .replace(/^vendor\/highlightjs\/styles\//, "") .replace(/\.min\.css$/, "") .replace(/\.css$/, ""); const parts = trimmed.split("/"); const name = parts.pop() || trimmed; const base = name .split("-") .map((chunk) => chunk.length ? chunk[0].toUpperCase() + chunk.slice(1) : chunk ) .join(" "); if (parts[0] === "base16") return `Base16: ${base}`; return base; } function getThemeState() { return new Promise((resolve) => { chrome.storage.sync.get( { themeMode: null, theme: null, lightTheme: DEFAULT_LIGHT_THEME, darkTheme: DEFAULT_DARK_THEME, lineNumbers: false }, (data) => { resolve({ themeMode: data.themeMode || data.theme || "system", lightTheme: data.lightTheme || DEFAULT_LIGHT_THEME, darkTheme: data.darkTheme || DEFAULT_DARK_THEME, lineNumbers: Boolean(data.lineNumbers) }); } ); }); } function resolveMode(themeMode) { if (themeMode === "dark" || themeMode === "light") return themeMode; return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } function ensureThemeLink(themeFile) { const url = chrome.runtime.getURL(themeFile); let link = document.getElementById("json-highlight-theme"); if (!link) { link = document.createElement("link"); link.id = "json-highlight-theme"; link.rel = "stylesheet"; const container = document.head || document.documentElement || document.body; const baseStyle = document.getElementById("json-formatter-style"); if (container) { if (baseStyle && baseStyle.parentNode === container) { container.insertBefore(link, baseStyle); } else { container.appendChild(link); } } } if (link.href !== url) link.href = url; } function applyThemeState(state) { const resolved = resolveMode(state.themeMode); const cssFile = resolved === "dark" ? state.darkTheme : state.lightTheme; document.documentElement.dataset.jsonTheme = resolved; window.__jsonFormatterTheme = resolved; ensureThemeLink(cssFile); return resolved; } function populateThemeSelect(select) { const entries = THEME_FILES.map((file) => ({ file, label: themeLabel(file) })).sort((a, b) => a.label.localeCompare(b.label)); select.innerHTML = ""; entries.forEach((entry) => { const option = document.createElement("option"); option.value = entry.file; option.textContent = entry.label; select.appendChild(option); }); } function saveThemeState(state) { chrome.storage.sync.set({ themeMode: state.themeMode, lightTheme: state.lightTheme, darkTheme: state.darkTheme, lineNumbers: state.lineNumbers }); } let toggleCounter = 0; function nextToggleId(prefix) { toggleCounter += 1; return `${prefix}-${toggleCounter}`; } function createTokenSpan(className, text) { const span = document.createElement("span"); if (className) span.className = className; span.textContent = text; return span; } function createLine(indent, toggleId) { const line = document.createElement("span"); line.className = "line"; line.dataset.line = ""; let toggle = null; if (toggleId) { toggle = document.createElement("button"); toggle.type = "button"; toggle.className = "json-toggle"; toggle.dataset.toggleId = toggleId; toggle.textContent = "-"; line.appendChild(toggle); } const code = document.createElement("span"); code.className = "json-code"; line.appendChild(code); if (indent > 0) { code.appendChild(document.createTextNode(" ".repeat(indent))); } return { line, code, toggle }; } function appendKeyPrefix(code, key) { if (key === null || key === undefined) return; code.appendChild(createTokenSpan("hljs-attr", JSON.stringify(key))); code.appendChild(document.createTextNode(": ")); } function appendComma(code) { code.appendChild(document.createTextNode(",")); } function appendScalar(code, value, stringEntry) { if (typeof value === "string") { const span = createTokenSpan( "hljs-string json-string", JSON.stringify(value) ); code.appendChild(span); if (stringEntry) stringEntry.element = span; return; } if (typeof value === "number") { code.appendChild(createTokenSpan("hljs-number", String(value))); return; } if (typeof value === "boolean" || value === null) { code.appendChild(createTokenSpan("hljs-literal", String(value))); return; } code.appendChild(document.createTextNode(String(value))); } function appendValue(container, value, indent, key, shouldComma, registry) { if (Array.isArray(value)) { if (value.length === 0) { const lineData = createLine(indent, null); appendKeyPrefix(lineData.code, key); lineData.code.appendChild(document.createTextNode("[]")); if (shouldComma) appendComma(lineData.code); container.appendChild(lineData.line); return; } const toggleId = nextToggleId("block"); const openLine = createLine(indent, toggleId); appendKeyPrefix(openLine.code, key); openLine.code.appendChild(document.createTextNode("[")); const body = document.createElement("div"); body.className = "json-block-body"; value.forEach((item, index) => { appendValue( body, item, indent + INDENT_SIZE, null, index < value.length - 1, registry ); }); const closeLine = createLine(indent, null); closeLine.code.appendChild(document.createTextNode("]")); const summaryLine = createLine(indent, toggleId); summaryLine.line.classList.add("json-block-summary"); summaryLine.line.style.display = "none"; appendKeyPrefix(summaryLine.code, key); summaryLine.code.appendChild(document.createTextNode("[...]")); if (shouldComma) { appendComma(closeLine.code); appendComma(summaryLine.code); } const block = document.createElement("div"); block.className = "json-block"; block.appendChild(openLine.line); block.appendChild(body); block.appendChild(closeLine.line); block.appendChild(summaryLine.line); registry.set(toggleId, { type: "block", collapsed: false, openLine: openLine.line, closeLine: closeLine.line, summaryLine: summaryLine.line, body, toggles: [openLine.toggle, summaryLine.toggle] }); container.appendChild(block); return; } if (value && typeof value === "object") { const entries = Object.entries(value); if (entries.length === 0) { const lineData = createLine(indent, null); appendKeyPrefix(lineData.code, key); lineData.code.appendChild(document.createTextNode("{}")); if (shouldComma) appendComma(lineData.code); container.appendChild(lineData.line); return; } const toggleId = nextToggleId("block"); const openLine = createLine(indent, toggleId); appendKeyPrefix(openLine.code, key); openLine.code.appendChild(document.createTextNode("{")); const body = document.createElement("div"); body.className = "json-block-body"; entries.forEach(([entryKey, entryValue], index) => { appendValue( body, entryValue, indent + INDENT_SIZE, entryKey, index < entries.length - 1, registry ); }); const closeLine = createLine(indent, null); closeLine.code.appendChild(document.createTextNode("}")); const summaryLine = createLine(indent, toggleId); summaryLine.line.classList.add("json-block-summary"); summaryLine.line.style.display = "none"; appendKeyPrefix(summaryLine.code, key); summaryLine.code.appendChild(document.createTextNode("{...}")); if (shouldComma) { appendComma(closeLine.code); appendComma(summaryLine.code); } const block = document.createElement("div"); block.className = "json-block"; block.appendChild(openLine.line); block.appendChild(body); block.appendChild(closeLine.line); block.appendChild(summaryLine.line); registry.set(toggleId, { type: "block", collapsed: false, openLine: openLine.line, closeLine: closeLine.line, summaryLine: summaryLine.line, body, toggles: [openLine.toggle, summaryLine.toggle] }); container.appendChild(block); return; } const isString = typeof value === "string"; const canCollapse = isString && value.length > MAX_STRING_LENGTH; const stringToggleId = canCollapse ? nextToggleId("string") : null; const lineData = createLine(indent, stringToggleId); appendKeyPrefix(lineData.code, key); const stringEntry = canCollapse ? { type: "string", collapsed: false, startCollapsed: true, collapsedText: "\"...\"", element: null, toggle: lineData.toggle, fullHtml: null } : null; appendScalar(lineData.code, value, stringEntry); if (shouldComma) appendComma(lineData.code); container.appendChild(lineData.line); if (stringEntry && stringToggleId) { registry.set(stringToggleId, stringEntry); } } function refreshLineNumbers(container, enabled) { const lines = container.querySelectorAll(".line"); let lineNumber = 1; lines.forEach((line) => { if (line.style.display === "none") return; line.dataset.line = String(lineNumber); lineNumber += 1; }); if (!enabled) return; const maxDigits = String(Math.max(1, lineNumber - 1)).length; const width = Math.max(32, maxDigits * 8 + 12); document.body.style.setProperty("--json-ln-width", `${width}px`); } function updateToggleButtons(buttons, collapsed) { buttons.forEach((button) => { if (button) button.textContent = collapsed ? "+" : "-"; }); } function applyBlockCollapsed(entry, collapsed) { entry.collapsed = collapsed; entry.openLine.style.display = collapsed ? "none" : ""; entry.body.style.display = collapsed ? "none" : ""; entry.closeLine.style.display = collapsed ? "none" : ""; entry.summaryLine.style.display = collapsed ? "" : "none"; updateToggleButtons(entry.toggles, collapsed); } function applyStringCollapsed(entry, collapsed) { entry.collapsed = collapsed; if (collapsed) { entry.element.textContent = entry.collapsedText; } else if (entry.fullHtml) { entry.element.innerHTML = entry.fullHtml; } updateToggleButtons([entry.toggle], collapsed); } function escapeHtml(text) { return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function splitHighlightedHtmlLines(highlightedHtml) { const container = document.createElement("div"); container.innerHTML = highlightedHtml; const lines = []; let currentLine = document.createDocumentFragment(); let currentTarget = currentLine; let openDescriptors = []; let currentClones = []; function cloneDescriptor(desc) { const clone = document.createElement(desc.tagName); desc.attrs.forEach(([name, value]) => clone.setAttribute(name, value)); return clone; } function rebuildOpenElements() { currentClones = []; let target = currentLine; openDescriptors.forEach((desc) => { const clone = cloneDescriptor(desc); target.appendChild(clone); currentClones.push(clone); target = clone; }); currentTarget = target; } function startNewLine() { lines.push(currentLine); currentLine = document.createDocumentFragment(); rebuildOpenElements(); } function describeElement(node) { return { tagName: node.tagName.toLowerCase(), attrs: Array.from(node.attributes).map((attr) => [attr.name, attr.value]) }; } function enterElement(node) { const desc = describeElement(node); openDescriptors.push(desc); const clone = cloneDescriptor(desc); currentTarget.appendChild(clone); currentClones.push(clone); currentTarget = clone; } function exitElement() { openDescriptors.pop(); currentClones.pop(); currentTarget = currentClones.length ? currentClones[currentClones.length - 1] : currentLine; } function processNode(node) { if (node.nodeType === Node.TEXT_NODE) { const text = (node.nodeValue || "") .replace(/\r\n/g, "\n") .replace(/\r/g, "\n"); const parts = text.split("\n"); parts.forEach((part, index) => { if (part) { currentTarget.appendChild(document.createTextNode(part)); } if (index < parts.length - 1) { startNewLine(); } }); return; } if (node.nodeType === Node.ELEMENT_NODE) { enterElement(node); node.childNodes.forEach(processNode); exitElement(); } } container.childNodes.forEach(processNode); lines.push(currentLine); return lines; } function highlightText(rawText, language) { if (window.hljs && typeof hljs.highlight === "function") { return hljs.highlight(rawText, { language }).value; } return escapeHtml(rawText); } function splitRawLines(rawText) { return rawText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n"); } function getIndentCount(line) { let count = 0; for (let i = 0; i < line.length; i += 1) { const char = line[i]; if (char === " ") { count += 1; } else if (char === "\t") { count += 2; } else { break; } } return count; } function buildYamlGroups(lines) { const groups = []; const stack = []; function isIgnorable(line) { const trimmed = line.trim(); return trimmed === "" || trimmed.startsWith("#"); } function nextNonEmptyIndent(fromIndex) { for (let i = fromIndex; i < lines.length; i += 1) { if (isIgnorable(lines[i])) continue; return getIndentCount(lines[i]); } return null; } for (let i = 0; i < lines.length; i += 1) { if (isIgnorable(lines[i])) continue; const indent = getIndentCount(lines[i]); while (stack.length && indent <= stack[stack.length - 1].indent) { const group = stack.pop(); const end = i - 1; if (end > group.start) { groups.push({ start: group.start, end }); } } const nextIndent = nextNonEmptyIndent(i + 1); if (nextIndent !== null && nextIndent > indent) { stack.push({ start: i, indent }); } } const lastIndex = lines.length - 1; while (stack.length) { const group = stack.pop(); if (lastIndex > group.start) { groups.push({ start: group.start, end: lastIndex }); } } return groups; } function buildMarkdownGroups(lines) { const headings = []; lines.forEach((line, index) => { const match = line.match(/^\s*(#{1,6})\s+/); if (match) { headings.push({ index, level: match[1].length }); } }); const groups = []; headings.forEach((heading, i) => { let end = lines.length - 1; for (let j = i + 1; j < headings.length; j += 1) { if (headings[j].level <= heading.level) { end = headings[j].index - 1; break; } } if (end > heading.index) { groups.push({ start: heading.index, end }); } }); return groups; } function buildXmlGroups(lines) { const groups = []; const stack = []; const tagRegex = /<[^>]+>/g; function tagName(tag) { const match = tag.match(/^<\/?\s*([^\s>\/]+)/); return match ? match[1] : null; } lines.forEach((line, index) => { const tags = line.match(tagRegex) || []; tags.forEach((tag) => { if (tag.startsWith("