I'm working on a messaging service that allows users to send messages with attachments. I was using an RTE but it added a lot of unnecessary complexity and so now my textbox is just react-textarea-autosize. It is working as expected but when a user pastes/drops an image in to the text box I am trying to capture it and store it in a separate array of files that appears just below the message.
I started using the "onPaste" event, and logging the event shows event.clipboardData as:
{dropEffect: "none",
effectAllowed: "uninitialized",
files: FileList {length: 0},
items: DataTransferItemList {length: 0},
types: []}
using event.clipboardData.getData("text") returns the correct result if text is copied, and an empty string if an image is copied.
I then attempted to add a separate div that was set to "contentEditable:true" and listen to a paste on that to determine if using a textarea was the issue, and although I could visibly see the image being pasted, but the event still contained no useful data. This also does not work for dropping image files, or dragging and dropping images from other websites.
What gives? I don't understand because the paste event is still firing, I've been testing this mainly in chrome, but firefox also seemed to face difficulties, although it was returning an array of types in the cliboardData object.
Is there anyway to receive an image file that is captured from the paste/drop event?
I don't know why the files and items show a length of zero, but what I discovered is that the DataTransferItemList doesn't display properties in logs, instead you have to use getter functions as referenced in the MDN Docs. I found a great small example of different Inputs with data transfers.
https://codepen.io/tech_query/pen/MqGgap
form.onpaste = form.ondrop = event => {
if (event.type === 'drop') event.preventDefault();
const target = event.target;
const cell = target.nextElementSibling.querySelectorAll('td');
cell[0].textContent = event.type;
const transfer = event.clipboardData || event.dataTransfer;
cell[1].firstChild.innerHTML = Array.from(
transfer.items, item => `<li>${item.kind} ${item.type}</li>`
).join('\n');
};
Related
I'm new to web audio API, I used a demo and managed to separate the two channels of PC audio input source and drive a double demo analyzer view.
I've realized that most functions work by handling automatically the whole stream of audio data, but my question is this:
how can I get a single sample from each channel of the stereo input source when I want it (programmatically) and put it on a variable, then another one at a different time?
var input = audioContext.createMediaStreamSource(stream);
var options = {
numberOfOutputs : 2
}
var splitter = new ChannelSplitterNode(audioContext, options);
input.connect( splitter );
splitter.connect(analyser1, 0, 0);
splitter.connect(analyser2, 1, 0);
If you're not too concerned about latency, the MediaRecorder interface can be used to capture raw audio data from a streaming input source (like the one returned by AudioContext.createMediaStreamSource). Use the dataavailable event or MediaRecorder.requestData method to access the raw data as a Blob. There's a fairly straightforward example of this in samdutton/simpl on GitHub. (Also there's a related Q&A on StackOverflow here.)
More generally if you can get the audio you want to analyze into a AudioBuffer, the AudioBuffer.getChannelData method can be used to extract the raw PCM sample data associated with an audio channel.
However if you're doing this in "real-time" - i.e., if you're trying to process the live input audio for "live" playback or visualization - then you'll probably want to look at the AudioWorklet API. Specifically, you'll want to create an AudioWorkletProcessor that examines the individual samples as part of the process handler.
E.g., something like this:
// For this example we'll compute the average level (PCM value) for the frames
// in the audio sample we're processing.
class ExampleAudioWorkletProcessor extends AudioWorkletProcessor {
process (inputs, outputs, parameters) {
// the running total
sum = 0
// grab samples from the first channel of the first input connected to this node
pcmData = inputs[0][0]
// process each individual sample (frame)
for (i = 0; i < pcmData.length; i++ ) {
sum += pcmData[i]
}
// write something to the log just to show it's working at all
console.log("AVG:", (sum / pcmData.length).toFixed(5))
// be sure to return `true` to keep the worklet running
return true
}
}
but that's neither bullet-proof nor particularly efficient code. (Notably you don't really want to use console.log here. You'll probably either (a) write something to the outputs array to send audio data to the next AudioNode in the chain or (b) use postMessage to send data back to the main (non-audio) thread.)
Note that since the AudioWorkletProcessor is executing within the "audio thread" rather than the main "UI thread" like the rest of your code, there are some hoops you must jump through to set up the worklet and communicate with the primary execution context. That's probably outside the scope of what's reasonable to describe here, but here is a complete example from MDN, and there are a large number of tutorials that can walk you through the steps if you search for keywords like "AudioWorkletProcessor", "AudioWorkletNode" or just "AudioWorklet". Here's one example.
Is there a way in javascript to copy an html string (ie <b>xx<b>) into the clipboard as text/html, so that it can then be pasted into for example a gmail message with the formatting (ie, xx in bold)
There exists solutions to copy to the clipboard as text (text/plain) for example https://stackoverflow.com/a/30810322/460084 but not as text/html
I need a non flash, non jquery solution that will work at least on IE11 FF42 and Chrome.
Ideally I would like to store both text and html versions of the string in the clipboard so that the right one can be pasted depending if the target supports html or not.
Since this answer has gotten some attention, I have completely rewritten the messy original to be easier to grasp. If you want to look at the pre-revisioned version, you can find it here.
The boiled down question:
Can I use JavaScript to copy the formatted output of some HTML code to the users clipboard?
Answer:
Yes, with some limitations, you can.
Solution:
Below is a function that will do exactly that. I tested it with your required browsers, it works in all of them. However, IE 11 will ask for confirmation on that action.
Explanation how this works can be found below, you may interactively test the function out in this jsFiddle.
// This function expects an HTML string and copies it as rich text.
function copyFormatted (html) {
// Create container for the HTML
// [1]
var container = document.createElement('div')
container.innerHTML = html
// Hide element
// [2]
container.style.position = 'fixed'
container.style.pointerEvents = 'none'
container.style.opacity = 0
// Detect all style sheets of the page
var activeSheets = Array.prototype.slice.call(document.styleSheets)
.filter(function (sheet) {
return !sheet.disabled
})
// Mount the container to the DOM to make `contentWindow` available
// [3]
document.body.appendChild(container)
// Copy to clipboard
// [4]
window.getSelection().removeAllRanges()
var range = document.createRange()
range.selectNode(container)
window.getSelection().addRange(range)
// [5.1]
document.execCommand('copy')
// [5.2]
for (var i = 0; i < activeSheets.length; i++) activeSheets[i].disabled = true
// [5.3]
document.execCommand('copy')
// [5.4]
for (var i = 0; i < activeSheets.length; i++) activeSheets[i].disabled = false
// Remove the container
// [6]
document.body.removeChild(container)
}
Explanation:
Look into the comments in the code above to see where you currently are in the following process:
We create a container to put our HTML code into.
We style the container to be hidden and detect the page's active stylesheets. The reason will be explained shortly.
We put the container into the page's DOM.
We remove possibly existing selections and select the contents of our container.
We do the copying itself. This is actually a multi-step process:
Chrome will copy text as it sees it, with applied CSS styles, while other browsers will copy it with the browser's default styles.
Therefore we will disable all user styles before copying to get the most consistent result possible.
Before we do this, we prematurely execute the copy command.
This is a hack for IE11: In this browser, the copying must be manually confirmed once. Until the user clicked the "Confirm" button, IE users would see the page without any styles. To avoid this, we copy first, wait for confirmation, then disable the styles and copy again. That time we won't get a confirmation dialog since IE remembers our last choice.
We actually disable the page's styles.
Now we execute the copy command again.
We re-enable the stylesheets.
We remove the container from the page's DOM.
And we're done.
Caveats:
The formatted content will not be perfectly consistent across browsers.
As explained above, Chrome (i.e. the Blink engine) will use a different strategy than Firefox and IE: Chrome will copy the contents with their CSS styling, but omitting any styles that are not defined.
Firefox and IE on the other hand won't apply page-specific CSS, they will apply the browser's default styles. This also means they will have some weird styles applied to them, e.g. the default font (which is usually Times New Roman).
For security reasons, browsers will only allow the function to execute as an effect of a user interaction (e.g. a click, keypress etc.)
There is a much simpler solution. Copy a section of your page (element) than copying HTML.
With this simple function you can copy whatever you want (text, images, tables, etc.) on your page or the whole document to the clipboard.
The function receives the element id or the element itself.
function copyElementToClipboard(element) {
window.getSelection().removeAllRanges();
let range = document.createRange();
range.selectNode(typeof element === 'string' ? document.getElementById(element) : element);
window.getSelection().addRange(range);
document.execCommand('copy');
window.getSelection().removeAllRanges();
}
How to use:
copyElementToClipboard(document.body);
copyElementToClipboard('myImageId');
If you want to use the new Clipboard API, use the write method like below:
var type = "text/html";
var blob = new Blob([text], { type });
var data = [new ClipboardItem({ [type]: blob })];
navigator.clipboard.write(data).then(
function () {
/* success */
},
function () {
/* failure */
}
);
Currently(Sep 2021), The problem is that Firefox doesn't support this method.
I have done a few modifications on Loilo's answer above:
setting (and later restoring) the focus to the hidden div prevents FF going into endless recursion when copying from a textarea
setting the range to the inner children of the div prevents chrome inserting an extra <br> in the beginning
removeAllRanges on getSelection() prevents appending to existing selection (possibly not needed)
try/catch around execCommand
hiding the copy div better
On OSX this will not work. Safari does not support execCommand and chrome OSX has a known bug https://bugs.chromium.org/p/chromium/issues/detail?id=552975
code:
clipboardDiv = document.createElement('div');
clipboardDiv.style.fontSize = '12pt'; // Prevent zooming on iOS
// Reset box model
clipboardDiv.style.border = '0';
clipboardDiv.style.padding = '0';
clipboardDiv.style.margin = '0';
// Move element out of screen
clipboardDiv.style.position = 'fixed';
clipboardDiv.style['right'] = '-9999px';
clipboardDiv.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px';
// more hiding
clipboardDiv.setAttribute('readonly', '');
clipboardDiv.style.opacity = 0;
clipboardDiv.style.pointerEvents = 'none';
clipboardDiv.style.zIndex = -1;
clipboardDiv.setAttribute('tabindex', '0'); // so it can be focused
clipboardDiv.innerHTML = '';
document.body.appendChild(clipboardDiv);
function copyHtmlToClipboard(html) {
clipboardDiv.innerHTML=html;
var focused=document.activeElement;
clipboardDiv.focus();
window.getSelection().removeAllRanges();
var range = document.createRange();
range.setStartBefore(clipboardDiv.firstChild);
range.setEndAfter(clipboardDiv.lastChild);
window.getSelection().addRange(range);
var ok=false;
try {
if (document.execCommand('copy')) ok=true; else utils.log('execCommand returned false !');
} catch (err) {
utils.log('execCommand failed ! exception '+err);
}
focused.focus();
}
see jsfiddle where you can enter html segment into the textarea and copy to the clipboard with ctrl+c.
For those looking for a way to do this using ClipboardItem and cannot get it to work even with dom.events.asyncClipboard.clipboardItem set to true, the answer can be found at nikouusitalo.com.
And here is my working code (tested on Firefox 102).
const clipboardItem = new
ClipboardItem({'text/html': new Blob([html],
{type: 'text/html'}),
'text/plain': new Blob([html],
{type: 'text/plain'})});
navigator.clipboard.write([clipboardItem]).
then(_ => console.log("clipboard.write() Ok"),
error => alert(error));
Make sure you try pasting it into a rich text editor such as gmail and not to a plain text/markdown editor such as stackoverflow.
Hi I know the title of my post is quite tricky, so let me talk about it in detail here. I'm working on html+js page that gets the orientation value from my smartphone. The two devices are connected through google firebase, and it gets the realtime orientation value when my smartphone is on that page. I'm having trouble with connecting this value with the function of triggering the arrow keys.
For example, if the beta value is more than 0, up-key is triggered, etc.
I'm trying to use this orientation value to trigger up, down, left, right keys to be pressed accordingly, but I couldn't find any clue for it. What I get from google or stackflow was usually about connecting functions with the keyboard-events, but what I'm trying to do is the opposite; triggering keyboard-events based on conditions of the orientation values.
I'd appreciate if anyone can give me some clues or quick examples, so that I can twick. Sorry that I couldn't link any jsfiddles or part of the code since it's already connected with my firebase!
p.s. the code part I'm showing below is the part I checked it's working! I checked the orientations values are valid.
const controlAlpha = document.querySelector("#alpha").innerText;
const controlBeta = document.querySelector("#beta").innerText;
const controlGamma = document.querySelector("#gamma").innerText;
betaControl = function(){
if (textBeta<0){
console.log("beta under 0");
}
else{
console.log("beta over 0");
}
}
betaControl();
Fiddle here: https://jsfiddle.net/chpaeeL9/1/
Microsoft Bot Framework has a webchat module that allows you to talk to your bot.
When the user clicks the Say Hi button, I want to place some text into the webchat's textbox, and click the Send button inside the webchat using JavaScript.
Sounds like something too easy, but it wasn't. Here's the code that I currently have, and it doesn't work: the click event somehow is not triggered.
$('#sayhibutton').click(function() {
$('.wc-console').addClass('has-text'); // works
$('.wc-shellinput').val("Hi bot!").change(); // works
$('.wc-send').click(); // doesn't work!
$('.wc-send svg').click(); // doesn't work either
});
Update: if that helps, it seems the interface is written using React.
Update: my question is different from my previous question about how to avoid iframes in webchat.
OK, for the lack of a better option my solution was a pretty dirty and ugly one.
Save the code in botchat.js into a file and reference that saved file from the HTML, rather than the CDN version.
Pretty-print the file in Chrome and find the line that says:
e.prototype.sendMessage = function() {
this.props.inputText.trim().length > 0 && this.props.sendMessage(this.props.inputText)
}
Replace the middle line with this:
this.textInput.value.trim().length > 0 && this.props.sendMessage(this.textInput.value)
This basically means to take the message text from this.textInput.value rather than from this.props.inputText, or in other words, take it directly from the textinput's DOM node.
Somehow triggering the change event on a textbox doesn't cause an actual change event on the textbox, which is why we need to do this.
this is an issue with react try this,
var input = document.getElementsByClassName("wc-shellinput")[0];
var lastValue = input.value;
input.value = 'YOUR MESSAGE';
var event = new CustomEvent('input', { bubbles: true });
// hack React15
event.simulated = true;
// hack React16
var tracker = input._valueTracker;
if (tracker) {
tracker.setValue(lastValue);
}
input.dispatchEvent(event);
//send the message
$(".wc-send:first").click();
to read more see this post: https://github.com/Microsoft/BotFramework-WebChat/issues/680
I just started using SSE and wonder how I can make them more dynamic.
I'm using a Box to select users and an image and text changes corresponding to the username.
Now I want to check for user updates via SSE and want the user to be still selectable.
I tried to add the eventSource when I'm changing the <select> box:
function setSelected(elm) {
selectedName = elm.options[elm.selectedIndex].innerHTML;
var eSource = new EventSource("getState.php?passVar=" + selectedName);
eSource.onmessage = function(event) {
document.getElementById("stateText").innerHTML = event.data;
};
}
How can I reach my goal?
edit
I have now added the eventSource successfully (I had an issue with the source itself).
But when I now add another source, I have actually two sources running.
How can I remove the old one?
To remove the previous event source use the close() method. You're going to have to keep the reference to eSource around somehow to do this.