Clipboard and the web
On a webapp I wanted to add a button which would copy content which could be pasted structured into a document. Namely I have a list of items which I would like to be copied to adjacent cells in a spreadsheet.
When you copy content from a page into a document it tries to preserve format (which often gives ugly results) because the application you copy from attaches content in multiple content types in the clipboard.
The application you copy to then fetches its preferred content among the list: a basic text editor picks entry with text/plain
content type, an email writer picks entry with text/html
, an image editor image/png
, …
That may require transformation, and if the application doesn’t find any relevant format it just does nothing.
The list of available content types after copying something can be obtained with:
xclip -selection clipboard -target TARGETS -out
For example after selecting content on a web page in Firefox and hitting CTRL+C
:
TIMESTAMP
TARGETS
MULTIPLE
SAVE_TARGETS
text/html
text/_moz_htmlcontext
text/_moz_htmlinfo
UTF8_STRING
COMPOUND_TEXT
TEXT
STRING
text/plain;charset=utf-8
text/plain
text/x-moz-url-priv
Many are not actually available or duplicates, but text/plain
and text/html
are there:
xclip -selection clipboard -target 'text/plain' -out
xclip -selection clipboard -target 'text/html' -out
Depending on the source there can be images in image/png
format:
xclip -selection clipboard -target 'image/png' -out > image.png
Or even more surprising formats like OpenDocument fragments (application/x-openoffice-objectdescriptor-xml
) which allows LibreOffice to copy/paste charts from a presentation to another.
But the main one is text/html
as it provides a common format understood by many applications: if my webapp can write this entry in clipboard, then it can be pasted structured in many other applications.
It can even includes inline style that the target application may respect (and will break document formatting if you set font size or color).
This can be tests in developer tools: add inline style to an the element, then select content around it and copy.
I’m looking at the source/copy side, but if you work on the target/paste side, note you have to be pretty lenient on accepted format: Firefox (and others) truncate content quite brutally and there is no attempt in balancing closing tags.
This is exposed to Javascript via clipboard API.
The API suggest the ability to write any content type, but browsers actually restict to only text/plain
and text/html
.
const text = 'Hello, world!';
const html = 'Hello <span style="color: red;">world</span>!';
await navigator.clipboard.write([new ClipboardItem({
'text/plain': new Blob([text], {type: 'text/plain'}),
'text/html': new Blob([html], {type: 'text/html'}),
})]);
Style is not actually what I want, the only thing it does is annoying the user by breaking their document style.
But my list can be copied as a <table>
and spreadsheets will recognize this format and paste to cells.
In order to avoid building the markup manually, and taking the risk of content injection or broken format, I generate an actual DOM tree without attaching it to the document, and access their outerHTML
.
The table has a single row with many cells, but it could have a different layout.
async copyList(list) {
const text = list.join('\n');
const table = document.createElement('table');
const tbody = document.createElement('tbody');
table.appendChild(tbody);
const tr = document.createElement('tr');
tbody.appendChild(tr);
for (const item of list) {
const td = document.createElement('td');
td.textContent = item;
tr.appendChild(td);
}
const html = table.outerHTML;
await navigator.clipboard.write([new ClipboardItem({
'text/plain': new Blob([text], {type: 'text/plain'}),
'text/html': new Blob([html], {type: 'text/html'}),
})]);
}