Split string and add extra word at onChange in Reactjs - html

I am trying to show on my page spotify links submitted by users. The problem is the standard Spotify share link is missing the 'embed' in part of the url that is needed to render it so I have been trying to use 'split' to adjust the url to an embed-able one, however I just cannot get it to work?
This is the function I am using to split the url and add the extra embed text
function spotify () {
const message = "urlInput";
let split = message.split(".com/");
let joined = split[0]+".com/embed/"+split[1];
}
This is the relevant part of html code I am using to get the users input
{
currentAccount ? (<textarea
placeholder={spotifyLink}
type="url"
id="urlInput"
value={messageValue}
onChange={e => {setMessageValue(e.target.value); {spotify}}} />) : null
}
<button className="waveButton" onClick={music}>
Submit
</button>
and the function attached to the button onClick
const music = async () => {
try {
const { ethereum } = window;
if (ethereum) {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const musicPortalContract = new ethers.Contract(contractAddress, contractABI, signer);
let count = await musicPortalContract.getTotalSongs();
console.log("Retrieved total song count...", count.toNumber());
const musicTxn = await musicPortalContract.music(messageValue);
await musicTxn.wait();
count = await musicPortalContract.getTotalSongs();
console.log("Retrieved total song count...", count.toNumber());
} else {
console.log("Ethereum object doesn't exist!");
}
} catch (error) {
console.log(error);
}
}
I would like to transform the url from this:
https://open.spotify.com/track/3u5N55tHf7hXATSQrjBh2q?si=8fe4896e171e4991
to this:
https://open.spotify.com/embed/track/46q5BtHso0ECuTKeq70ZhW?si=79e6006e92104e51
There might be a better way to do this than using .split but i'm not sure?
EDIT: Adding extra code here that is used for other functions such as getting the array of user inputs, incase it is useful.
const getAllMusic = async () => {
try {
const { ethereum } = window;
if (ethereum) {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const musicPortalContract = new ethers.Contract(contractAddress, contractABI, signer);
const music = await musicPortalContract.getAllMusic();
console.log("lets surf")
let musicArray = [];
musics.forEach(music => {
musicArray.push({
address: music.owner,
message: music.message
});
});
/* Store our data in React State*/
setAllMusic(musicArray);
} else {
console.log("Ethereum object doesn't exist!")
}
} catch (error) {
console.log(error);
}
}
and the related html to it
{allMusics.map((wave, index) => {
return (
<div key={index}>
<div><iframe src={music.message} width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe></div>

Related

How do I write an async request to get a markdown file's content? Svelte

I'm having a great time building my blog with Svelte, but I'm switching the structure to to be accessed through a JSON API.
Right now it's easy to get the markdown metadata and path, but I'd love to also get the content.
How would I modify this posts.json.js file to also get the content?
const allPostFiles = import.meta.glob('../blog/posts/*.md')
const iterablePostFiles = Object.entries(allPostFiles)
const allPosts = await Promise.all(
iterablePostFiles.map(async ([path, resolver]) => {
const { metadata } = await resolver()
const postPath = path.slice(2, -3)
return {
meta: metadata,
path: postPath
}
})
)
const sortedPosts = allPosts.sort((a, b) => {
return new Date(b.meta.date) - new Date(a.meta.date)
})
return {
body: sortedPosts
}
Install and enable the vite-plugin-markdown
// svelte.config.js
import { plugin as markdown, Mode } from "vite-plugin-markdown";
/** #type {import('#sveltejs/kit').Config} */
export default {
kit: {
vite: {
plugins: [markdown({ mode: Mode.HTML })],
},
},
};
then the content will be available as html and frontmatter data as attributes
iterablePostFiles.map(async ([path, resolver]) => {
const { attributes, html } = await resolver();
return {
attributes,
html,
path: path.slice(2, -3),
};
})
(I suggest adding the metadata into the markdown files via frontmatter )
The answer above works perfectly, but it also works to tweak the API with this code:
const allPosts = await Promise.all(
iterablePostFiles.map(async ([path, resolver]) => {
const { metadata } = await resolver()
// because we know every path will start with '..' and end with '.md', we can slice from the beginning and the end
const postPath = path.slice(2, -3)
const post = await resolver()
const content = post.default.render()
return {
meta: metadata,
path: postPath,
text: content
}
})
)
The important addition is this:
const post = await resolver()
const content = post.default.render()
using these variable chains to avoid using the JS reserved word default.

Weird behaviour with React Hooks and FileReader

I'm new to React Hooks and honestly I'm not sure if this problem is related to Hooks or if I'm just doing something generally wrong.
I want to build a image uploader comonent that uses the HTML5 FileReader in order to show users the uploaded images before actually POSTing them.
Below is what I have so far.
Basically <div id="from-effect"></div> is currently my way of checking whether the images could be rendered.
I first wanted to fill this <div> without side effects (like <div>I have {files.length} files</div>) but this didn't react to changes at all.
The solution below with useEffect is reacting to changes.
However, if you try uploading a few images you will notice that quite often it's showing wrong results.
function FileUploader(props) {
const [files, setFiles] = useState([]);
const loadImageContent = (name, newFiles) => {
return (e) => {
newFiles.push({ name: name, src: e.target.result });
};
}
const handleUpload = async (e) => {
const newFiles = [];
for (const file of e.target.files) {
const reader = new FileReader();
reader.onload = loadImageContent(file.name, newFiles);
await reader.readAsDataURL(file);
}
setFiles(newFiles);
}
useEffect(() => {
console.log('in use Effect, files:', files);
const prevCont = document.getElementById("from-effect");
prevCont.innerHTML = `I have ${files.length} files`;
});
return <div>
<input
type="file" name="fileUploader" id="fileUploader"
accept="image/*" multiple="multiple"
onChange={handleUpload}
/>
<div id="from-effect"></div>
</div>;
}
What am I doing wrong?
Or even better, how can I implement this without side effects?
I am not sure I follow your ultimate goal, or what you mean when you say you want to show users the uploaded images before POSTing them - do you want to POST automatically, or do you want the user to click an "upload/save/POST" button or something?
Here is an example of how to display images:
Edit: made things a little more clear, added "save" button which shows an alert that contains data you could possibly use to POST back to your server. Also, added a method to "JSONify" the file metadata, since the way we are uploading files does not let us natively convert [object File] into JSON.
const { useState } = React;
function FileUploader(props) {
const [files, setFiles] = useState([]);
const getFileMetadata = file => {
/**
* The way we are handling uploads does not allow us to
* turn the uploaded [object File] into JSON.
*
* Therefore, we have to write our own "toJSON()" method.
*/
return {
lastModified: file.lastModified,
name: file.name,
size: file.size,
type: file.type,
webkitRelativePath: file.webkitRelativePath
}
}
const handleUpload = e => {
let newstate = [];
for (let i = 0; i < e.target.files.length; i++) {
let file = e.target.files[i];
let metadata = getFileMetadata(file);
let url = URL.createObjectURL(file);
newstate = [...newstate, { url, metadata }];
}
setFiles(newstate);
};
const handleSave = () => {
alert(`POST Files Here..\n\n ${JSON.stringify(files,null,2)}`);
}
return (
<div>
<input type="file" accept="image/*" multiple onChange={handleUpload} />
<div>
<button onClick={handleSave} disabled={!(files && files.length > 0)}>
Save Image(s)
</button>
</div>
{files.map(f => {
return (
<div>
<img src={f.url} height="100" width="100" />
</div>
);
})}
</div>
);
}
ReactDOM.render(<FileUploader />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

Is it possible to populate the input bar in webchat with an onclick method

I'm attempting to display a list of popular questions to the user, when they click them I want them to populate the input bar and/or send the message to the bot via the directline connection.
I've attempted using the ReactDOM.getRootNode() and tracking down the input node and setting the .value attribute, but this does not populate the field. I assume there is some sort of form validation that prevents this.
Also, if I console log the input node then save it as a global variable in the console screen I can change the value that way, but then the message will not actually be able to be sent, hitting enter or the send arrow does nothing. While it may seem that the suggestedActions option would work well for this particular application, I CANNOT use it for this use case.
const [chosenOption, setChosenOption] = useState(null);
const getRootNode = (componentRoot) =>{
let root = ReactDom.findDOMNode(componentRoot)
let inputBar = root.lastChild.lastChild.firstChild.firstChild
console.log('Initial Console log ',inputBar)
setInputBar(inputBar)
}
//in render method
{(inputBar && chosenOption) && (inputBar.value = chosenOption)}
this is the function I tried to use to find the node, the chosen option works as intended, but I cannot change the value in a usable way.
I would like the user to click on a <p> element which changes the chosenOption value and for that choice to populate the input bar and/or send a that message to the bot over directline connection.What I'm trying to accomplish
You can use Web Chat's store to dispatch events to set the send box (WEB_CHAT/SET_SEND_BOX) or send a message (WEB_CHAT/SEND_MESSAGE) when an item gets clicked. Take a look at the code snippet below.
Simple HTML
<body>
<div class="container">
<div class="details">
<p>Hello World!</p>
<p>My name is TJ</p>
<p>I am from Denver</p>
</div>
<div class="wrapper">
<div id="webchat" class="webchat" role="main"></div>
</div>
</div>
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>
<script>
// Initialize Web Chat store
const store = window.WebChat.createStore();
// Get all paragraph elements and add on click listener
const paragraphs = document.getElementsByTagName("p");
for (const paragraph of paragraphs) {
paragraph.addEventListener('click', ({ target: { textContent: text }}) => {
// Dispatch set send box event
store.dispatch({
type: 'WEB_CHAT/SET_SEND_BOX',
payload: {
text
}
});
});
}
(async function () {
const res = await fetch('/directline/token', { method: 'POST' });
const { token } = await res.json();
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
store,
}, document.getElementById('webchat'));
document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));
</script>
</body>
React Version
import React, { useState, useEffect } from 'react';
import ReactWebChat, { createDirectLine, createStore } from 'botframework-webchat';
const WebChat = props => {
const [directLine, setDirectLine] = useState();
useEffect(() => {
const initializeDirectLine = async () => {
const res = await fetch('http://localhost:3978/directline/token', { method: 'POST' });
const { token } = await res.json();
setDirectLine(createDirectLine({ token }));
};
initializeDirectLine();
}, []);
return directLine
? <ReactWebChat directLine={directLine} {...props} />
: "Connecting..."
}
export default () => {
const [store] = useState(createStore());
const items = ["Hello World!", "My name is TJ.", "I am from Denver."]
const click = ({target: { textContent: text }}) => {
store.dispatch({
type: 'WEB_CHAT/SET_SEND_BOX',
payload: {
text
}
});
}
return (
<div>
<div>
{ items.map((item, index) => <p key={index} onClick={click}>{ item }</p>) }
</div>
<WebChat store={store} />
</div>
)
};
Screenshot
For more details, take a look at the Programmatic Post as Activity Web Chat sample.
Hope this helps!

How do I pass this string value into my page.evaluate in a reasonable way?

my code is pretty straightforward, the crawler object is a puppeteer instance:
crawler.selectorReturner = async function(page, selector) {
await page.waitForSelector(selector);
var returnSelector = async function(){
return selector;
}
await page.exposeFunction('returnSelector', returnSelector);
var getSelections = await page.evaluate(
() => {
var resultsobj = {
selections: []
};
var selector = returnSelector();
var selections = Array.from(document.body.querySelectorAll(selector), ({ selected }) => { return selected; });
resultsobj.selections = selections;
return resultsobj;
}
);
return getSelections;
}
an example of how I use crawler.selectorReturner
const initialhrefsObj = await crawler.selectorReturner(page,"a[href]");
but in all the various ways I have tried I am not able to get document.body.querySelectorAll(selector) to work
Evaluation failed: DOMException: Failed to execute 'querySelectorAll' on 'Element': '[object Promise]' is not a valid selector.
I obviously don't want to have a bunch of functions with the selectors hard coded I want to pass it in to the page evaluate. How can I pass a string in and have that string be available inside of my page.evaluate?
The #evaluate takes a variadic parameter in position 2 onward, so you're free to pass in selectors that way. This should look like:
var getSelections = await page.evaluate(
(selectorString) => {
var resultsobj = {
selections: []
};
var selector = returnSelector(selectorString);
var selections = Array.from(document.body.querySelectorAll(selector), ({ selected }) => { return selected; });
resultsobj.selections = selections;
return resultsobj;
},
'.my-selector' // Selector argument here
);

How to bypass CSP(Content-Security-Policy) using puppeteer's API page.addScriptTag?

scenario:
I use puppeteer launched chrome in headless mode, and call page.addScriptTag with an cross-domain javascript file. Now if the opening site has csp set and restricts only same origin javascript tags, how can I bypass this using puppeteer API?
Use:
await page.setBypassCSP(true)
Documentation
This is my first stackoverflow contribution so have mercy on me. I found this work around to allow you to get past CSP, Here.
The basic idea is that you intercept page requests and use a library like node-fetch to make the request and disable the CSP header when passing it back to chrome.
Here's the snippet that initially came from the github issue tracker.
Replace "example.com" with the website that needs to have CSP disabled.
const fetch = require('node-fetch')
const requestInterceptor = async (request) => {
try {
const url = request.url()
const requestHeaders = request.headers()
const acceptHeader = requestHeaders.accept || ''
if (url.includes("example.com") && (acceptHeader.includes('text/html'))) {
const cookiesList = await page.cookies(url)
const cookies = cookiesList.map(cookie => `${cookie.name}=${cookie.value}`).join('; ')
delete requestHeaders['x-devtools-emulate-network-conditions-client-id']
if (requestHeaders.Cookie) {
requestHeaders.cookie = requestHeaders.Cookie
delete requestHeaders.Cookie
}
const theseHeaders = Object.assign({'cookie': cookies}, requestHeaders, {'accept-language': 'en-US,en'})
const init = {
body: request.postData(),
headers: theseHeaders,
method: request.method(),
follow: 20,
}
const result = await fetch(
url,
init,
)
const resultHeaders = {}
result.headers.forEach((value, name) => {
if (name.toLowerCase() !== 'content-security-policy') {
resultHeaders[name] = value
} else {
console.log('CSP', `omitting CSP`, {originalCSP: value})
}
})
const buffer = await result.buffer()
await request.respond({
body: buffer,
resultHeaders,
status: result.status,
})
} else {
request.continue();
}
} catch (e) {
console.log("Error while disabling CSP", e);
request.abort();
}
}
await page.setRequestInterception(true)
page.on('request', requestInterceptor)