├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── css └── style.css ├── gulpfile.js ├── index.html ├── js ├── app.js └── app.min.js └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mikael Jergefelt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # conversational-ui 2 | 3 | Conversational UI web app experiment. [Demo](https://librarian.dev/conversational-ui/) 4 | 5 | ![](https://raw.githubusercontent.com/dermike/conversational-ui-referencing-guide/master/screenshot/screenshot.jpg) 6 | 7 | ## Setup 8 | 9 | ### js/app.js 10 | 11 | Built upon the structure `Category -> Sub-category -> Info` as defined in `js/app.js`. Also check this file for customising the selection of replies, randomly selected for each step. See [this repo](https://github.com/dermike/conversational-ui-referencing-guide) for a real world example using AJAX for content. 12 | 13 | ### index.html 14 | 15 | Content view for the info step is defined here under `.content` div. Id's from menu and submenu need to be combined. 16 | 17 | ### Build 18 | 19 | This project mainly uses ES2015 JavaScript features. Can be run in Chrome or Safari Technical Preview without a hitch, but needs transpilation with [Babel](https://babeljs.io) for other browsers using [Gulp](http://gulpjs.com). 20 | 21 | To transpile, install [Node.js](https://nodejs.org) and [Gulp](http://gulpjs.com) if you haven't already. Then install the project dependencies required, listed in `package.json`, with: 22 | 23 | ```sh 24 | npm install 25 | ``` 26 | 27 | Then run the transpile task defined in `gulpfile.js`: 28 | 29 | ```sh 30 | gulp js 31 | ```` 32 | 33 | This transpiles and minifies to `js/app.min.js`. 34 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | height: 100%; 4 | } 5 | 6 | *, *:before, *:after { 7 | box-sizing: inherit; 8 | } 9 | 10 | body { 11 | background: #fff; 12 | height: 100%; 13 | margin: 0; 14 | padding: 0; 15 | font-family: HelveticaNeue, "Helvetica Neue", Helvetica, Arial, sans-serif; 16 | font-size: 16px; 17 | line-height: 1.5; 18 | overflow: hidden; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | 23 | h1, h2, h3, h4, h5, h6 { 24 | margin: 1em 0; 25 | line-height: 1.2; 26 | } 27 | 28 | h1 { 29 | font-size: 36px; 30 | } 31 | 32 | h2 { 33 | font-size: 30px; 34 | } 35 | 36 | h3 { 37 | font-size: 24px; 38 | } 39 | 40 | h4 { 41 | font-size: 18px; 42 | } 43 | 44 | .container { 45 | width: 768px; 46 | max-width: 100%; 47 | margin: 0 auto; 48 | height: 100%; 49 | position: fixed; 50 | overflow: hidden; 51 | top: 0; 52 | left: 0; 53 | right: 0; 54 | bottom: 0; 55 | } 56 | 57 | .chat, .content { 58 | position: absolute; 59 | width: 100%; 60 | max-width: 100%; 61 | top: 0; 62 | left: 0; 63 | right: 0; 64 | bottom: 0; 65 | overflow-x: hidden; 66 | overflow-y: auto; 67 | -webkit-overflow-scrolling: touch; 68 | } 69 | 70 | .chat { 71 | height: 100%; 72 | padding: 1em; 73 | margin: 0; 74 | } 75 | 76 | .content { 77 | background-color: #066; 78 | color: #fff; 79 | -webkit-transition: transform 0.35s ease; 80 | transition: transform 0.35s ease; 81 | -webkit-transform: translateX(-102%); 82 | transform: translateX(-102%); 83 | margin: 0; 84 | outline: 0; 85 | } 86 | 87 | .content article { 88 | padding: 1em; 89 | display: none; 90 | overflow-y: auto; 91 | } 92 | 93 | .content article h3 { 94 | margin-top: 2em; 95 | } 96 | 97 | .content article.show { 98 | display: block; 99 | } 100 | 101 | .content .close { 102 | position: absolute; 103 | top: 0; 104 | right: 0; 105 | font-size: 2.5em; 106 | margin-right: 0.5em; 107 | cursor: pointer; 108 | z-index: 101; 109 | background-color: transparent; 110 | border: none; 111 | color: #fff; 112 | padding: 0; 113 | } 114 | 115 | .content[aria-hidden="true"] button, .content[aria-hidden="true"] a { 116 | display: none; 117 | } 118 | 119 | .content[aria-hidden="false"] { 120 | -webkit-transform: translateX(0%); 121 | transform: translateX(0%); 122 | } 123 | 124 | @media (min-width: 769px) { 125 | .container { 126 | border-left: 1px solid #e1e1e1; 127 | border-right: 1px solid #e1e1e1; 128 | } 129 | .content article { 130 | padding: 1em 2em; 131 | } 132 | } 133 | 134 | .message { 135 | margin: 0.8em 0; 136 | padding: 0.2em 0; 137 | max-width: 80%; 138 | width: auto; 139 | border-radius: 8px; 140 | position: relative; 141 | -webkit-transition: transform 0.3s ease; 142 | transition: transform 0.3s ease; 143 | display: table; 144 | } 145 | 146 | .message.user { 147 | max-width: 100%; 148 | margin: 0 auto; 149 | } 150 | 151 | .message.user.selected { 152 | max-width: 80%; 153 | background-color: #066; 154 | color: #fff; 155 | margin: 0.8em 0 0.8em auto; 156 | } 157 | 158 | .message.bot p { 159 | margin: 0.5em 1em; 160 | } 161 | 162 | .message.bot a { 163 | color: #fff; 164 | } 165 | 166 | .message.user p, .message.user nav { 167 | margin: 0.25em 0 0 0; 168 | text-align: center; 169 | } 170 | 171 | .message.user.selected p { 172 | margin: 0.5em 1em; 173 | text-align: left; 174 | } 175 | 176 | .message.bot:after { 177 | content: ' '; 178 | position: absolute; 179 | width: 0; 180 | height: 0; 181 | left: -1em; 182 | right: auto; 183 | top: 0.4em; 184 | bottom: auto; 185 | border: 12px solid; 186 | border-color: #933 #933 transparent transparent; 187 | } 188 | 189 | .message.user.selected:after { 190 | content: ' '; 191 | position: absolute; 192 | width: 0; 193 | height: 0; 194 | left: auto; 195 | right: -1em; 196 | top: 0.4em; 197 | bottom: auto; 198 | border: 12px solid; 199 | border-color: transparent transparent #066 #066; 200 | } 201 | 202 | .message.bot { 203 | background-color: #933; 204 | color: #fff; 205 | -webkit-transform: translateX(-120%); 206 | transform: translateX(-120%); 207 | } 208 | 209 | .message.bot.show { 210 | -webkit-transform: translateX(0%); 211 | transform: translateX(0%); 212 | } 213 | 214 | .message.user button { 215 | font-size: 1em; 216 | line-height: 1.5; 217 | background-color: #e1e1e1; 218 | color: #232323; 219 | border: 0; 220 | padding: 0.5em 1em; 221 | border-radius: 6px; 222 | margin: 0 0.3em 0.5em 0; 223 | cursor: pointer; 224 | -webkit-transition: transform 0.3s ease; 225 | transition: transform 0.3s ease; 226 | -webkit-transform: translateX(768px); 227 | transform: translateX(768px); 228 | } 229 | 230 | .message.user button.show { 231 | -webkit-transform: translateX(0px); 232 | transform: translateX(0px); 233 | } 234 | 235 | .message.user button.selected { 236 | background-color: #b0ca3b; 237 | } 238 | 239 | .message.user button.newmenu { 240 | background-color: transparent; 241 | border: 1px solid #e1e1e1; 242 | } 243 | 244 | a { 245 | color: #fff; 246 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const gulp = require('gulp'); 3 | const uglify = require('gulp-uglify'); 4 | const rename = require('gulp-rename'); 5 | const babel = require('gulp-babel'); 6 | 7 | gulp.task('js', () => { 8 | gulp.src('js/app.js') 9 | .pipe(babel()) 10 | .pipe(uglify()) 11 | .pipe(rename('app.min.js')) 12 | .pipe(gulp.dest('js')); 13 | }); 14 | 15 | gulp.task('default', ['js']); 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Conversational UI Demo 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | 17 | 22 |
23 | 58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | { 2 | 'use strict'; 3 | let structure = { 4 | 'menu': [ 5 | { 6 | 'title': 'Projects', 7 | 'id': 'projects', 8 | 'submenu': [ 9 | { 10 | 'title': 'Project 1', 11 | 'id': 'p1' 12 | }, 13 | { 14 | 'title': 'Project 2', 15 | 'id': 'p2' 16 | }, 17 | { 18 | 'title': 'Project 3', 19 | 'id': 'p3' 20 | } 21 | ] 22 | }, 23 | { 24 | 'title': 'Writing', 25 | 'id': 'writing', 26 | 'submenu': [ 27 | { 28 | 'title': 'Articles', 29 | 'id': 'articles' 30 | }, 31 | { 32 | 'title': 'Theses', 33 | 'id': 'theses' 34 | } 35 | ] 36 | }, 37 | { 38 | 'title': 'Contact', 39 | 'id': 'contact', 40 | 'submenu': [ 41 | { 42 | 'title': 'Social media', 43 | 'id': 'socialmedia' 44 | }, 45 | { 46 | 'title': 'Email', 47 | 'id': 'email' 48 | }, 49 | { 50 | 'title': 'Address', 51 | 'id': 'address' 52 | } 53 | ] 54 | } 55 | ] 56 | }, 57 | idle, 58 | idletime = 45000; 59 | const chat = document.querySelector('.chat'); 60 | const content = document.querySelector('.content'); 61 | 62 | const newMessage = (message, type = 'user') => { 63 | let bubble = document.createElement('li'), 64 | slideIn = (el, i) => { 65 | setTimeout(() => { 66 | el.classList.add('show'); 67 | }, i * 150 ? i * 150 : 10); 68 | }, 69 | scroll, 70 | scrollDown = () => { 71 | chat.scrollTop += Math.floor(bubble.offsetHeight / 18); 72 | }; 73 | bubble.classList.add('message'); 74 | bubble.classList.add(type); 75 | bubble.innerHTML = type === 'user' ? `` : `

${message}

`; 76 | chat.appendChild(bubble); 77 | 78 | scroll = window.setInterval(scrollDown, 16); 79 | setTimeout(() => { 80 | window.clearInterval(scroll); 81 | chat.scrollTop = chat.scrollHeight; 82 | }, 300); 83 | 84 | setTimeout(() => { 85 | bubble.classList.add('show'); 86 | }, 10); 87 | 88 | if (type === 'user') { 89 | let animate = chat.querySelectorAll('button:not(:disabled)'); 90 | for (let i = 0; i < animate.length; i += 1) { 91 | slideIn(animate[i], i); 92 | } 93 | bubble.classList.add('active'); 94 | } 95 | }; 96 | 97 | const randomReply = replies => replies[Math.floor(Math.random() * replies.length)]; 98 | 99 | const checkUp = () => { 100 | let lastMessage = document.querySelector('.active'), 101 | idleReplies = [ 102 | 'Did you fall asleep? 😴', 103 | 'Coffee break? ☕', 104 | 'Still there?', 105 | '❄ 🌱 🌞 🍂' 106 | ]; 107 | if (lastMessage) { 108 | lastMessage.parentNode.removeChild(lastMessage); 109 | } 110 | newMessage(randomReply(idleReplies), 'bot'); 111 | setTimeout(() => { 112 | let helpReplies = [ 113 | 'Don\'t like this conversation? Send an email to mike@redvolume.com if you want a real one. 🚀', 114 | 'Not finding what you\'re looking for? Send an email to mike@redvolume.com with any questions...', 115 | 'Wanna talk to a real person? 💬 Fire off an email to mike@redvolume.com. 🔥' 116 | ]; 117 | newMessage(randomReply(helpReplies), 'bot'); 118 | setTimeout(() => { 119 | let knowMoreReplies = [ 120 | 'Where\'s the normal web page? 😞', 121 | 'I want a regular web page 😱', 122 | 'Do you have a regular website? 😻' 123 | ], 124 | menuAgainReplies = [ 125 | 'Show me the options again please ✅', 126 | 'Ok, go! 🚗', 127 | 'I wanna check something 👍' 128 | ]; 129 | newMessage(`
`); 130 | }, 300); 131 | }, 500); 132 | }; 133 | 134 | const init = () => { 135 | let welcomeReplies = [ 136 | 'Hello! 👋 I\'m Mike, a medical librarian working at Karolinska Institutet. I try to create the best user experiences possible, both physical and virtual, using humour, friendliness, web technologies and good design.' 137 | ]; 138 | idle = window.setInterval(() => { 139 | window.clearInterval(idle); 140 | checkUp(); 141 | }, idletime); 142 | newMessage(randomReply(welcomeReplies), 'bot'); 143 | setTimeout(() => { 144 | let startReplies = [ 145 | 'Hey, tell me more! 🚀', 146 | 'Hi! 👀', 147 | 'I\'ll bite 😄' 148 | ]; 149 | newMessage(``); 150 | }, 300); 151 | }; 152 | 153 | const makeUserBubble = el => { 154 | el.parentNode.parentNode.classList.add('selected'); 155 | el.parentNode.parentNode.classList.remove('active'); 156 | el.parentNode.parentNode.innerHTML = `

${el.textContent}

`; 157 | }; 158 | 159 | const showMenu = again => { 160 | let menu = '', 161 | goBack = chat.querySelector('button.newmenu'), 162 | againReplies = [ 163 | 'Here\'s a few things that I can tell you about... 🎤', 164 | 'Ok, check this out! 👇', 165 | 'Anything else you\'re interested in? 🙏' 166 | ], 167 | replies = [ 168 | 'What would you like to know more about? 💡', 169 | 'Can I interest you in any of this? 💯', 170 | 'Select something of the following... 👇' 171 | ]; 172 | if (goBack) { 173 | makeUserBubble(goBack); 174 | } 175 | setTimeout(() => { 176 | again ? newMessage(randomReply(againReplies), 'bot') : newMessage(randomReply(replies), 'bot'); 177 | structure.menu.forEach((val, index) => { 178 | menu += ``; 179 | }); 180 | setTimeout(() => { 181 | newMessage(menu); 182 | }, 300); 183 | }, 500); 184 | idle = window.setInterval(() => { 185 | window.clearInterval(idle); 186 | checkUp(); 187 | }, idletime); 188 | }; 189 | 190 | const menuClick = clicked => { 191 | let submenu = '', 192 | menuChoice = structure.menu[clicked.getAttribute('data-submenu')], 193 | replies = [ 194 | '👍 Here\'s what I have on that...', 195 | 'See anything interesting? 🙈', 196 | 'Any of this cool?' 197 | ], 198 | userReplies = [ 199 | `I wanna read about something other than ${menuChoice.title.toLowerCase()} 😜`, 200 | 'Show me the menu again 😋', 201 | `${menuChoice.title} was interesting, but show me something else... 😒` 202 | ]; 203 | menuChoice.submenu.forEach(val => { 204 | let id = `${menuChoice.id}-${val.id}`; 205 | submenu += ``; 206 | }); 207 | submenu += `
`; 208 | setTimeout(() => { 209 | newMessage(randomReply(replies), 'bot'); 210 | setTimeout(() => { 211 | newMessage(submenu); 212 | }, 300); 213 | }, 500); 214 | }; 215 | 216 | const toggleContent = article => { 217 | let buttons = chat.querySelectorAll('button'); 218 | if (article) { 219 | article.classList.add('show'); 220 | chat.setAttribute('aria-hidden', 'true'); 221 | content.setAttribute('aria-hidden', 'false'); 222 | content.tabIndex = '0'; 223 | content.focus(); 224 | } else { 225 | content.setAttribute('aria-hidden', 'true'); 226 | content.tabIndex = '-1'; 227 | chat.setAttribute('aria-hidden', 'false'); 228 | if (history.state && history.state.id === 'content') { 229 | history.back(); 230 | } 231 | setTimeout(() => { 232 | let active = document.querySelector('.content article.show'); 233 | if (active) { 234 | active.classList.remove('show'); 235 | chat.querySelector(`button[data-content="${active.id}"]`).focus(); 236 | } 237 | }, 300); 238 | } 239 | for (let i = 0; i < buttons.length; i += 1) { 240 | buttons[i].tabIndex = article ? '-1' : '0'; 241 | } 242 | }; 243 | 244 | const subMenuClick = clicked => { 245 | if (clicked.classList.contains('newmenu')) { 246 | showMenu(true); 247 | } else { 248 | toggleContent(document.getElementById(clicked.getAttribute('data-content'))); 249 | history.pushState({'id': clicked.getAttribute('data-content')}, '', `#${clicked.getAttribute('data-content')}`); 250 | } 251 | }; 252 | 253 | document.addEventListener('click', e => { 254 | if (e.target.classList.contains('choice')) { 255 | window.clearInterval(idle); 256 | if (!e.target.classList.contains('submenu')) { 257 | makeUserBubble(e.target); 258 | } 259 | 260 | if (e.target.classList.contains('menu')) { 261 | menuClick(e.target); 262 | } 263 | 264 | if (e.target.classList.contains('submenu')) { 265 | subMenuClick(e.target); 266 | } 267 | 268 | if (e.target.classList.contains('start')) { 269 | showMenu(); 270 | } 271 | 272 | if (e.target.classList.contains('showmenu')) { 273 | showMenu(true); 274 | } 275 | 276 | if (e.target.classList.contains('showinfo')) { 277 | let infoReplies = [ 278 | 'Here\'s my website ', 279 | 'Here you go: ', 280 | 'Check this one out ' 281 | ], 282 | okReplies = [ 283 | 'OK 😎', 284 | 'How do I get back? 🌒', 285 | 'Ok, thanks! 👌' 286 | ]; 287 | setTimeout(() => { 288 | newMessage(`${randomReply(infoReplies)} https://librarian.codes`, 'bot'); 289 | setTimeout(() => { 290 | newMessage(``); 291 | }, 300); 292 | }, 500); 293 | } 294 | } 295 | if (e.target.classList.contains('close')) { 296 | e.preventDefault(); 297 | history.back(); 298 | } 299 | }); 300 | 301 | document.addEventListener('keydown', e => { 302 | if (e.keyCode === 27 && content.getAttribute('aria-hidden') === 'false') { 303 | history.back(); 304 | } 305 | }); 306 | 307 | window.addEventListener('popstate', e => { 308 | toggleContent(e.state && e.state.id && document.getElementById(e.state.id) ? document.getElementById(e.state.id) : ''); 309 | }); 310 | 311 | setTimeout(() => { 312 | init(); 313 | }, 500); 314 | 315 | if (document.getElementById(window.location.hash.split('#')[1])) { 316 | let contentId = window.location.hash.split('#')[1]; 317 | history.replaceState('', '', '.'); 318 | setTimeout(() => { 319 | toggleContent(document.getElementById(contentId)); 320 | history.pushState({'id': contentId}, '', `#${contentId}`); 321 | }, 10); 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /js/app.min.js: -------------------------------------------------------------------------------- 1 | "use strict";!function(){var t={menu:[{title:"Projects",id:"projects",submenu:[{title:"Project 1",id:"p1"},{title:"Project 2",id:"p2"},{title:"Project 3",id:"p3"}]},{title:"Writing",id:"writing",submenu:[{title:"Articles",id:"articles"},{title:"Theses",id:"theses"}]},{title:"Contact",id:"contact",submenu:[{title:"Social media",id:"socialmedia"},{title:"Email",id:"email"},{title:"Address",id:"address"}]}]},e=void 0,n=45e3,o=document.querySelector(".chat"),i=document.querySelector(".content"),a=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"user",n=document.createElement("li"),i=function(t,e){setTimeout(function(){t.classList.add("show")},150*e?150*e:10)},a=void 0,s=function(){o.scrollTop+=Math.floor(n.offsetHeight/18)};if(n.classList.add("message"),n.classList.add(e),n.innerHTML="user"===e?"":"

"+t+"

",o.appendChild(n),a=window.setInterval(s,16),setTimeout(function(){window.clearInterval(a),o.scrollTop=o.scrollHeight},300),setTimeout(function(){n.classList.add("show")},10),"user"===e){for(var r=o.querySelectorAll("button:not(:disabled)"),c=0;cmike@redvolume.com if you want a real one. 🚀','Not finding what you\'re looking for? Send an email to mike@redvolume.com with any questions...','Wanna talk to a real person? 💬 Fire off an email to mike@redvolume.com. 🔥'];a(s(t),"bot"),setTimeout(function(){var t=["Where's the normal web page? 😞","I want a regular web page 😱","Do you have a regular website? 😻"],e=["Show me the options again please ✅","Ok, go! 🚗","I wanna check something 👍"];a('
")},300)},500)},c=function(){var t=["Hello! 👋 I'm Mike, a medical librarian working at Karolinska Institutet. I try to create the best user experiences possible, both physical and virtual, using humour, friendliness, web technologies and good design."];e=window.setInterval(function(){window.clearInterval(e),r()},n),a(s(t),"bot"),setTimeout(function(){var t=["Hey, tell me more! 🚀","Hi! 👀","I'll bite 😄"];a('")},300)},u=function(t){t.parentNode.parentNode.classList.add("selected"),t.parentNode.parentNode.classList.remove("active"),t.parentNode.parentNode.innerHTML="

"+t.textContent+"

"},l=function(i){var c="",l=o.querySelector("button.newmenu"),d=["Here's a few things that I can tell you about... 🎤","Ok, check this out! 👇","Anything else you're interested in? 🙏"],m=["What would you like to know more about? 💡","Can I interest you in any of this? 💯","Select something of the following... 👇"];l&&u(l),setTimeout(function(){i?a(s(d),"bot"):a(s(m),"bot"),t.menu.forEach(function(t,e){c+='"}),setTimeout(function(){a(c)},300)},500),e=window.setInterval(function(){window.clearInterval(e),r()},n)},d=function(e){var n="",o=t.menu[e.getAttribute("data-submenu")],i=["👍 Here's what I have on that...","See anything interesting? 🙈","Any of this cool?"],r=["I wanna read about something other than "+o.title.toLowerCase()+" 😜","Show me the menu again 😋",o.title+" was interesting, but show me something else... 😒"];o.submenu.forEach(function(t){var e=o.id+"-"+t.id;n+='"}),n+='
",setTimeout(function(){a(s(i),"bot"),setTimeout(function(){a(n)},300)},500)},m=function(t){var e=o.querySelectorAll("button");t?(t.classList.add("show"),o.setAttribute("aria-hidden","true"),i.setAttribute("aria-hidden","false"),i.tabIndex="0",i.focus()):(i.setAttribute("aria-hidden","true"),i.tabIndex="-1",o.setAttribute("aria-hidden","false"),history.state&&"content"===history.state.id&&history.back(),setTimeout(function(){var t=document.querySelector(".content article.show");t&&(t.classList.remove("show"),o.querySelector('button[data-content="'+t.id+'"]').focus())},300));for(var n=0;nhttps://librarian.codes',"bot"),setTimeout(function(){a('")},300)},500)}()),t.target.classList.contains("close")&&(t.preventDefault(),history.back())}),document.addEventListener("keydown",function(t){27===t.keyCode&&"false"===i.getAttribute("aria-hidden")&&history.back()}),window.addEventListener("popstate",function(t){m(t.state&&t.state.id&&document.getElementById(t.state.id)?document.getElementById(t.state.id):"")}),setTimeout(function(){c()},500),document.getElementById(window.location.hash.split("#")[1])&&!function(){var t=window.location.hash.split("#")[1];history.replaceState("","","."),setTimeout(function(){m(document.getElementById(t)),history.pushState({id:t},"","#"+t)},10)}()}(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "conversational-ui", 3 | "version": "1.0.0", 4 | "description": "Conversational UI example", 5 | "main": "gulpfile.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/dermike/conversational-ui.git" 12 | }, 13 | "keywords": [ 14 | "ui" 15 | ], 16 | "author": "Mikael Jergefelt", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/dermike/conversational-ui/issues" 20 | }, 21 | "homepage": "https://github.com/dermike/conversational-ui#readme", 22 | "devDependencies": { 23 | "babel-preset-es2015": "^6.9.0", 24 | "gulp": "^3.9.1", 25 | "gulp-babel": "^6.1.2", 26 | "gulp-rename": "^1.2.2", 27 | "gulp-uglify": "^1.5.3" 28 | } 29 | } 30 | --------------------------------------------------------------------------------