Skip to content

Commit

Permalink
first
Browse files Browse the repository at this point in the history
  • Loading branch information
moinism committed Jun 29, 2017
1 parent 6b8c625 commit a22d4d4
Show file tree
Hide file tree
Showing 13 changed files with 772 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test
node_modules
.DS_Store
5 changes: 5 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
src
test
Gulpfile.js
node_modules
.DS_Store
60 changes: 60 additions & 0 deletions Gulpfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

var fs = require('fs'),
gulp = require('gulp'),
sass = require('gulp-sass'),
banner = require('gulp-banner'),
rename = require('gulp-rename'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
htmlclean = require('htmlclean'),
replace = require('gulp-replace'),
minify = require('gulp-clean-css');

function escape (text) {
return text.replace(/'/g, "\\'");
}

function htmlTemplate() {
return replace('BOTUI_TEMPLATE', escape(
htmlclean(fs.readFileSync('./src/botui.html', 'utf8'))
));
}

gulp.task('styles', function() {
gulp.src(['./src/styles/normal.scss',
'./src/styles/botui.scss'])
.pipe(sass().on('error', sass.logError))
.pipe(minify())
.pipe(concat('botui.min.css'))
.pipe(gulp.dest('./build/'));
});

gulp.task('themes', function() {
gulp.src('./src/styles/themes/*.scss')
.pipe(sass().on('error', sass.logError))
.pipe(minify())
.pipe(rename(function (path) {
path.basename = 'botui-theme-' + path.basename;
}))
.pipe(gulp.dest('./build/'));
});

gulp.task('scripts', function () {
gulp.src('./src/scripts/botui.js') // simply copy the original one
.pipe(htmlTemplate())
.pipe(gulp.dest('./build/'));

gulp.src('./src/scripts/botui.js') // minified version
.pipe(uglify())
.pipe(htmlTemplate())
.pipe(rename('botui.min.js'))
.pipe(gulp.dest('./build/'));
});

gulp.task('watch',function() {
gulp.watch('./src/styles/*.scss', ['styles']);
gulp.watch('./src/styles/themes/*.scss', ['themes']);
gulp.watch(['./src/scripts/botui.js', './src/botui.html'], ['scripts']);
});

gulp.task('default', ['styles', 'scripts', 'themes']);
1 change: 1 addition & 0 deletions build/botui-theme-default.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

217 changes: 217 additions & 0 deletions build/botui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
window.BotUI = (function (id, 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(!window.Vue) {
throw Error('BotUI: VueJS is required but not found on this page.');
}

var _botApp, _options = {
fontawesome: true
},
_interface = {},
_actionResolve,
_regex = {
link: '/<((?:[a-z][a-z]+))\\|((?:http|https)(?::\\/{2}[\\w]+)(?:[\\/|\\.]?)(?:[^\\s"]*))>/gi'
},
_fontAwesome = 'https://use.fontawesome.com/ea731dcb6f.js';

// merge opts passed to constructor with _options
if(opts) {
for (var prop in _options) {
if (opts.hasOwnProperty(prop)) {
_options[prop] = opts[prop];
}
}
}

// function replaceURL(val) {
// var exp = /((<)((?:[a-z][a-z]+))(\\|)(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|](>))/ig;
// return val.replace(exp, "<a href='$2'>$1</a>");
// }

function _handleAction(text) {
if(_instance.action.addMessage) {
_interface.message.human({
delay: 100,
content: text
});
}
_instance.action.show = !_instance.action.autoHide;
}

var _botuiComponent = {
template: '<div class="botui botui-container"><div class="botui-messages-container"><div v-for="msg in messages"><transition name="slide-fade"><div class="botui-message" v-if="msg.visible"><div class="botui-message-content" :class="{human: msg.human}" v-text="msg.content"></div></div></transition></div></div><div class="botui-actions-container"><transition name="slide-fade"><div v-if="action.show"><form v-if="action.type == \'text\'" class="botui-actions-text" @submit.prevent="handle_action_text()"> <input type="text" ref="input" :type="action.text.sub_type" v-model="action.text.value" class="botui-actions-text-input" :placeholder="action.text.placeholder" :size="action.text.size" :value="action.text.value" required/> <button v-if="isMobile" class="botui-actions-text-submit">Go</button></form><div v-if="action.type == \'button\'" class="botui-actions-buttons"> <button type="button" class="botui-actions-buttons-button" v-for="button in action.button.buttons" @click="handle_action_button(button)" autofocus><i v-if="button.icon" class="fa" :class="\'fa-\' + button.icon"></i> {{button.text}}</button></div></div></transition></div></div>', // 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 window.innerWidth <= 768;
}
},
methods: {
handle_action_button: function (button) {
_handleAction(button.text);
_actionResolve({
type: 'button',
text: button.text,
value: button.value
});
},
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 = '';
}
}
};

_botApp = new Vue({
components: {
'bot-ui': _botuiComponent
}
}).$mount('#' + id);

var _instance = _botApp.$children[0]; // to access the component's data

var _addMessage = function (_msg) {

if(!_msg.content) {
throw Error('BotUI: "content" is required in message object.');
}

_msg.visible = _msg.delay ? false : true;
var _index = _instance.messages.push(_msg) - 1;

return new Promise(function (resolve, reject) {
setTimeout(function () {
_instance.messages[_index].visible = true;
resolve(_index);
}, _msg.delay || 0);
});
};

_interface.message = {
add: function (addOpts) {
return _addMessage(addOpts);
},
bot: function (addOpts) {
addOpts = addOpts || {};
return _addMessage(addOpts);
},
human: function (addOpts) {
addOpts = addOpts || {};
addOpts.human = true;
return _addMessage(addOpts);
},
get: function (index) {
return _instance.messages[index];
},
remove: function (index) {
return !!_instance.messages.splice(index, 1).length; // return a boolean
},
update: function (index, msg) { // only content can be updated, not the message type.
_instance.messages[index].content = msg.content;
return Promise.resolve();
},
removeAll: function () {
return !!_instance.messages.splice(0, _instance.messages.length).length;
}
};

function mergeAtoB(objA, objB) {
for (var prop in objA) {
if (!objB.hasOwnProperty(prop)) {
objB[prop] = objA[prop];
}
}
}

var _showActions = function (_opts) {

mergeAtoB({
type: 'text',
autoHide: true,
addMessage: true
}, _opts);

_instance.action.type = _opts.type;
_instance.action.autoHide = _opts.autoHide;
_instance.action.addMessage = _opts.addMessage;

return new Promise(function(resolve, reject) {
_actionResolve = resolve;
setTimeout(function () {
_instance.action.show = true;
if(_opts.type == 'text') {
Vue.nextTick(function () {
_instance.$refs.input.focus();
});
}
}, _opts.delay || 0);
});
};

_interface.action = {
show: _showActions,
hide: function () {
_instance.action.show = false;
},
text: function (_opts) {
_instance.action.text = _opts;
return _showActions(_opts);
},
button: function (_opts) {
_opts.type = 'button';

if(!_opts.buttons) {
throw Error('BotUI: "buttons" property is expected as an array.');
}

_instance.action.button.buttons = _opts.buttons;
return _showActions(_opts);
}
};

function loadFontAwesome() {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = _fontAwesome;

document.body.appendChild(script);
}

if(_options.fontawesome) {
loadFontAwesome();
}

if(_options.debug) {
_interface._botApp = _botApp; // current Vue instance
}

return _interface;
});
2 changes: 2 additions & 0 deletions build/botui.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions build/botui.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "botui",
"version": "0.1.0",
"description": "A JS library to build the UI for your bot",
"main": "build/botui.min.js",
"devDependencies": {
"gulp": "^3.9.1",
"gulp-banner": "^0.1.3",
"gulp-clean-css": "^3.5.0",
"gulp-concat": "^2.6.0",
"gulp-rename": "^1.2.2",
"gulp-replace": "^0.6.1",
"gulp-sass": "^2.3.2",
"gulp-uglify": "^2.0.0",
"htmlclean": "^3.0.2"
},
"dependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Moin Uddin <me@moin.im> (https://moin.im/)",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/moinism/botui.git"
},
"keywords": [
"bot",
"ui",
"js",
"chat",
"interface"
],
"bugs": {
"url": "https://github.com/moinism/botui/issues"
},
"homepage": "https://botui.moin.im"
}
33 changes: 33 additions & 0 deletions src/botui.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<div class="botui botui-container">
<div class="botui-messages-container">
<div v-for="msg in messages">
<transition name="slide-fade">
<div class="botui-message" v-if="msg.visible">
<div class="botui-message-content" :class="{human: msg.human}"
v-text="msg.content">
</div>
</div>
</transition>
</div>
</div>
<div class="botui-actions-container">
<transition name="slide-fade">
<div v-if="action.show">
<form v-if="action.type == 'text'" class="botui-actions-text"
@submit.prevent="handle_action_text()">
<input type="text" ref="input" :type="action.text.sub_type"
v-model="action.text.value" class="botui-actions-text-input" :placeholder="action.text.placeholder"
:size="action.text.size" :value="action.text.value" required/>
<button v-if="isMobile" class="botui-actions-text-submit">Go</button>
</form>
<div v-if="action.type == 'button'" class="botui-actions-buttons">
<button type="button" class="botui-actions-buttons-button" v-for="button in action.button.buttons"
@click="handle_action_button(button)"
autofocus>
<i v-if="button.icon" class="fa" :class="'fa-' + button.icon"></i> {{button.text}}
</button>
</div>
</div>
</transition>
</div>
</div>

0 comments on commit a22d4d4

Please sign in to comment.