Reading Multiline, Word, and HTML Data From the Clipboard

I have now been able to read for the clipboard with this function for my addon:

function readClipboard() {
    field = document.activeElement;
    var area = document.getElementById("fcb_txt_area");
    if (area) {
        // console.log("Found existing area");
        //area.setAttribute("style", "visibility:visible;");
        area.setAttribute("style", "display:inline;");
    } else {
        // console.log("Creating new area");
        area = document.createElement("textarea");
        area.setAttribute("id", "fcb_txt_area");
        if (field) {
            field.parentNode.append(area);
        } else {
            document.body.appendChild(area);
        }
        area.contentEditable = true;
    }

    area.textContent = '';
    area.select();
    //console.log('Pre-paste: ' + area.value);
    //console.log(document.execCommand("paste"));
    document.execCommand("paste");
    var value = area.value;
    //console.log('Post-paste: ' + area.value);
    area.setAttribute("style", "display:none");
    console.log("Clipboard Contents: ",value);
    return value;
}

It works properly, , but only on single lines of text or html:

On a single line of text, I get:
Clipboard Contents: "https://discourse.mozilla.org/c"

On a single line of html, I get:
Unknown property ‘zoom’. Declaration dropped.

On multiline text, I get:
Clipboard Contents: line1line2line3
but I copied:
line1
line2
line3

With MS word, I copy, and I get: (Just copying one word, “try”)

Clipboard Contents:    content_script.js:70:5
Unknown property ‘mso-ascii-theme-font’.  Declaration dropped.  posting.php
Unknown property ‘mso-fareast-font-family’.  Declaration dropped.  posting.php
Unknown property ‘mso-fareast-theme-font’.  Declaration dropped.  posting.php
Unknown property ‘mso-hansi-theme-font’.  Declaration dropped.
………

Which is pretty clearly the function choking on MS Word’s bloated style/html defaults.

My questions re:

How do read the clipboard contents and preserve new lines, and how do I clean up/decrapify things like html and word?

I don’t know where you dug that up, but that’s pretty ugly.

If you use a function that uses the proper APIs for this, it should work.
Just decide which MIME-type(s) you actually need and use this:

Of course it’s ugly code.

I’m pretty sure that my code causes cancer in lab rats, and psychological problems in cetaceans. (In addition to hair loss and erectile dysfunctions on Honey Badgers)

I’m thinking of putting a warning sticker on my code.

The only MIME type I need is plain text. I’m recreating bbCodeXtra, and there is no need for it to handle anything but that.

:grin: I was pretty sure that I saw a very similar readClipboard function before.

I’m not sure if plain text is all you should want for that. A rather recent addition of the very MarkDown/bbCode/… editor on this very site is that if you paste HTML, it gets “reverse-compiled” to MarkDown. That’s very useful, especially when quoting. They have to intercept the paste event for that as well.

I cannot speak to markdown, but in regular phpBB, a paste generates text, though there are WYSIWYG addons that probably have that capability.

I’m trying to create a Webextensions compatible functional clone of bbCodeXtra, using the existing menu structure and language localizations, and it just does text operations.

OK, I am assuming that I set the mime type when calling the function, something like

var moo = readFromClipboard('text/plain')

What I get in response though is, “[object Promise]”.

How do I get the data from a promise, or better yet, eschew a promise altogether, since I am still writing for ES 5.

It’s been 30 years since I did any programming, and that was in APL, which (ironically enough) is more readable than Javascript.

Promises work in es5, no problem. You get the data by adding a callback with .then(). See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

And also ES6 and later really aren’t more complicated.
Sure, they add some new stuff, but using that you can also avoid some of the weirdest old “features” of JavaScript.
And especially when working with Promises and asynchronous code in general, await can make things a lot more straight forward.

The function here is inherently synchronous though:

Right click, select submenu bbcode, select submenu clipboard, select operation “Make url from selection”.

You don’t want progress in the code until success.

I will try the callback then with then, after reading the (always confusing) documentation.

JavaScript/browsers are actually inherently asynchronous. Just because you think there is some fixed sequence of events doesn’t mean it is synchronous. Just as an example of how much is going on (and remember, each “wait” is not synchronous, or nothing else coulc happen parallel to it):

Right click

mousedown, wait, mouseup, [ trigger contextmenu | notify extensions ], open menu

now extensions can even update the available menus (asynchronously)

select submenu bbcode,

hover effects …

select submenu clipboard,

hover, build submenu, …

select operation “Make url from selection”

notify background, which notifies content script (very much asynchronously)

get selected text (this one is synchronous) and the clipboard content (existing and proposed APIs are booth asynchronous)

replace HTML content (synchronous, but I guess if those APIs were designed now, they’d be asynchronous


You don’t want progress in the code until success.

I guess what you want is that there is no partial operation visible to the user? Asynchronous doesn’t mean inconsistent (if done properly). Since there is only a single operation here that changes what the user sees, there is really no way to mess this up in this case.

And using callbacks or .then() is in no way less asynchronous than async/await. It is really just uglier (most of the time).

Basically, I just want something that is syntactically readable to me. (I’ve been doing Javascript in my spare time for 3 weeks).

I find the notation and docs completely incomprehensible, so (for example), “adding a callback with .then()”, is less comprehensible to me than “(⍴X)⍴(,X)[A[⍋(,⍉(⌽⍴X)⍴⍳1↑⍴X)[A←⍋,X]]]”, which I know sorts a matrix in ascending order.

The example given earlier (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) does not appear to me to show how to actually get a value from a returned promise, just the success or failure of a hypothetical function, which in my case, is as proverbially useful as nipples on a bull.

I do understand how this promises might make Javascript run more smoothly, but I really don’t care in my application, because the syntax has become even more obscure.

I just want to get the contents of the clipboard into a variable reliably with code that is as clear (to me) as possible.

I understand that this will necessarily mean that the code is slower and larger than it would be using “good” programming techniques, but this is some simple string handling, not the back end at healthcare.gov.

If you get an object back from a function, try viewing it as JSON. Not the easiest format, but many viewers can “pretty print” it. So in your example:

var moo = readFromClipboard('text/plain');
console.log(JSON.stringify(moo));

Once you know the structure, you can pull out the parts you want.

Actually, no, that would work if readFromClipboard() is not asynchronous. I guess you need something more like:

var moo = readFromClipboard('text/plain');
moo.then(moo => console.log(JSON.stringify(moo)));

I just want to get the contents of the clipboard into a variable reliably with code that is as clear (to me) as possible.

I understand that this will necessarily mean that the code is slower and larger

Ok, so maybe it helps if (for now) you forget how the promise API works. Simply see all functions that return promises as async functions.
Whenever you call an async function, put the await keyword in before the function invocation, and maybe, just to be sure about operator precedence, wrap the entire thing in parentheses.
And to be allowed to use await in a function, it needs to be declared as async, which in turn means it has to be invoked with await.

You may end up with way to many awaits, which can make the code slow, but the code will still be quite readable and short.

And for now, don’t ever use .then(). There is hardly ever a reason to do so. In my code (and I think I use promises as efficiently as possible) I use less than 10% of the promises explicitly.

So the shortest solution to the clipboard reading is:

async function someEventOrWhatever() {
    // ...
    const moo = (await readFromClipboard('text/plain'));
    console.log('`moo` is:', moo);
    // ...
}

Also, don’t call JSON.stringify on stuff you log. The console itself will offer the object for inspection much nicer (as long as you don’t make the mistake of concatenating it with the description string).

1 Like

OK, I’ll try it.

BTW, why are you assigning moo to a constant rather than a variable?

Because it’s not changed inside that snippet. const just means you can’t change it after setting it, other than that it’s exactly the same as let.

OK, I have a simplified extension, and I am checking it out on FF 56 as well as FF 63 Beta (In FF 56, I change dom.moduleScripts.enabled to true.), and I attempt to import the module:

import {Readfromclipboard} from './es6lib/dom.js';

I added dom.js to the manifest:

	"content_scripts": [
		{
		"matches": ["*://*/*"],
		"js": ["content_script.js", "./es6lib/dom.js"]
		}
	],

I get the following error:
SyntaxError: import declarations may only appear at top level of a module

Whether I attempt this in the background script or the content script.

Am I missing something?

I don’t think you can use the module syntax at all in content scripts, and that is where you need to use this function.

My script is a “normal” script that exposes its exports either to a AMD loader or as a global variable.
The scripts default path (when installed via npm i es6lib) is node_modules/es6lib/dom.js.
For you the easiest way (unless you are already using require.js or something like that) would be to load es6lib/dom.js first, then use it from the es6lib_dom global:

manifest.json:

	"content_scripts": [ {
		"matches": ["*://*/*"],
		"js": [ "node_modules/es6lib/dom.js", "content_script.js" ]
	} ],

content_script.js:

(function(global) {

const { readFromClipboard, } = global.es6lib_dom

// ... lots of other code, I presume

async function someEventOrWhatever() {
    // ...
    const moo = (await readFromClipboard('text/plain'));
    console.log('`moo` is:', moo);
    // ...
}

// ... more of your code

})();

That, and I find it to be a good practice to mark everything I don’t intend to change (which is the vast majority of variables) as const. Makes it easier to read, lint and maybe even optimize the code.
All other variables should be lets. Don’t use var. It is one of those “broken” ES5 (and below) things that have better alternatives in ES6 (and above).

1 Like

OK, I just tried that:

The relevent section of my manifest.json reads:

"content_scripts": [
	{
	"matches": ["*://*/*"],
	"js": ["node_modules/es6lib/dom.js", "content_script.js"]
	}
],

I did an NPM install on windows from the root of the addon as follows:

C:\Moo\phpBB\quick>npm install --prefix . ..\NiklasGollenstede-es6lib-v0.0.2-0-g
e0da2b6.tar.gz

The directory was created and the output was:

C:\Moo\phpBB\quick
└── es6lib@0.0.2

When I attempt to load the addon through the debugging panel, I get the following error in the browser console:
TypeError: global is undefined, can't access property "es6lib_dom" of it

The code for my content_script.js is:
(function(global) {

const { readFromClipboard, } = global.es6lib_dom

var clickedElement = null;

document.addEventListener("mousedown", function(event){
    //right click
    if(event.button == 2) { 
        clickedElement = event.target;
    }
}, true);
browser.runtime.onMessage.addListener(function(RunParse, CommandString, sendResponse) {
var CallFunction = RunParse.runwhat;
var ArgString = RunParse.ParseArg;
       CommandParse (ArgString);
        sendResponse({value: clickedElement.value});
});

async function CommandParse(ArgString) {
//       Get Info About Textbox
 var TextBoxName = clickedElement.getAttribute('name');
 var TextBoxID = clickedElement.getAttribute('id');
 var FocusInfo = document.activeElement;
var txtcont = document.getElementById(TextBoxID).value; //contents of edit box
var selstart = clickedElement.selectionStart; // index of selectin start
var selend = clickedElement.selectionEnd; //index of selection end
var selcont = txtcont.substring(selstart,selend); // selected text content
var firsttext = txtcont.substring(0,selstart); //stuff before the selection
var lasttext = txtcont.substring(selend); // stuff after the selection
//var clipcont = readClipboard (); //contents of clipboard
/*
console.log("moo:",moo);
    // ...
    const moop = await readFromClipboard('text/plain');
    console.log('`moop` is:', moop);
    // ...
*/

//simplified pasting

}

})();

Am I missing something?

You are not passing in anything to your iife as global param. You probably want to pass in window (or not use an iife to make window called global at all…)

I was literally copying and pasting what NilkasG suggested in order to run the he entered above.

I’ve been unable to get it to load into the content script or into the context script, and I can’t get it to work when I paste the code inline to either.

I’m just trying to find a syntactically simple way to test out how this works on reading the clipboard value, and I have zero concern about efficiency, and negative concern about the latest and greatest features of Javascript.