├── src ├── assets │ ├── .gitkeep │ └── css │ │ └── chat.css ├── chat.html ├── chat │ ├── messages │ │ ├── typing-indicator.tsx │ │ ├── messagetype.tsx │ │ ├── action.tsx │ │ ├── buttons.tsx │ │ ├── text.tsx │ │ └── list.tsx │ ├── chat-action.tsx │ ├── index.tsx │ ├── botman.ts │ ├── message-area.tsx │ ├── message-holder.tsx │ └── chat.tsx ├── widget │ ├── chat-frame.tsx │ ├── configuration.ts │ ├── arrow-icon.tsx │ ├── chat-floating-button.tsx │ ├── api.ts │ ├── index.tsx │ ├── chat-title-msg.tsx │ ├── style.ts │ └── widget.tsx ├── typings │ ├── index.ts │ └── laravel-echo │ │ └── index.d.ts └── demo.html ├── .npmignore ├── .gitignore ├── .travis.yml ├── .babelrc ├── .editorconfig ├── test └── setup.js ├── tsconfig.json ├── LICENSE.md ├── README.md ├── .eslintrc ├── package.json └── webpack.config.babel.js /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | .DS_Store 4 | /coverage 5 | /.idea 6 | yarn.lock 7 | /.vscode 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | /build 4 | .DS_Store 5 | /coverage 6 | /.idea 7 | yarn.lock 8 | /.vscode 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | yarn: true 4 | directories: 5 | - node_modules 6 | node_js: 7 | - stable 8 | script: 9 | - yarn test 10 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceMaps": true, 3 | "presets": [ 4 | ["es2015", { "loose":true }], 5 | "stage-0" 6 | ], 7 | "plugins": [ 8 | ["transform-decorators-legacy"], 9 | ["transform-react-jsx", { "pragma": "h" }] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BotMan Widget 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [{package.json,.*rc,*.yml}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime'; 2 | import chai from 'chai'; 3 | import assertJsx, { options } from 'preact-jsx-chai'; 4 | 5 | // when checking VDOM assertions, don't compare functions, just nodes and attributes: 6 | options.functions = false; 7 | 8 | // activate the JSX assertion extension: 9 | chai.use(assertJsx); 10 | 11 | global.sleep = ms => new Promise( resolve => setTimeout(resolve, ms) ); 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "noEmit": false, 5 | "noImplicitAny": true, 6 | "strictNullChecks": false, 7 | "module": "es6", 8 | "jsx": "react", 9 | "jsxFactory": "h", 10 | "target": "es5", 11 | "allowJs": true, 12 | "moduleResolution": "node", 13 | "lib": ["dom", "es5", "scripthost", "es2015.promise"] 14 | }, 15 | "include": [ 16 | "./src/**/*" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/chat/messages/typing-indicator.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import {botman} from './../botman'; 3 | import { IButton, IMessage, IMessageTypeProps } from '../../typings'; 4 | import MessageType from "./messagetype"; 5 | 6 | export default class TypingIndicator extends MessageType { 7 | 8 | render(props: IMessageTypeProps) { 9 | return ( 10 |
11 | ); 12 | } 13 | 14 | onVisibilityChange = () => { 15 | setTimeout(() => { 16 | this.setState({ visible : false}); 17 | this.props.onVisibilityChange(this.props.message, this.state); 18 | }, this.props.message.timeout * 1000); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/chat/messages/messagetype.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import {IMessageTypeProps, IMessageTypeState} from '../../typings'; 3 | 4 | export default abstract class MessageType extends Component { 5 | 6 | constructor() { 7 | super(); 8 | this.state = { 9 | visible: false, 10 | visibilityChanged: false, 11 | attachmentsVisible: true 12 | }; 13 | } 14 | 15 | onVisibilityChange = () => {}; 16 | 17 | /** 18 | * Check if we have a timeout 19 | */ 20 | componentDidMount() { 21 | setTimeout(() => { 22 | this.setState({ visible : true }); 23 | this.setState({ visibilityChanged : true }); 24 | this.onVisibilityChange(); 25 | this.props.onVisibilityChange(this.props.message, this.state); 26 | }, this.props.timeout || 0); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/chat/chat-action.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import {botman} from './botman'; 3 | import { IAction, IMessage } from '../typings'; 4 | 5 | 6 | export default class ChatAction extends Component { 7 | 8 | render(props: IChatActionProps) { 9 | return ( 10 |
this.performAction(props.action)}> 11 | {props.action.text} 12 |
13 | ); 14 | } 15 | 16 | performAction(action: IAction) { 17 | botman.callAPI(action.value, true, null, (msg: IMessage) => { 18 | this.props.messageHandler({ 19 | text: msg.text, 20 | type: msg.type, 21 | actions: msg.actions, 22 | attachment: msg.attachment, 23 | additionalParameters: msg.additionalParameters, 24 | from: 'chatbot' 25 | }); 26 | }, null); 27 | } 28 | } 29 | 30 | interface IChatActionProps { 31 | messageHandler: Function, 32 | action: IAction, 33 | } -------------------------------------------------------------------------------- /src/widget/chat-frame.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { IConfiguration } from '../typings'; 3 | 4 | export default class ChatFrame extends Component { 5 | 6 | shouldComponentUpdate() { 7 | // do not re-render via diff: 8 | return false; 9 | } 10 | 11 | render({iFrameSrc, isMobile, conf}: IChatFrameProps,{}) { 12 | let dynamicConf = window.botmanWidget || {} as IConfiguration; // these configuration are loaded when the chat frame is opened 13 | let encodedConf = encodeURIComponent(JSON.stringify({...conf, ...dynamicConf})); 14 | return ( 15 |