I am developing a chatbot using peekabot (link provided below for documentation and official example), and want to be able to open a URL in a new tab (as you would have the option to do using plain html) when a user clicks on an option. Linked with this is the fact that the URL button that is generated is styled wrong (shows up as blank without the label text on mouse-hover-over).
I have included the whole script below for context, but the bit I am referring to is:
6: {
text: 'You should check it out on test preview',
options: [{
text: "PREVIEW THIS LINK",
url: "www.google.com"
}]
},
The above is option 6 and on clicking it the user should go to the URL (e.g. google.com) but open in a new tab and also not be blank (the button link is blank on hover over for some reason)
I have tried: url: "www.google.com" target="_blank (see below) but this breaks the whole javascript code.
6: {
text: 'You should check it out on test preview',
options: [{
text: "PREVIEW THIS LINK",
url: "www.google.com" target="_blank
}]
},
I have also tried:
url: "www.google.com target="_blank"
and
url: "www.google.com target=_blank"
neither works.
For an answer:
Solution for opening the URL in a new tab
Correction so that the button link (for the URL) is not BLANK on hover-over. (when you move the mouse away from the button link, the text appears)
This is the official site - https://peekobot.github.io/peekobot/ but irritatingly even in their example - the url to GitHub is opened in the same tab.
The whole script below for context:
</script>
<script type="text/javascript" src="<?php echo base_url(''); ?>js/bot.js>v=1"></script>
<script>
const chat = {
1: {
text: 'Some Text here',
options: [{
text: 'More Text here',
next: 101
},
{
text: '??',
next: 201
}
,
{
text: '?????',
next: 301
}
]
},
101: {
text: 'Some information here,
options: [{
text: 'That is great, thanks!',
next: 102
},
{
text: 'I would like some more info please.',
next: 103
}]
},
102: {
text: 'Thanks and Goodbye',
url: siteurl + "index.php/student/test",
options: [{
text: 'Bye and thank you.',
next: 3
},
{
text: 'I would like some more info please.',
next: 104
}]
},
103: {
text: 'Info here',
options: [{
text: 'That is great, thanks!',
next: 103
},
{
text: 'I would like some more info please.',
next: 104
}]
},
5: {
text: 'Aah, you\'re missing out!',
next: 6
},
6: {
text: 'You should check it out on test preview',
options: [{
text: "Go to PRIVIEW",
url: "www.google.com"
}]
},
};
const bot = function () {
const peekobot = document.getElementById('peekobot');
const container = document.getElementById('peekobot-container');
const inner = document.getElementById('peekobot-inner');
let restartButton = null;
const sleep = function (ms) {
return new Promise(resolve => setTimeout(resolve, ms));
};
const scrollContainer = function () {
inner.scrollTop = inner.scrollHeight;
};
const insertNewChatItem = function (elem) {
//container.insertBefore(elem, peekobot);
peekobot.appendChild(elem);
scrollContainer();
//debugger;
elem.classList.add('activated');
};
const printResponse = async function (step) {
const response = document.createElement('div');
response.classList.add('chat-response');
response.innerHTML = step.text;
insertNewChatItem(response);
await sleep(1500);
if (step.options) {
const choices = document.createElement('div');
choices.classList.add('choices');
step.options.forEach(function (option) {
const button = document.createElement(option.url ? 'a' : 'button');
button.classList.add('choice');
button.innerHTML = option.text;
if (option.url) {
button.href = option.url;
} else {
button.dataset.next = option.next;
}
choices.appendChild(button);
});
insertNewChatItem(choices);
} else if (step.next) {
printResponse(chat[step.next]);
}
};
const printChoice = function (choice) {
const choiceElem = document.createElement('div');
choiceElem.classList.add('chat-ask');
choiceElem.innerHTML = choice.innerHTML;
insertNewChatItem(choiceElem);
};
const disableAllChoices = function () {
const choices = document.querySelectorAll('.choice');
choices.forEach(function (choice) {
choice.disabled = 'disabled';
});
return;
};
const handleChoice = async function (e) {
if (!e.target.classList.contains('choice') || 'A' === e.target.tagName) {
// Target isn't a button, but could be a child of a button.
var button = e.target.closest('#peekobot-container .choice');
if (button !== null) {
button.click();
}
return;
}
e.preventDefault();
const choice = e.target;
disableAllChoices();
printChoice(choice);
scrollContainer();
await sleep(1500);
if (choice.dataset.next) {
printResponse(chat[choice.dataset.next]);
}
// Need to disable buttons here to prevent multiple choices
};
const handleRestart = function () {
startConversation();
}
const startConversation = function () {
printResponse(chat[1]);
}
const init = function () {
container.addEventListener('click', handleChoice);
restartButton = document.createElement('button');
restartButton.innerText = "Restart";
restartButton.classList.add('restart');
restartButton.addEventListener('click', handleRestart);
container.appendChild(restartButton);
startConversation();
};
init();
}
bot();
</script>
UPDATE:
Based on a suggestion below, I've tried:
if (step.options) {
const choices = document.createElement('div');
choices.classList.add('choices');
step.options.forEach(function (option) {
const button = document.createElement(option.url ? 'a' : 'button');
button.classList.add('choice');
button.innerHTML = option.text;
if (option.url) {
button.href = option.url;
if (option.target) {
button.target = option.target;
} else {
button.dataset.next = option.next;
}
choices.appendChild(button);
});
insertNewChatItem(choices);
} else if (step.next) {
printResponse(chat[step.next]);
}
};
This breaks the whole code so the chatbot doesn't run at all.
What I'm thinking is you would have to modify the code or get the author to modify the code for you.
I'm looking at the main js code here: https://github.com/Peekobot/peekobot/blob/master/peekobot.js
This snippet is what I am looking at:
step.options.forEach(function (option) {
const button = document.createElement(option.url ? 'a' : 'button');
button.classList.add('choice');
button.innerHTML = option.text;
if (option.url) {
button.href = option.url;
} else {
button.dataset.next = option.next;
}
choices.appendChild(button);
});
That last part would get changed to something like this, I would think.
if (option.url) {
button.href = option.url;
if (option.target) {
button.target = option.target;
}
} else {
...
Related
I am working on a project that requires internationalisation using AWS for translation. I currently store translations in objects that are recursively
looped through, each string being sent off for translation and then re-added to the object. I need to leave the object key untranslated.
However, I am experiencing inconsistent results, most of the time the full object is returned translated, however sometimes there are values missing,
and the ones that are missing are inconsistent as well, sometimes there, sometimes not.
I think the issue may be to do with sending hundreds of requests to the API in a short space of time.
QUESTION:
Any ideas what the issue could be?
What is the best way for translating an object using AWS Translate API?
Is there a way to send the entire object off at once keeping keys un-mutated?
Below is my code for translation:
const translateText = async ({ text = '', sourceLang, targetLang }) => {
if (!targetLang || !sourceLang) {
throw new Error('Missing source or target lang');
}
const params = {
SourceLanguageCode: sourceLang,
TargetLanguageCode: targetLang,
Text: text,
};
try {
const translationData = await translateAWS.translateText(params).promise();
return translationData.TranslatedText;
} catch (error) {
throw new Error('translateText API error :>> ', error);
}
};
const translateJSON = async ({
obj,
targetLang,
sourceLang,
displayLang = true,
}) => {
const response = {};
for (const key in obj) {
let word = '';
try {
if (typeof obj[key] === 'object') {
word = await translateJSON({
obj: obj[key],
targetLang,
sourceLang,
displayLang: false,
});
} else {
word = await translateText({ text: obj[key], sourceLang, targetLang });
}
} catch (error) {
Sentry.captureException('translateJSON API error:', error);
word = '';
}
if (displayLang) {
response[targetLang] = response[targetLang] || {};
response[targetLang][key] = word;
} else {
response[key] = word;
}
}
return response;
};
Example object to translate:
const common = {
buttons: {
readMore: 'Read more',
seeAdvice: 'See advice',
goBack: 'Go back',
accessibility: 'Accessibility',
decreaseTextSize: '- Decrease text size',
increaseTextSize: '+ Increase text size',
seeMore: 'See more',
seeLess: 'See less',
addATip: 'Add a tip',
addAnotherTip: 'Add another tip',
addColourOverlay: 'Add colour overlay',
},
words: { and: 'and' },
placeholders: { select: 'Select...' },
heading: {
shareThisPage: 'Share this page',
helpfulResources: 'Helpful resources',
},
section: {
subSection: {
description:
'So we can show you the best information, which one of these best describes you?',
},
changeLanguage: {
title: 'Change language',
placeholder: 'Search',
},
helpMe: {
title: 'Help!',
subtitle: 'Text here',
},
helpBudget: {
title: 'Need help with budgeting?',
},
colors: [
{
label: 'Blue',
},
{
label: 'Green',
},
{
label: 'Yellow',
},
{
label: 'Red',
},
],
};
export default common;
i am using react quill editor for my project and using my backend server to image upload but i need to access props inside the image handler of react quill and i am unable to do so as not able to access this object inside image handler.
here is my editor code.
<ReactQuill
ref={(el) => (this.quillRef = el)}
onChange={this.handleChange}
placeholder={"share your thoughts"}
modules={{
toolbar: {
container: [
[{ header: "1" }, { header: [2, 3, 4, 5, 6] }, { font: [] }],
[{ size: ["small", false, "large", "huge"] }],
["bold", "italic", "underline", "strike", "blockquote"],
[{ list: "ordered" }, { list: "bullet" }],
["link", "image", "video"],
["clean"],
["code-block"],
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
[{ align: [] }],
],
handlers: {
image: this.imageHandler,
},
},
}}
/>;
function imageHandler() {
let self = this;
let image;
let image_extension;
const Cryptr = require("cryptr");
const cryptr = new Cryptr(key);
const users = localStorage.getItem("users")
? JSON.parse(cryptr.decrypt(localStorage.getItem("users")))
: {};
// console.log(users[users.lastLoginId])
let loggedinUser = users[users.lastLoginId];
const input = document.createElement("input");
input.setAttribute("type", "file");
input.setAttribute("accept", "image/*");
input.setAttribute("class", "Editor-mage");
input.click();
input.onchange = async () => {
//debugger
const file = input.files[0];
var ValidImageTypes = [
"image/gif",
"image/jpeg",
"image/png",
"image/jpg",
"image/GIF",
"image/JPEG",
"image/PNG",
"image/JPG",
];
let file_type = file.type;
let filename = file.name;
let extension = filename.split(".").pop();
if (ValidImageTypes.indexOf(file_type) >= 0) {
if (true) {
var fileToLoad = file;
loadImage(
fileToLoad,
(canvas) => {
if (canvas) {
// this.setState({
image = canvas.toDataURL();
image_extension = extension;
//});
const res = new Promise(function (resolve, reject) {
axios({
method: "post",
url: API_URL + "api/v1/postblogimage",
headers: {
"x-access-handler": loggedinUser.token,
},
data: {
image: image,
image_extension: image_extension,
userid: loggedinUser.userid,
},
})
//axios.post(API_URL + 'api/v1/postblogimage', formData, config)
.then((response) => {
//debugger
if (
response.data.error == "false" ||
response.data.error == false
) {
if (
response.data.status == 200 &&
response.data.message == "Image uploaded successfully"
) {
//debugger
const range = self.quill.getSelection(true);
// Insert temporary loading placeholder image
// this.quill.insertEmbed(range.index, 'image', `${window.location.origin}/images/loaders/placeholder.gif`);
// Move cursor to right side of image (easier to continue typing)
self.quill.setSelection(range.index + 1);
// Remove placeholder image
self.quill.deleteText(range.index, 1);
// Insert uploaded image
let url = response.data.data[0].imageURL;
self.quill.insertEmbed(range.index, "image", url);
self.quill.pasteHTML(
range.index,
<img
src={url}
class="blog-image-content"
alt="Responsive image"
/>
);
}
}
// }
})
.catch((error) => {
// reject(Error("It broke"));
});
});
}
},
{ orientation: true }
);
} else {
// this.setState({
// image_warning:'File size larger than maximum allowed limit',
image = "";
image_extension = "";
// })
this.fileInput.value = "";
}
} else {
}
};
}
can someone please help me out with this one as i am stuck for long on this.
any help and suggestion will be greatly appreciated.
I read the documentation of Quilljs.
Handler functions will be bound to the toolbar (so using this will refer to the toolbar instance) and passed the value attribute of the input if the corresponding format is inactive, and false otherwise. Adding a custom handler will overwrite the default toolbar and theme behavior.
and found out the imageHandler will be called in the context of the toolbar, so this.props will not work as intended when it is called.
so to achieve the accesss to props , you can do something like this:
handlers: {
image: (val) => this.imageHandler({ val, componentProps: this.props });
}
In the imageHandler you can access it like this:
function imageHandler({ val, componentProps }) {
// componentProps has all your propss, try to print it and see
// rest of your code, instead of this.props.something take componentProps.something
}
Let me know if it helps. Thanks
I think you can use higher order function.
image: this.imageHandler(props)
...
function imageHandler(props) {
return function() {
let self = this;
let image;
...
}
}
How do you add a custom button to the grapesjs toolbar?
I have followed the instructions on this github issue and written the code below, but the button doesn't appear in the toolbar as expected.
What am I missing?
initToolbar() {
const { em } = this;
const model = this;
const ppfx = (em && em.getConfig('stylePrefix')) || '';
if (!model.get('toolbar')) {
var tb = [];
if (model.collection) {
tb.push({
attributes: { class: 'fa fa-arrow-up' },
command: ed => ed.runCommand('core:component-exit', { force: 1 })
});
}
if (model.get('draggable')) {
tb.push({
attributes: {
class: `fa fa-arrows ${ppfx}no-touch-actions`,
draggable: true
},
//events: hasDnd(this.em) ? { dragstart: 'execCommand' } : '',
command: 'tlb-move'
});
}
if (model.get('schedule')) {
tb.push({
attributes: { class: 'fa fa-clock', },
command: 'tlb-settime'
});
}
if (model.get('copyable')) {
tb.push({
attributes: { class: 'fa fa-clone' },
command: 'tlb-clone'
});
}
if (model.get('removable')) {
tb.push({
attributes: { class: 'fa fa-trash-o' },
command: 'tlb-delete'
});
}
model.set('toolbar', tb);
}
},
One way to add new toolbar icons is to add the button as each component is selected.
// define this event handler after editor is defined
// like in const editor = grapesjs.init({ ...config });
editor.on('component:selected', () => {
// whenever a component is selected in the editor
// set your command and icon here
const commandToAdd = 'tlb-settime';
const commandIcon = 'fa fa-clock';
// get the selected componnet and its default toolbar
const selectedComponent = editor.getSelected();
const defaultToolbar = selectedComponent.get('toolbar');
// check if this command already exists on this component toolbar
const commandExists = defaultToolbar.some(item => item.command === commandToAdd);
// if it doesn't already exist, add it
if (!commandExists) {
selectedComponent.set({
toolbar: [ ...defaultToolbar, { attributes: {class: commandIcon}, command: commandToAdd }]
});
}
});
If it's important to you that only components with the "schedule" attribute have this toolbar option show up, as in your example, you can access and check this from selectedComponent:
const selectedComponent = editor.getSelected();
const defaultToolbar = selectedComponent.get('toolbar');
const commandExists = defaultToolbar.some(item => item.command === commandToAdd);
// add this
const hasScheduleAttribute = selectedComponent.attributes.schedule;
if (!commandExists && hasScheduleAttribute) { // ...set toolbar code
I was following a tutorial (https://www.youtube.com/watch?v=R1S_NhKkvGA) on how to make a text adventure game in javascript, and wanted to re purpose that code into a game of my own, except depending on what option you choose, the image changes. How would I do that? Original code can be found here (https://codepen.io/WebDevSimplified/pen/xoKZbd), but heres the basic gist of the code:
const textElement = document.getElementById('text')
const optionButtonsElement = document.getElementById('option-buttons')
let state = {}
function startGame() {
state = {}
showTextNode(1)
}
function showTextNode(textNodeIndex) {
const textNode = textNodes.find(textNode => textNode.id === textNodeIndex)
textElement.innerText = textNode.text
while (optionButtonsElement.firstChild) {
optionButtonsElement.removeChild(optionButtonsElement.firstChild)
}
textNode.options.forEach(option => {
if (showOption(option)) {
const button = document.createElement('button')
button.innerText = option.text
button.classList.add('btn')
button.addEventListener('click', () => selectOption(option))
optionButtonsElement.appendChild(button)
}
})
}
function showOption(option) {
return option.requiredState == null || option.requiredState(state)
}
function selectOption(option) {
const nextTextNodeId = option.nextText
if (nextTextNodeId <= 0) {
return startGame()
}
state = Object.assign(state, option.setState)
showTextNode(nextTextNodeId)
}
const textNodes = [
{
id: 1,
text: 'You wake up in a strange place and you see a jar of blue goo near you.',
options: [
{
text: 'Take the goo',
setState: { blueGoo: true },
nextText: 2
},
{
text: 'Leave the goo',
nextText: 2
}
]
}
My changes are just the text on the buttons and the story text.
You can try this in the selectOption function:
function selectOption(option) {
const nextTextNodeId = option.nextText
switch (nextTextNodeId) {
case 2:
document.body.style.backgroundImage = "url('https://images.pexels.com/photos/2886284/pexels-photo-2886284.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260')";
break;
}
if (nextTextNodeId <= 0) {
return startGame()
}
state = Object.assign(state, option.setState)
showTextNode(nextTextNodeId)
}
Now you just have to add new cases with your preferred image.
I have an Electron app with 3 windows and each window has a different menu. The menu template code for each menu is quite long and I would like to externalize it. So far nothing I have tried works.
I've tried different ways to "modularize" it but got lots of errors. The approach below works to set up the menu, but none of the functions referenced in the menu work (e.g. quitApplication).
Is what I am trying to do not possible or am I just "doing it wrong"?
var test = require("./app/js/menuTest.js");
var tm = new test();
var menuTemplate = tm.getMenu();
myWindow = Menu.buildFromTemplate(menuTemplate);
menuTest.js
function testMenu() {
this.getMenu = function () {
var menuTemplate = [
{
label: global.productData.appName,
submenu: [
{ label: 'About ' + global.productData.appName, click: () => { showAboutWindow() } },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ label: 'Quit', click: () => { quitApplication() }, accelerator: 'CmdOrCtrl+q' }
]
// code deleted for clarity
return menuTemplate;
}
}
module.exports = testMenu;
From how I understand your question, you want to move the template code out of your main process script, but keep the functions in there.
This can be achieved by moving the menu structure object into a separate module. The module exports a function that takes an object with references to the functions you want to call in the menu.
I believe this does not add significant complexity and "externalizes" just the menu template code.
menu1.js:
module.exports = function(actions) {
return [
{
label: "Foo",
submenu: [
{ label: "Bar", click: actions.bar },
{ label: "About", click: actions.about }
]
}
];
}
main.js:
const {app,BrowserWindow,Menu} = require("electron");
const actions = {
bar: function () {
console.log("bar");
},
about: function () {
console.log("about");
}
};
const menu1_template = require("./menu1.js")(actions);
const menu1 = Menu.buildFromTemplate(menu1_template);
Menu.setApplicationMenu(menu1);
let mainWindow;
app.on("ready", function() {
mainWindow = new BrowserWindow();
});