openvibers/openvibe.actor Commits

Copy gives the answer's words, not its citation numbers (#230)

OP OpenVibers committed 12 days ago · 6c0f7e43da · 2 files, +22 −2

Copying an answer took its innerText, so the citation chips' numbers came along ("visits 1 2") and each code
block's Copy button too. Copy now reads a trimmed clone: no chips, no buttons, and no space left before the
punctuation a chip sat against.

Changes

ddiff --git a/public/js/chat.js b/public/js/chat.js
iindex fa527ff..4d1b39f 100644
---- a/public/js/chat.js
++++ b/public/js/chat.js
@@@ -379,16 +379,34 @@
if (b.dataset.wired) return; b.dataset.wired = '1'; b.hidden = false;
b.addEventListener('click', function () {
var ans = b.closest('.ct-reply').querySelector('[data-answer]');
-- copy(ans ? ans.innerText : '', b);
++ copy(ans ? plainText(ans) : '', b);
});
});
(rootEl || chat).querySelectorAll('.ct-answer pre').forEach(function (pre) {
if (pre.querySelector('.ct-copy-code')) return;
var b = document.createElement('button'); b.type = 'button'; b.className = 'ct-copy-code'; b.textContent = 'Copy';
-- b.addEventListener('click', function () { var code = pre.querySelector('code') || pre; copy(code.innerText, b); });
++ b.addEventListener('click', function () { copy(plainText(pre.querySelector('code') || pre), b); });
pre.appendChild(b);
});
}
++ // What a person means by the answer's text: without the citation chips' numbers ("visits 12" was "visits 1 2")
++ // and without the code blocks' own Copy buttons. innerText keeps the line breaks only for laid-out nodes, so the
++ // trimmed copy is read while it sits, invisible, in the page.
++ function plainText(el) {
++ var c = el.cloneNode(true);
++ c.querySelectorAll('.evs, .ct-copy-code').forEach(function (x) {
++ // "one host [1 2], and" → "one host, and": the space before a chip goes when punctuation follows it.
++ var before = x.previousSibling, after = x.nextSibling;
++ if (before && before.nodeType === 3 && (!after || (after.nodeType === 3 && /^[\s,.;:!?)]/.test(after.nodeValue)))) before.nodeValue = before.nodeValue.replace(/\s+$/, '');
++ x.remove();
++ });
++ c.style.cssText = 'position:fixed;left:-10000px;top:0;width:' + (el.clientWidth || 600) + 'px;opacity:0;pointer-events:none';
++ c.setAttribute('aria-hidden', 'true');
++ document.body.appendChild(c);
++ var t = c.innerText.replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim();
++ c.remove();
++ return t;
++ }
function copy(str, btn) {
var label = btn.querySelector('span') || btn;
var was = label.textContent;
ddiff --git a/test/chat.test.js b/test/chat.test.js
iindex 2185ba2..8b70760 100644
---- a/test/chat.test.js
++++ b/test/chat.test.js
@@@ -150,6 +150,8 @@ const LOC = /^\/chat\/(tsk_[0-9A-HJKMNP-TV-Z]{26})#t-\1$/;
assert.match(js, /\(hover: hover\) and \(pointer: fine\)/, 'Enter sends only with a keyboard and mouse; a phone\'s Enter is a new line');
assert.match(js, /prefers-reduced-motion: reduce/);
assert.match(js, /function streamIn\(ans\)/, 'the checked answer streams in word by word');
++ assert.match(js, /copy\(ans \? plainText\(ans\) : '', b\)/, 'Copy takes the answer as text…');
++ assert.match(js, /c\.querySelectorAll\('\.evs, \.ct-copy-code'\)/, '…without the citation chips\' numbers or the code blocks\' Copy buttons');
assert.match(js, /if \(ans && !reduce && !document\.documentElement\.hasAttribute\('data-ov-motion'\)\) streamIn\(ans\)/, 'not under reduced motion');
const css = require('fs').readFileSync(require('path').join(__dirname, '..', 'public', 'css', 'chat.css'), 'utf8');
assert.match(css, /\.ct-answer\.is-streaming \.sw \{ opacity: 0;/, 'only opacity: a screen reader reads it all at once');