diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa5818d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Ignore tunneling tool +/ngrok diff --git a/README.md b/README.md index 6fe3d0e..7e0e965 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,29 @@ -# chat-bubble -### A super-simple, tiny script for creating chat bot interfaces +# 💬 chat-bubble +## A super-simple, tiny script for creating chat bot interfaces - + -- Super-easy to use, even if you don't code much! Here's the [tutorial](https://htmlpreview.github.io/?https://github.com/dmitrizzle/chat-bubble/blob/master/component/start-here/hello.html). -- Insanely small footprint, 1.03KB GZipped. -- No dependencies. Written with ES5, vanilla JavaScript. +- Super-easy to install & use! Here's the [tutorial bot](https://htmlpreview.github.io/?https://github.com/dmitrizzle/chat-bubble/blob/master/component/examples/0-tutor.html). +- Insanely small footprint, 1KB GZipped. +- No dependencies. Written with ES5, vanilla JavaScript. Works with modern browsers. + +### Usage +```html +
+``` + +```javascript + + // set up the chatbot script + var givemeBubbles = new Bubbles( + document.getElementById('chat'), // attach chatbot to placeholder above ^^ + "givemeBubbles" // you need to pass the name of the constructor variable that evokes Bubble function here + ); + + // pass JSON to your function and you're done! + givemeBubbles.talk({ "ice": { "says": [ "Hi" ] } }); +``` +Please see examples and the [tutorial bot](https://htmlpreview.github.io/?https://github.com/dmitrizzle/chat-bubble/blob/master/component/examples/0-tutor.html) link to learn more on how to use this thing. This component comes with styles in CSS files. They are separated in to multiple files. You can turn them into styled components, SASS or whatever for your convenience. For now it's just vanilla CSS so that this tool can work anywhere, regardless of the environment. diff --git a/component/Bubbles.js b/component/Bubbles.js index be231e7..190580a 100644 --- a/component/Bubbles.js +++ b/component/Bubbles.js @@ -1,18 +1,53 @@ function Bubbles(container, self, options) { // options - options = typeof options !== "undefined" ? options : {}; - animationTime = options.animationTime || 200; // how long it takes to animate chat bubble, also set in CSS - typeSpeed = options.typeSpeed || 5; // delay per character, to simulate the machine "typing" - widerBy = options.widerBy || 2; // add a little extra width to bubbles to make sure they don't break - sidePadding = options.sidePadding || 6; // padding on both sides of chat bubbles + options = typeof options !== "undefined" ? options : {}; + animationTime = options.animationTime || 200; // how long it takes to animate chat bubble, also set in CSS + typeSpeed = options.typeSpeed || 5; // delay per character, to simulate the machine "typing" + widerBy = options.widerBy || 2; // add a little extra width to bubbles to make sure they don't break + sidePadding = options.sidePadding || 6; // padding on both sides of chat bubbles + inputCallbackFn = options.inputCallbackFn || false; // should we display an input field? + var standingAnswer = "ice"; // remember where to restart convo if interrupted + + + // set up the stage container.classList.add("bubble-container"); var bubbleWrap = document.createElement("div"); bubbleWrap.className = "bubble-wrap"; container.appendChild(bubbleWrap); + // install user input textfield + this.typeInput = function(callbackFn){ + var inputWrap = document.createElement("div"); + inputWrap.className = "input-wrap"; + var inputText = document.createElement("textarea"); + inputText.setAttribute("placeholder", "Type your question..."); + inputWrap.appendChild(inputText); + inputText.addEventListener("keypress", function(e){ // register user input + if(e.keyCode == 13){ + e.preventDefault(); + typeof bubbleQueue !== false ? clearTimeout(bubbleQueue) : false; // allow user to interrupt the bot + var lastBubble = document.querySelectorAll(".bubble.say"); lastBubble = lastBubble[lastBubble.length-1]; + lastBubble.classList.contains("reply") && !lastBubble.classList.contains("reply-freeform") ? lastBubble.classList.add("bubble-hidden") : false; + addBubble(" ", function(){}, "reply reply-freeform"); + // callback + typeof callbackFn === "function" ? callbackFn({ + "input" : this.value, + "convo" : convo, + "standingAnswer": standingAnswer + }) : false; + this.value = ""; + } + }); + container.appendChild(inputWrap); + bubbleWrap.style.paddingBottom = "100px"; + inputText.focus(); + } + inputCallbackFn ? this.typeInput(inputCallbackFn) : false; + + // init typing bubble var bubbleTyping = document.createElement("div"); @@ -25,9 +60,10 @@ function Bubbles(container, self, options) { bubbleWrap.appendChild(bubbleTyping); // accept JSON & create bubbles - this.talk = function(convo) { + this.talk = function(convo, here) { this.convo = convo; - this.reply(); + this.reply(this.convo[here]); + here ? standingAnswer = here : false; } this.reply = function(turn) { turn = typeof turn !== "undefined" ? turn : this.convo.ice; @@ -52,7 +88,7 @@ function Bubbles(container, self, options) { // navigate "answers" this.answer = function(key){ var func = function(key){ typeof window[key] === "function" ? window[key]() : false; } - this.convo[key] !== undefined ? this.reply(this.convo[key]) : func(key); + this.convo[key] !== undefined ? (this.reply(this.convo[key]), standingAnswer = key) : func(key); }; // api for typing bubble @@ -77,7 +113,10 @@ function Bubbles(container, self, options) { start(); } + + // create a bubble + var bubbleQueue = false; var addBubble = function(say, posted, reply){ reply = typeof reply !== "undefined" ? reply : ""; // create bubble element @@ -91,7 +130,10 @@ function Bubbles(container, self, options) { // answer picker styles if(reply !== ""){ var bubbleButtons = bubbleContent.querySelectorAll(".bubble-button"); - bubbleButtons.forEach(function(el){ el.style.width = el.offsetWidth - sidePadding * 2 + widerBy + "px"; }); + bubbleButtons.forEach(function(el){ + if(!el.parentNode.parentNode.classList.contains("reply-freeform")) + el.style.width = el.offsetWidth - sidePadding * 2 + widerBy + "px"; + }); bubble.addEventListener("click", function(){ bubbleButtons.forEach(function(el){ el.style.width = 0 + "px"; @@ -110,7 +152,7 @@ function Bubbles(container, self, options) { setTimeout(function() { bubbleTyping.classList.remove("imagine"); }, animationTime ); } setTimeout(function() { bubbleTyping.classList.add("imagine"); }, wait - animationTime * 2 ); - setTimeout(function() { + bubbleQueue = setTimeout(function() { bubble.classList.remove("imagine"); var bubbleWidthCalc = bubbleContent.offsetWidth + widerBy + "px"; bubble.style.width = reply == "" ? bubbleWidthCalc : ""; @@ -134,4 +176,6 @@ function Bubbles(container, self, options) { }, wait + animationTime * 2); } -} \ No newline at end of file + + +} // close function \ No newline at end of file diff --git a/component/example-help.html b/component/example-help.html deleted file mode 100644 index 8328e69..0000000 --- a/component/example-help.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/component/examples/0-tutor.html b/component/examples/0-tutor.html new file mode 100644 index 0000000..4537722 --- /dev/null +++ b/component/examples/0-tutor.html @@ -0,0 +1,229 @@ + + ++ Note: This is a simple script that isn't connected to any Natural Language Cognition networks. Whatever you type on the keyboard will be matched against the possible options given (dark grey bubbles). This script can't correct your spelling, it can't infer from synonyms or slang etc. But it is forgiving in terms of punctuation, capitalization and amount of text you type. For example, if one of the accepted answers is "Do 👉 this," you can simply type "do" and it'll work. +
+