fix(llm): chat panes never push the page sideways; markdown tables, fenced code, quotes and rules render; sticky autoscroll only when already at the bottom
Some checks failed
CI / lint (push) Successful in 32s
CI / notebooks-smoke (push) Successful in 1m36s
Deploy / notebooks (push) Has been skipped
CI / test (push) Successful in 2m13s
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / llm (push) Has been skipped
Deploy / mc (push) Has been skipped
Infra CI / zotero (push) Successful in 14s
Infra CI / docs (push) Successful in 1m32s
Infra CI / api (push) Successful in 1m13s
Infra CI / llm (push) Successful in 47s
Infra CI / mc (push) Failing after 33s
Deploy / report (push) Successful in 18s
Infra CI / notebooks (push) Successful in 5m32s
Some checks failed
CI / lint (push) Successful in 32s
CI / notebooks-smoke (push) Successful in 1m36s
Deploy / notebooks (push) Has been skipped
CI / test (push) Successful in 2m13s
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / llm (push) Has been skipped
Deploy / mc (push) Has been skipped
Infra CI / zotero (push) Successful in 14s
Infra CI / docs (push) Successful in 1m32s
Infra CI / api (push) Successful in 1m13s
Infra CI / llm (push) Successful in 47s
Infra CI / mc (push) Failing after 33s
Deploy / report (push) Successful in 18s
Infra CI / notebooks (push) Successful in 5m32s
- .msg gets min-width: 0 and the lineage/valuation wrappers max-width: 100%, so a wide table scrolls inside its message instead of stretching the column; #log hides horizontal overflow. - long cited labels and the valuation descriptor / lineage event cells may wrap (they were nowrap); bubbles use overflow-wrap: anywhere. - paint() handles | pipe | tables, ``` fences (also unterminated while streaming), > quotes and --- rules instead of showing them raw. - streaming tokens and arriving panes only scroll the log when the reader was within 120px of the bottom; a new question still jumps. - narrow viewports: header and input row wrap, user bubbles full width. Verified with a Node DOM-stub harness over the page script and tests/llm/test_api.py.
This commit is contained in:
@@ -44,18 +44,21 @@
|
|||||||
text-transform: uppercase; letter-spacing: .08em; opacity: .8;
|
text-transform: uppercase; letter-spacing: .08em; opacity: .8;
|
||||||
}
|
}
|
||||||
#log {
|
#log {
|
||||||
flex: 1 1 auto; overflow-y: auto; padding: 26px 22px;
|
flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; padding: 26px 22px;
|
||||||
display: flex; flex-direction: column; gap: 22px;
|
display: flex; flex-direction: column; gap: 22px;
|
||||||
max-width: 860px; width: 100%; margin: 0 auto;
|
max-width: 860px; width: 100%; margin: 0 auto;
|
||||||
}
|
}
|
||||||
.msg { display: flex; flex-direction: column; gap: 7px; }
|
/* min-width: 0 lets a wide table pane scroll inside its message instead
|
||||||
|
of stretching the column (a flex child's default min-width is its
|
||||||
|
content width, which is what pushed the page sideways). */
|
||||||
|
.msg { display: flex; flex-direction: column; gap: 7px; min-width: 0; max-width: 100%; }
|
||||||
.msg .role {
|
.msg .role {
|
||||||
font-family: var(--font-mono); font-size: 10px; letter-spacing: .12em;
|
font-family: var(--font-mono); font-size: 10px; letter-spacing: .12em;
|
||||||
text-transform: uppercase; color: var(--muted-fg);
|
text-transform: uppercase; color: var(--muted-fg);
|
||||||
}
|
}
|
||||||
.bubble {
|
.bubble {
|
||||||
padding: 14px 16px; white-space: pre-wrap; word-wrap: break-word;
|
padding: 14px 16px; white-space: pre-wrap; overflow-wrap: anywhere;
|
||||||
border: 1px solid var(--border); border-radius: 3px;
|
border: 1px solid var(--border); border-radius: 3px; max-width: 100%;
|
||||||
}
|
}
|
||||||
.user { align-items: flex-end; }
|
.user { align-items: flex-end; }
|
||||||
.user .role { text-align: right; }
|
.user .role { text-align: right; }
|
||||||
@@ -65,10 +68,11 @@
|
|||||||
}
|
}
|
||||||
.bot .bubble { background: var(--card); }
|
.bot .bubble { background: var(--card); }
|
||||||
/* Model-cited [ID] tokens rendered as editorial small-caps mono. */
|
/* Model-cited [ID] tokens rendered as editorial small-caps mono. */
|
||||||
.bot .bubble .cite {
|
.bot .bubble .cite, .lineage-wrap .cite, .provenance .cite {
|
||||||
font-family: var(--font-mono); font-size: .82em; color: var(--accent);
|
font-family: var(--font-mono); font-size: .82em; color: var(--accent);
|
||||||
background: var(--secondary); padding: 1px 5px; border-radius: 2px;
|
background: var(--secondary); padding: 1px 5px; border-radius: 2px;
|
||||||
white-space: nowrap;
|
/* a 90-char label must be allowed to wrap rather than overflow */
|
||||||
|
white-space: normal; overflow-wrap: anywhere; box-decoration-break: clone;
|
||||||
}
|
}
|
||||||
/* A cite with a known paragraph URL is a jump link (FR ¶, eCFR §). */
|
/* A cite with a known paragraph URL is a jump link (FR ¶, eCFR §). */
|
||||||
.bot .bubble a.cite { text-decoration: underline dotted; cursor: pointer; }
|
.bot .bubble a.cite { text-decoration: underline dotted; cursor: pointer; }
|
||||||
@@ -84,6 +88,30 @@
|
|||||||
.bot .bubble ul, .bot .bubble ol { margin: 0 0 .6em 1.2em; padding: 0; }
|
.bot .bubble ul, .bot .bubble ol { margin: 0 0 .6em 1.2em; padding: 0; }
|
||||||
.bot .bubble li { margin: .15em 0; }
|
.bot .bubble li { margin: .15em 0; }
|
||||||
.bot .bubble code { font-family: var(--font-mono); font-size: .88em; }
|
.bot .bubble code { font-family: var(--font-mono); font-size: .88em; }
|
||||||
|
.bot .bubble pre {
|
||||||
|
font-family: var(--font-mono); font-size: .85em; line-height: 1.4;
|
||||||
|
background: var(--secondary); border: 1px solid var(--border); border-radius: 3px;
|
||||||
|
padding: 10px 12px; margin: 0 0 .6em; overflow-x: auto; white-space: pre; max-width: 100%;
|
||||||
|
}
|
||||||
|
.bot .bubble pre code { font-size: inherit; }
|
||||||
|
.bot .bubble blockquote {
|
||||||
|
margin: 0 0 .6em; padding: 2px 12px; border-left: 3px solid var(--border);
|
||||||
|
color: var(--muted-fg);
|
||||||
|
}
|
||||||
|
.bot .bubble hr { border: 0; border-top: 1px solid var(--border); margin: .8em 0; }
|
||||||
|
/* Markdown tables the model writes (| a | b |) render as real tables in
|
||||||
|
a horizontally scrolling wrapper, never as raw pipe text. */
|
||||||
|
.bot .bubble .md-table { overflow-x: auto; max-width: 100%; margin: 0 0 .6em; }
|
||||||
|
.bot .bubble table.md {
|
||||||
|
border-collapse: collapse; font-size: 14px; white-space: normal;
|
||||||
|
}
|
||||||
|
.bot .bubble table.md th, .bot .bubble table.md td {
|
||||||
|
border: 1px solid var(--border); padding: 4px 8px; text-align: left; vertical-align: top;
|
||||||
|
}
|
||||||
|
.bot .bubble table.md th {
|
||||||
|
font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em;
|
||||||
|
text-transform: uppercase; color: var(--muted-fg);
|
||||||
|
}
|
||||||
table.valuation {
|
table.valuation {
|
||||||
border-collapse: collapse; width: 100%; font-size: 13px; margin-top: 4px;
|
border-collapse: collapse; width: 100%; font-size: 13px; margin-top: 4px;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
@@ -94,22 +122,28 @@
|
|||||||
table.valuation th { font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em;
|
table.valuation th { font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em;
|
||||||
text-transform: uppercase; color: var(--muted-fg); }
|
text-transform: uppercase; color: var(--muted-fg); }
|
||||||
table.valuation td:nth-child(-n+3), table.valuation th:nth-child(-n+3) { text-align: left; }
|
table.valuation td:nth-child(-n+3), table.valuation th:nth-child(-n+3) { text-align: left; }
|
||||||
|
/* the descriptor is the one cell that may wrap; numbers stay on one line */
|
||||||
|
table.valuation td:nth-child(2) { white-space: normal; min-width: 160px; max-width: 320px; }
|
||||||
table.valuation tr.proposed td { font-style: italic; opacity: .85; }
|
table.valuation tr.proposed td { font-style: italic; opacity: .85; }
|
||||||
table.valuation caption {
|
table.valuation caption {
|
||||||
caption-side: bottom; text-align: left; font-size: 11px;
|
caption-side: bottom; text-align: left; font-size: 11px;
|
||||||
color: var(--muted-fg); padding: 4px 2px 0;
|
color: var(--muted-fg); padding: 4px 2px 0;
|
||||||
}
|
}
|
||||||
.valuation-wrap { overflow-x: auto; }
|
.valuation-wrap { overflow-x: auto; max-width: 100%; }
|
||||||
table.lineage {
|
table.lineage {
|
||||||
border-collapse: collapse; width: 100%; font-size: 13px; margin-top: 4px;
|
border-collapse: collapse; width: 100%; font-size: 13px; margin-top: 4px;
|
||||||
}
|
}
|
||||||
table.lineage th, table.lineage td {
|
table.lineage th, table.lineage td {
|
||||||
border: 1px solid var(--border); padding: 4px 6px; text-align: left; white-space: nowrap;
|
border: 1px solid var(--border); padding: 4px 6px; text-align: left; white-space: nowrap;
|
||||||
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
/* event text can carry long from → to code lists; let it wrap */
|
||||||
|
table.lineage td:nth-child(2), table.lineage td:nth-child(4) { white-space: normal; }
|
||||||
|
table.lineage td:nth-child(2) { max-width: 360px; }
|
||||||
table.lineage th { font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em;
|
table.lineage th { font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em;
|
||||||
text-transform: uppercase; color: var(--muted-fg); }
|
text-transform: uppercase; color: var(--muted-fg); }
|
||||||
tr.unanchored td { color: var(--muted, #888); font-style: italic; }
|
tr.unanchored td { color: var(--muted, #888); font-style: italic; }
|
||||||
.lineage-wrap { overflow-x: auto; margin-bottom: 6px; }
|
.lineage-wrap { overflow-x: auto; max-width: 100%; margin-bottom: 6px; }
|
||||||
.lineage-wrap .caption { font-size: 12px; color: var(--muted-fg); margin-bottom: 2px; }
|
.lineage-wrap .caption { font-size: 12px; color: var(--muted-fg); margin-bottom: 2px; }
|
||||||
ul.elements, ul.guidance {
|
ul.elements, ul.guidance {
|
||||||
font-size: 12px; color: var(--muted-fg); margin: 6px 0 0; padding-left: 18px;
|
font-size: 12px; color: var(--muted-fg); margin: 6px 0 0; padding-left: 18px;
|
||||||
@@ -129,7 +163,7 @@
|
|||||||
details.sources[open] > summary::before { content: "▾ "; }
|
details.sources[open] > summary::before { content: "▾ "; }
|
||||||
.src {
|
.src {
|
||||||
border-left: 3px solid var(--border); padding: 6px 12px; margin: 10px 0;
|
border-left: 3px solid var(--border); padding: 6px 12px; margin: 10px 0;
|
||||||
color: var(--muted-fg);
|
color: var(--muted-fg); overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
.src b { color: var(--accent); font-family: var(--font-mono); font-size: .8em; font-weight: 500; }
|
.src b { color: var(--accent); font-family: var(--font-mono); font-size: .8em; font-weight: 500; }
|
||||||
.err { color: var(--destructive); border-color: var(--destructive); }
|
.err { color: var(--destructive); border-color: var(--destructive); }
|
||||||
@@ -176,6 +210,19 @@
|
|||||||
margin: auto; max-width: 500px; text-align: center; color: var(--muted-fg);
|
margin: auto; max-width: 500px; text-align: center; color: var(--muted-fg);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
/* Narrow viewports: the header and the input row wrap instead of
|
||||||
|
squeezing; a user bubble may use the full width. */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
header { flex-wrap: wrap; gap: 6px 14px; padding: 12px 14px; }
|
||||||
|
header .who { margin-left: 0; flex-basis: 100%; }
|
||||||
|
#log { padding: 16px 12px; gap: 16px; }
|
||||||
|
form { flex-wrap: wrap; padding: 12px; gap: 8px; }
|
||||||
|
textarea { flex: 1 1 100%; }
|
||||||
|
form label.recent, form label.mode { flex: 1 1 auto; }
|
||||||
|
button { flex: 0 0 auto; margin-left: auto; padding: 8px 18px; }
|
||||||
|
.user .bubble { max-width: 100%; }
|
||||||
|
table.valuation td:nth-child(2) { min-width: 120px; max-width: 200px; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -209,6 +256,16 @@
|
|||||||
const box = document.getElementById('q');
|
const box = document.getElementById('q');
|
||||||
const send = document.getElementById('send');
|
const send = document.getElementById('send');
|
||||||
|
|
||||||
|
// Only follow the stream when the reader is already at the bottom —
|
||||||
|
// scrolling up to re-read an earlier answer must not be yanked back by
|
||||||
|
// every token or pane that arrives.
|
||||||
|
function nearBottom() {
|
||||||
|
return log.scrollHeight - log.scrollTop - log.clientHeight < 120;
|
||||||
|
}
|
||||||
|
function stick(wasNear) {
|
||||||
|
if (wasNear) log.scrollTop = log.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
fetch('whoami').then(r => r.json()).then(d => {
|
fetch('whoami').then(r => r.json()).then(d => {
|
||||||
if (d.user) document.getElementById('who').textContent = d.user;
|
if (d.user) document.getElementById('who').textContent = d.user;
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
@@ -263,10 +320,35 @@
|
|||||||
// Block markdown: headings (# … ######), bullet and numbered lists,
|
// Block markdown: headings (# … ######), bullet and numbered lists,
|
||||||
// paragraphs. The model writes markdown; showing raw "## History" was
|
// paragraphs. The model writes markdown; showing raw "## History" was
|
||||||
// the bug this replaces.
|
// the bug this replaces.
|
||||||
|
function isTableRow(line) {
|
||||||
|
return /^\s*\|.*\|\s*$/.test(line);
|
||||||
|
}
|
||||||
|
function isTableRule(line) {
|
||||||
|
return /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/.test(line);
|
||||||
|
}
|
||||||
|
function splitRow(line) {
|
||||||
|
return line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(c => c.trim());
|
||||||
|
}
|
||||||
|
function paintTable(el, rows, links) {
|
||||||
|
const wrap = document.createElement('div');
|
||||||
|
wrap.className = 'md-table';
|
||||||
|
const t = document.createElement('table');
|
||||||
|
t.className = 'md';
|
||||||
|
const [head, ...body] = rows;
|
||||||
|
const tr = t.createTHead().insertRow();
|
||||||
|
for (const cell of head) { const th = document.createElement('th'); paintInline(th, cell, links); tr.appendChild(th); }
|
||||||
|
const tb = t.createTBody();
|
||||||
|
for (const r of body) {
|
||||||
|
const row = tb.insertRow();
|
||||||
|
for (let i = 0; i < head.length; i++) { paintInline(row.insertCell(), r[i] || '', links); }
|
||||||
|
}
|
||||||
|
wrap.appendChild(t); el.appendChild(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
function paint(el, text, links) {
|
function paint(el, text, links) {
|
||||||
el.innerHTML = '';
|
el.innerHTML = '';
|
||||||
const lines = text.split('\n');
|
const lines = text.split('\n');
|
||||||
let para = [], list = null;
|
let para = [], list = null, table = null, fence = null, quote = null;
|
||||||
const flushPara = () => {
|
const flushPara = () => {
|
||||||
if (!para.length) return;
|
if (!para.length) return;
|
||||||
const p = document.createElement('p');
|
const p = document.createElement('p');
|
||||||
@@ -274,8 +356,47 @@
|
|||||||
el.appendChild(p); para = [];
|
el.appendChild(p); para = [];
|
||||||
};
|
};
|
||||||
const flushList = () => { list = null; };
|
const flushList = () => { list = null; };
|
||||||
|
const flushTable = () => {
|
||||||
|
if (table && table.length) paintTable(el, table, links);
|
||||||
|
table = null;
|
||||||
|
};
|
||||||
|
const flushQuote = () => { quote = null; };
|
||||||
for (const raw of lines) {
|
for (const raw of lines) {
|
||||||
const line = raw.replace(/\s+$/, '');
|
const line = raw.replace(/\s+$/, '');
|
||||||
|
// fenced code: everything until the closing fence is verbatim
|
||||||
|
if (fence) {
|
||||||
|
if (/^\s*```/.test(line)) { fence = null; continue; }
|
||||||
|
fence.textContent += (fence.textContent ? '\n' : '') + raw;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (/^\s*```/.test(line)) {
|
||||||
|
flushPara(); flushList(); flushTable(); flushQuote();
|
||||||
|
const pre = document.createElement('pre');
|
||||||
|
fence = document.createElement('code');
|
||||||
|
pre.appendChild(fence); el.appendChild(pre);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isTableRow(line)) {
|
||||||
|
flushPara(); flushList(); flushQuote();
|
||||||
|
if (isTableRule(line)) continue; // the |---|---| separator
|
||||||
|
if (!table) table = [];
|
||||||
|
table.push(splitRow(line));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
flushTable();
|
||||||
|
if (/^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
|
||||||
|
flushPara(); flushList(); flushQuote();
|
||||||
|
el.appendChild(document.createElement('hr'));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const q = /^\s*>\s?(.*)$/.exec(line);
|
||||||
|
if (q) {
|
||||||
|
flushPara(); flushList();
|
||||||
|
if (!quote) { quote = document.createElement('blockquote'); el.appendChild(quote); }
|
||||||
|
const p = document.createElement('p'); paintInline(p, q[1], links); quote.appendChild(p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
flushQuote();
|
||||||
const h = /^(#{1,6})\s+(.*)$/.exec(line);
|
const h = /^(#{1,6})\s+(.*)$/.exec(line);
|
||||||
const li = /^\s*(?:[-*•]|\d+[.)])\s+(.*)$/.exec(line);
|
const li = /^\s*(?:[-*•]|\d+[.)])\s+(.*)$/.exec(line);
|
||||||
if (h) {
|
if (h) {
|
||||||
@@ -297,7 +418,7 @@
|
|||||||
flushList(); para.push(line);
|
flushList(); para.push(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flushPara();
|
flushPara(); flushTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
// label → url for this turn, filled by the lineage/valuation/sources
|
// label → url for this turn, filled by the lineage/valuation/sources
|
||||||
@@ -321,6 +442,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderLineage(wrap, ev) {
|
function renderLineage(wrap, ev) {
|
||||||
|
const near = nearBottom();
|
||||||
const events = ev.events || [];
|
const events = ev.events || [];
|
||||||
const box = document.createElement('div');
|
const box = document.createElement('div');
|
||||||
box.className = 'lineage-wrap';
|
box.className = 'lineage-wrap';
|
||||||
@@ -413,7 +535,7 @@
|
|||||||
box.appendChild(note);
|
box.appendChild(note);
|
||||||
}
|
}
|
||||||
wrap.appendChild(box);
|
wrap.appendChild(box);
|
||||||
log.scrollTop = log.scrollHeight;
|
stick(near);
|
||||||
}
|
}
|
||||||
|
|
||||||
const VAL_COLS = [
|
const VAL_COLS = [
|
||||||
@@ -430,6 +552,7 @@
|
|||||||
return String(v);
|
return String(v);
|
||||||
}
|
}
|
||||||
function renderValuation(wrap, ev) {
|
function renderValuation(wrap, ev) {
|
||||||
|
const near = nearBottom();
|
||||||
const rows = ev.rows || [];
|
const rows = ev.rows || [];
|
||||||
const box = document.createElement('div');
|
const box = document.createElement('div');
|
||||||
box.className = 'valuation-wrap';
|
box.className = 'valuation-wrap';
|
||||||
@@ -486,10 +609,11 @@
|
|||||||
}
|
}
|
||||||
box.appendChild(prov);
|
box.appendChild(prov);
|
||||||
wrap.appendChild(box);
|
wrap.appendChild(box);
|
||||||
log.scrollTop = log.scrollHeight;
|
stick(near);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSources(wrap, ev) {
|
function renderSources(wrap, ev) {
|
||||||
|
const near = nearBottom();
|
||||||
const sources = ev.sources || [];
|
const sources = ev.sources || [];
|
||||||
if (ev.model) {
|
if (ev.model) {
|
||||||
const meta = document.createElement('div');
|
const meta = document.createElement('div');
|
||||||
@@ -532,7 +656,7 @@
|
|||||||
d.appendChild(el);
|
d.appendChild(el);
|
||||||
}
|
}
|
||||||
wrap.appendChild(d);
|
wrap.appendChild(d);
|
||||||
log.scrollTop = log.scrollHeight;
|
stick(near);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ask(question) {
|
async function ask(question) {
|
||||||
@@ -566,7 +690,7 @@
|
|||||||
const line = part.replace(/^data: /, '').trim();
|
const line = part.replace(/^data: /, '').trim();
|
||||||
if (!line) continue;
|
if (!line) continue;
|
||||||
const ev = JSON.parse(line);
|
const ev = JSON.parse(line);
|
||||||
if (ev.type === 'token') { answer += ev.text; paint(b, answer, links); log.scrollTop = log.scrollHeight; }
|
if (ev.type === 'token') { const near = nearBottom(); answer += ev.text; paint(b, answer, links); stick(near); }
|
||||||
else if (ev.type === 'lineage') { collectLinks(links, ev); renderLineage(wrap, ev); }
|
else if (ev.type === 'lineage') { collectLinks(links, ev); renderLineage(wrap, ev); }
|
||||||
else if (ev.type === 'valuation') { collectLinks(links, ev); renderValuation(wrap, ev); }
|
else if (ev.type === 'valuation') { collectLinks(links, ev); renderValuation(wrap, ev); }
|
||||||
else if (ev.type === 'sources') { collectLinks(links, ev); renderSources(wrap, ev); paint(b, answer, links); }
|
else if (ev.type === 'sources') { collectLinks(links, ev); renderSources(wrap, ev); paint(b, answer, links); }
|
||||||
|
|||||||
Reference in New Issue
Block a user