-
Notifications
You must be signed in to change notification settings - Fork 366
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
772 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
test | ||
node_modules | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
src | ||
test | ||
Gulpfile.js | ||
node_modules | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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']); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
Oops, something went wrong.