diff --git a/js/ui/botui/build/botui.js b/js/ui/botui/build/botui.js new file mode 100644 index 0000000..77afb46 --- /dev/null +++ b/js/ui/botui/build/botui.js @@ -0,0 +1,435 @@ +/* + * botui 0.3.9M1 + * A JS library to build the UI for your bot + * https://botui.org + * + * Copyright 2019, Moin Uddin + * Released under the MIT license. + */ + +(function(root, factory) { + "use strict"; + if (typeof define === 'function' && define.amd) { + define([], function() { + return (root.BotUI = factory(root)); + }); + } else { + root.BotUI = factory(root); + } +}(typeof window !== 'undefined' ? window : this, function(root, undefined) { + "use strict"; + + var BotUI = (function(id, opts) { + + opts = opts || {}; + + if (!id) { + throw Error('BotUI: Container id is required as first argument.'); + } + + if (!document.getElementById(id)) { + throw Error('BotUI: Element with id #' + id + ' does not exist.'); + } + + if (!root.Vue && !opts.vue) { + throw Error('BotUI: Vue is required but not found.'); + } + + var _botApp, // current vue instance. + _options = { + debug: false, + fontawesome: true, + searchselect: true + }, + _container, // the outermost Element. Needed to scroll to bottom, for now. + _interface = {}, // methods returned by a BotUI() instance. + _actionResolve, + _markDownRegex = { + icon: /!\(([^\)]+)\)/igm, // !(icon) + image: /!\[(.*?)\]\((.*?)\)/igm, // ![aleternate text](src) + link: /\[([^\[]+)\]\(([^\)]+)\)(\^?)/igm // [text](link) ^ can be added at end to set the target as 'blank' + }, + _fontAwesome = 'https://use.fontawesome.com/ea731dcb6f.js', + _esPromisePollyfill = 'https://cdn.jsdelivr.net/es6-promise/4.1.0/es6-promise.min.js', // mostly for IE + _searchselect = "https://unpkg.com/vue-select@2.4.0/dist/vue-select.js"; + + root.Vue = root.Vue || opts.vue; + + // merge opts passed to constructor with _options + for (var prop in _options) { + if (opts.hasOwnProperty(prop)) { + _options[prop] = opts[prop]; + } + } + + if (!root.Promise && typeof Promise === "undefined" && !opts.promise) { + loadScript(_esPromisePollyfill); + } + + function _linkReplacer(match, $1, $2, $3) { + var _target = $3 ? 'blank' : ''; // check if '^' sign is present with link syntax + return "" + $1 + ""; + } + + function _parseMarkDown(text) { + return text + .replace(_markDownRegex.image, "$1") + .replace(_markDownRegex.icon, "") + .replace(_markDownRegex.link, _linkReplacer); + } + + function loadScript(src, cb) { + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = src; + + if (cb) { + script.onload = cb; + } + + document.body.appendChild(script); + } + + function _handleAction(text) { + if (_instance.action.addMessage) { + _interface.message.human({ + delay: 100, + content: text + }); + } + _instance.action.show = !_instance.action.autoHide; + } + + var _botuiComponent = { + template: '
', // replaced by HTML template during build. see Gulpfile.js + data: function() { + return { + action: { + text: { + size: 30, + placeholder: 'Write here ..' + }, + button: {}, + show: false, + type: 'text', + autoHide: true, + addMessage: true + }, + messages: [] + }; + }, + computed: { + isMobile: function() { + return root.innerWidth && root.innerWidth <= 768; + } + }, + methods: { + handle_action_button: function(button) { + for (var i = 0; i < this.action.button.buttons.length; i++) { + if (this.action.button.buttons[i].value == button.value && typeof(this.action.button.buttons[i].event) == 'function') { + this.action.button.buttons[i].event(button); + if (this.action.button.buttons[i].actionStop) return false; + break; + } + } + + _handleAction(button.text); + + var defaultActionObj = { + type: 'button', + text: button.text, + value: button.value + }; + + for (var eachProperty in button) { + if (button.hasOwnProperty(eachProperty)) { + if (eachProperty !== 'type' && eachProperty !== 'text' && eachProperty !== 'value') { + defaultActionObj[eachProperty] = button[eachProperty]; + } + } + } + + _actionResolve(defaultActionObj); + }, + handle_action_text: function() { + if (!this.action.text.value) return; + _handleAction(this.action.text.value); + _actionResolve({ + type: 'text', + value: this.action.text.value + }); + this.action.text.value = ''; + }, + handle_action_select: function() { + if (this.action.select.searchselect && !this.action.select.multipleselect) { + if (!this.action.select.value.value) return; + _handleAction(this.action.select.value[this.action.select.label]); + _actionResolve({ + type: 'text', + value: this.action.select.value.value, + text: this.action.select.value.text, + obj: this.action.select.value + }); + } + if (this.action.select.searchselect && this.action.select.multipleselect) { + if (!this.action.select.value) return; + var values = new Array(); + var labels = new Array(); + for (var i = 0; i < this.action.select.value.length; i++) { + values.push(this.action.select.value[i].value); + labels.push(this.action.select.value[i][this.action.select.label]); + } + _handleAction(labels.join(', ')); + _actionResolve({ + type: 'text', + value: values.join(', '), + text: labels.join(', '), + obj: this.action.select.value + }); + } else { + if (!this.action.select.value) return; + for (var i = 0; i < this.action.select.options.length; i++) { // Find select title + if (this.action.select.options[i].value == this.action.select.value) { + _handleAction(this.action.select.options[i].text); + _actionResolve({ + type: 'text', + value: this.action.select.value, + text: this.action.select.options[i].text + }); + } + } + } + } + } + }; + + root.Vue.directive('botui-markdown', function(el, binding) { + if (binding.value == 'false') return; // v-botui-markdown="false" + el.innerHTML = _parseMarkDown(el.textContent); + }); + + root.Vue.directive('botui-scroll', { + inserted: function(el) { + _container.scrollTop = _container.scrollHeight; + el.scrollIntoView(true); + } + }); + + root.Vue.directive('focus', { + inserted: function(el) { + el.focus(); + } + }); + + root.Vue.directive('botui-container', { + inserted: function(el) { + _container = el; + } + }); + + _botApp = new root.Vue({ + components: { + 'bot-ui': _botuiComponent + } + }).$mount('#' + id); + + var _instance = _botApp.$children[0]; // to access the component's data + + function _addMessage(_msg) { + + if (!_msg.loading && !_msg.content) { + throw Error('BotUI: "content" is required in a non-loading message object.'); + } + + if (opts.callback) opts.callback(_msg); + + _msg.type = _msg.type || 'text'; + _msg.visible = (_msg.delay || _msg.loading) ? false : true; + var _index = _instance.messages.push(_msg) - 1; + + return new Promise(function(resolve, reject) { + setTimeout(function() { + if (_msg.delay) { + _msg.visible = true; + + if (_msg.loading) { + _msg.loading = false; + } + } + resolve(_index); + }, _msg.delay || 0); + }); + } + + function _checkOpts(_opts) { + if (typeof _opts === 'string') { + _opts = { + content: _opts + }; + } + return _opts || {}; + } + + _interface.clear = function() { + var _out = document.getElementById(id); + var CHILDREN_TO_PRESERVE = 0; + while (_out.childNodes[CHILDREN_TO_PRESERVE]) + _out.removeChild(_out.childNodes[CHILDREN_TO_PRESERVE]); + _out.appendChild(document.createElement("bot-ui")); + }, + _interface.removeAction = function() { + _instance.action.show = false; + }, + _interface.message = { + add: function(addOpts) { + return _addMessage(_checkOpts(addOpts)); + }, + bot: function(addOpts) { + addOpts = _checkOpts(addOpts); + return _addMessage(addOpts); + }, + human: function(addOpts) { + addOpts = _checkOpts(addOpts); + addOpts.human = true; + return _addMessage(addOpts); + }, + get: function(index) { + return Promise.resolve(_instance.messages[index]); + }, + remove: function(index) { + _instance.messages.splice(index, 1); + return Promise.resolve(); + }, + update: function(index, msg) { // only content can be updated, not the message type. + var _msg = _instance.messages[index]; + _msg.content = msg.content; + _msg.visible = !msg.loading; + _msg.loading = !!msg.loading; + return Promise.resolve(msg.content); + }, + removeAll: function() { + _instance.messages.splice(0, _instance.messages.length); + return Promise.resolve(); + } + }; + + function mergeAtoB(objA, objB) { + for (var prop in objA) { + if (!objB.hasOwnProperty(prop)) { + objB[prop] = objA[prop]; + } + } + } + + function _checkAction(_opts) { + if (!_opts.action && !_opts.actionButton && !_opts.actionText) { + throw Error('BotUI: "action" property is required.'); + } + } + + function _showActions(_opts) { + + _checkAction(_opts); + + mergeAtoB({ + type: 'text', + cssClass: '', + autoHide: true, + addMessage: true + }, _opts); + + _instance.action.type = _opts.type; + _instance.action.cssClass = _opts.cssClass; + _instance.action.autoHide = _opts.autoHide; + _instance.action.addMessage = _opts.addMessage; + + return new Promise(function(resolve, reject) { + _actionResolve = resolve; // resolved when action is performed, i.e: button clicked, text submitted, etc. + setTimeout(function() { + _instance.action.show = true; + }, _opts.delay || 0); + }); + }; + + _interface.action = { + show: _showActions, + hide: function() { + _instance.action.show = false; + return Promise.resolve(); + }, + text: function(_opts) { + _checkAction(_opts); + _instance.action.text = _opts.action; + return _showActions(_opts); + }, + button: function(_opts) { + _checkAction(_opts); + _opts.type = 'button'; + _instance.action.button.buttons = _opts.action; + return _showActions(_opts); + }, + select: function(_opts) { + _checkAction(_opts); + _opts.type = 'select'; + _opts.action.label = _opts.action.label || 'text'; + _opts.action.value = _opts.action.value || ''; + _opts.action.searchselect = typeof _opts.action.searchselect !== 'undefined' ? _opts.action.searchselect : _options.searchselect; + _opts.action.multipleselect = _opts.action.multipleselect || false; + if (_opts.action.searchselect && typeof(_opts.action.value) == 'string') { + if (!_opts.action.multipleselect) { + for (var i = 0; i < _opts.action.options.length; i++) { // Find object + if (_opts.action.options[i].value == _opts.action.value) { + _opts.action.value = _opts.action.options[i] + } + } + } else { + var vals = _opts.action.value.split(','); + _opts.action.value = new Array(); + for (var i = 0; i < _opts.action.options.length; i++) { // Find object + for (var j = 0; j < vals.length; j++) { // Search values + if (_opts.action.options[i].value == vals[j]) { + _opts.action.value.push(_opts.action.options[i]); + } + } + } + } + } + if (!_opts.action.searchselect) { + _opts.action.options.unshift({ + value: '', + text: _opts.action.placeholder + }); + } + _instance.action.button = _opts.action.button; + _instance.action.select = _opts.action; + return _showActions(_opts); + }, + buttontext: function(_opts) { + _checkAction(_opts); + _opts.type = 'buttontext'; + _instance.action.button.buttons = _opts.actionButton; + _instance.action.text = _opts.actionText; + return _showActions(_opts); + } + }; + + if (_options.fontawesome) { + loadScript(_fontAwesome); + } + + if (_options.searchselect) { + loadScript(_searchselect, function() { + Vue.component('v-select', VueSelect.VueSelect); + }); + } + + if (_options.debug) { + _interface._botApp = _botApp; // current Vue instance + } + + return _interface; + }); + + return BotUI; + +}));