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 -![Screenshot](screenshot.jpg?raw=true) +![Screenshot](screenshot.gif?raw=true) -- 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("" + this.value + "", 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 @@ + + + Bubblebot says Hello! + + + + + + + + + + + + + + + + + + + +

Start Here.

+ + + + +
+ + +
+

+ 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. +

+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/component/examples/1-basic.html b/component/examples/1-basic.html new file mode 100644 index 0000000..15a3374 --- /dev/null +++ b/component/examples/1-basic.html @@ -0,0 +1,41 @@ + + + Chat Bubble Basics 1 + + + + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/component/examples/2-basic-conversation.html b/component/examples/2-basic-conversation.html new file mode 100644 index 0000000..d89ef63 --- /dev/null +++ b/component/examples/2-basic-conversation.html @@ -0,0 +1,79 @@ + + + Chat Bubble Basics 1 + + + + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/component/examples/3-basic-custom-startpoint.html b/component/examples/3-basic-custom-startpoint.html new file mode 100644 index 0000000..e29646b --- /dev/null +++ b/component/examples/3-basic-custom-startpoint.html @@ -0,0 +1,89 @@ + + + Chat Bubble Basics 1 + + + + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/component/examples/4-advanced-keyboard.html b/component/examples/4-advanced-keyboard.html new file mode 100644 index 0000000..7cce50b --- /dev/null +++ b/component/examples/4-advanced-keyboard.html @@ -0,0 +1,139 @@ + + + Chat Bubble Basics 1 + + + + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/component/examples/5-advanced-conversation-functions.html b/component/examples/5-advanced-conversation-functions.html new file mode 100644 index 0000000..b82de92 --- /dev/null +++ b/component/examples/5-advanced-conversation-functions.html @@ -0,0 +1,101 @@ + + + Chat Bubble Basics 1 + + + + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/component/start-here/images/eliot.jpg b/component/examples/images/tutor-eliot.jpg similarity index 100% rename from component/start-here/images/eliot.jpg rename to component/examples/images/tutor-eliot.jpg diff --git a/component/start-here/images/example-1.jpg b/component/examples/images/tutor-install.jpg similarity index 100% rename from component/start-here/images/example-1.jpg rename to component/examples/images/tutor-install.jpg diff --git a/component/start-here/images/json.jpg b/component/examples/images/tutor-json.jpg similarity index 100% rename from component/start-here/images/json.jpg rename to component/examples/images/tutor-json.jpg diff --git a/component/start-here/hello.html b/component/start-here/hello.html deleted file mode 100644 index 55865c2..0000000 --- a/component/start-here/hello.html +++ /dev/null @@ -1,336 +0,0 @@ - - - Bubblebot says Hello! - - - - - - - - - - - - - - - - - - -

Hello!

- - - - -
- - - - - - - - - - - - - \ No newline at end of file diff --git a/component/styles/input.css b/component/styles/input.css new file mode 100644 index 0000000..fa32614 --- /dev/null +++ b/component/styles/input.css @@ -0,0 +1,38 @@ +/* style user input field */ +.bubble-container .input-wrap { + position: absolute; + bottom: 0; + left: 0; + right: 0; + font-family: "Helvetica Neue", Helvetica, sans-serif; + color: #2c2c2c; + background: rgba(255, 255, 255, 0.95); +} +.bubble-container .input-wrap textarea { + width: calc(100% - 20px); + font-family: "Helvetica Neue", Helvetica, sans-serif; + color: #2c2c2c; + background: #fafafa; + font-size: 1em; + letter-spacing: .5px; + font-weight: 400; + margin: 10px; + border-radius: 15px; + border: none; + padding: 10px 15px; + outline: none; + box-shadow: 0 0 0 1px #d0d0d0 inset; + line-height: 1.25em; +} +.bubble.reply-freeform { + margin: 0; +} +.bubble.reply.reply-freeform.say .bubble-content .bubble-button { + margin-top: 1px; + text-align: left; +} +.bubble.reply.say.bubble-hidden { + margin: 0; + transform: scale(0); + height: 0; +} \ No newline at end of file diff --git a/component/styles/reply.css b/component/styles/reply.css index aac36ac..96c76dd 100644 --- a/component/styles/reply.css +++ b/component/styles/reply.css @@ -1,4 +1,4 @@ -/* style usr response reply */ +/* style user response reply */ .bubble.reply { background: transparent; box-shadow: none; diff --git a/ngrok b/ngrok new file mode 100755 index 0000000..d91fa50 Binary files /dev/null and b/ngrok differ diff --git a/screenshot.gif b/screenshot.gif new file mode 100644 index 0000000..f156512 Binary files /dev/null and b/screenshot.gif differ diff --git a/screenshot.jpg b/screenshot.jpg deleted file mode 100644 index 853ab68..0000000 Binary files a/screenshot.jpg and /dev/null differ