├── .gitignore
├── README.md
├── composer.json
├── configs
├── cache.php
├── db.php
├── log.php
├── login.php
├── redis.php
├── upload.php
└── webim.php
├── resources
├── static
│ ├── css
│ │ ├── bootstrap.css
│ │ ├── bootstrap1.css
│ │ ├── chat.css
│ │ └── main.css
│ ├── img
│ │ ├── avatar1.jpg
│ │ ├── button.gif
│ │ ├── button.jpg
│ │ ├── button.png
│ │ ├── button1.jpg
│ │ ├── button2.jpg
│ │ ├── default.jpg
│ │ ├── f1.png
│ │ └── face
│ │ │ ├── 1.gif
│ │ │ ├── 10.gif
│ │ │ ├── 11.gif
│ │ │ ├── 12.gif
│ │ │ ├── 13.gif
│ │ │ ├── 14.gif
│ │ │ ├── 15.gif
│ │ │ ├── 16.gif
│ │ │ ├── 17.gif
│ │ │ ├── 18.gif
│ │ │ ├── 19.gif
│ │ │ ├── 2.gif
│ │ │ ├── 3.gif
│ │ │ ├── 4.gif
│ │ │ ├── 5.gif
│ │ │ ├── 6.gif
│ │ │ ├── 7.gif
│ │ │ ├── 8.gif
│ │ │ └── 9.gif
│ └── js
│ │ ├── bootstrap.js
│ │ ├── chat.js
│ │ ├── comet.js
│ │ ├── console.js
│ │ ├── jquery.js
│ │ └── jquery.json.js
└── templates
│ └── chatroom.php
├── server.php
└── src
├── MySQLPool.php
├── Pool.php
├── RedisPool.php
└── Server.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /.idea/
3 | /.project
4 | /.settings
5 | .buildpath
6 | *.lock
7 | /logs
8 | /resources/static/uploads/
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WebIM
2 | ========
3 |
4 | 使用`PHP`+`Swoole4`实现的网页即时聊天工具,在线体验地址:[http://webim.swoole.com/](http://webim.swoole.com/)
5 |
6 | * 基于`Swoole4`协程实现,可以同时支持数百万`TCP`连接在线
7 | * 基于`WebSocket`+`Http Comet`支持所有浏览器/客户端/移动端
8 | * 支持单聊/群聊/组聊等功能
9 | * 聊天记录使用`MySQL`存储
10 | * 用户列表和在线信息使用`Redis`存储
11 | * 基于`Server PUSH`的即时内容更新,登录/登出/状态变更/消息等会内容即时更新
12 | * 支持发送链接/图片/语音/视频/文件(开发中)
13 | * 支持`Web`端直接管理所有在线用户和群组(开发中)
14 |
15 | 依赖
16 | ----
17 | 需要`Swoole-4.4.7`或更高版本
18 | ```shell
19 | pecl install swoole
20 | ```
21 |
22 | 部署说明
23 | ----
24 |
25 | ### 安装依赖的 Composer 包
26 |
27 | ```shell
28 | composer install
29 | ```
30 |
31 | ### 修改配置
32 |
33 | * 配置`configs/redis.php`中的`Redis`服务器信息,用户列表和信息会存到`Redis`中
34 | * 配置`configs/db.php`中数据库信息,聊天记录会存储到`MySQL`中
35 | * 导入`MySQL`表接口到对应的数据库中
36 |
37 | 表结构
38 | ```sql
39 | CREATE TABLE `webim_history` (
40 | `id` int(11) NOT NULL AUTO_INCREMENT,
41 | `addtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
42 | `name` varchar(64) COLLATE utf8mb4_bin NOT NULL,
43 | `avatar` varchar(255) COLLATE utf8mb4_bin NOT NULL,
44 | `type` varchar(12) COLLATE utf8mb4_bin NOT NULL,
45 | `msg` text COLLATE utf8mb4_bin NOT NULL,
46 | `send_ip` varchar(20) COLLATE utf8mb4_bin NOT NULL,
47 | PRIMARY KEY (`id`)
48 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
49 | ```
50 |
51 | * 修改`configs/webim.php`中的选项,设置服务器的URL和端口
52 | ```php
53 | $config['server'] = array(
54 | //监听的HOST
55 | 'host' => '0.0.0.0',
56 | //监听的端口
57 | 'port' => '9503',
58 | //配置域名 [可选]
59 | 'name' => 'im.swoole.com',
60 | );
61 | ```
62 |
63 | * `server.host`,`server.port` 项为`WebIM`服务器即`WebSocket`服务器的地址与端口
64 | * `server.name`配置使用的域名(可选),如果未设置将直接使用`IP:PORT`进行访问
65 | * 监听`80`和`443`等`1024`以内端口需要`root`权限
66 |
67 |
68 | ### 启动服务器
69 |
70 | ```shell
71 | php server.php
72 | ```
73 |
74 | ### 配置域名解析或者本地 Host [可选]
75 |
76 | * 直接使用`IP:PORT`,这里不需要设置。直接打开 `http://IP:PORT/` 即可
77 | * 外网域名需要配置`DNS`解析
78 | * 本机域名需要修改`/etc/hosts`,增加`127.0.0.1 im.swoole.com`本机域名绑定
79 |
80 | 配置成功后,可以使用浏览器打开,如:`http://im.swoole.com:9503/`
81 |
82 | > 以上仅为示例,实际项目需要修改为对应的域名
83 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "matyhtf/framework": ">=3.0.0",
4 | "symfony/console": "^4.3",
5 | "swoole/ide-helper": "~4.4.7"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/configs/cache.php:
--------------------------------------------------------------------------------
1 | 'Redis',
4 | );
5 | return $cache;
--------------------------------------------------------------------------------
/configs/db.php:
--------------------------------------------------------------------------------
1 | SPF\Database::TYPE_MYSQLi,
4 | 'host' => "127.0.0.1",
5 | 'port' => 3306,
6 | 'dbms' => 'mysql',
7 | 'engine' => 'MyISAM',
8 | 'user' => "root",
9 | 'password' => "root",
10 | 'database' => "webim",
11 | 'charset' => "utf8",
12 | 'setname' => true,
13 | 'persistent' => false, //MySQL长连接
14 | );
15 | return $db;
--------------------------------------------------------------------------------
/configs/log.php:
--------------------------------------------------------------------------------
1 | 'FileLog',
4 | 'file' => dirname(__DIR__) . '/logs/webim.log',
5 | );
6 | return $log;
--------------------------------------------------------------------------------
/configs/login.php:
--------------------------------------------------------------------------------
1 | BASE_URL . '/api/get_user_info/',
5 | 'passport' => BASE_URL.'/page/login/',
6 | );
--------------------------------------------------------------------------------
/configs/redis.php:
--------------------------------------------------------------------------------
1 | '127.0.0.1',
4 | );
5 | return $redis;
--------------------------------------------------------------------------------
/configs/upload.php:
--------------------------------------------------------------------------------
1 | ROOT_PATH . '/resources/static/uploads/',
4 | 'base_url' => '/static/uploads/',
5 | );
--------------------------------------------------------------------------------
/configs/webim.php:
--------------------------------------------------------------------------------
1 | '127.0.0.1',
5 | //监听的端口
6 | 'port' => '9503',
7 | //WebSocket的URL地址,供浏览器使用的
8 | 'url' => 'ws://127.0.0.1:9503',
9 | //用于Comet跨域,必须设置为html所在的URL
10 | 'origin' => 'http://im.swoole.com:8888',
11 | );
12 |
13 | $config['swoole'] = array(
14 | 'log_file' => ROOT_PATH . '/log/swoole.log',
15 | 'worker_num' => 1,
16 | //不要修改这里
17 | 'max_request' => 0,
18 | 'task_worker_num' => 1,
19 | //是否要作为守护进程
20 | 'daemonize' => 0,
21 | );
22 |
23 | $config['webim'] = array(
24 | //聊天记录存储的目录
25 | 'log_file' => ROOT_PATH . '/log/webim.log',
26 | 'send_interval_limit' => 2, //只允许1秒发送一次
27 | );
28 |
29 | $config['storage'] = array(
30 | 'history_num' => 100,
31 | );
32 |
33 | return $config;
--------------------------------------------------------------------------------
/resources/static/css/chat.css:
--------------------------------------------------------------------------------
1 | body {
2 | }
3 |
4 |
5 | ul.dropdown-menu {
6 | min-width: 100px;
7 | }
8 |
9 | .login-box {
10 | padding: 50px 0;
11 | background-color: white;
12 | }
13 |
14 | div.container {
15 | width: 98%;
16 | }
17 |
18 | .c-sidebar-nav {
19 | padding: 9px 0;
20 | height: 620px;
21 | }
22 |
23 | #left-column {
24 | position:fixed;
25 | left: -10px;
26 | }
27 |
28 | #chat-column {
29 | position: fixed;
30 | background-color: whiteSmoke;
31 | left: 220px;
32 | overflow:auto;
33 | height:600px;
34 | }
35 |
36 | #right-column {
37 | position:fixed;
38 | right: 10px;
39 | }
40 |
41 | #input-box {
42 | position:fixed;
43 | bottom: 0px;
44 | left: 220px;
45 | height: 65px;
46 | background-color: #DDD;
47 | }
48 |
49 | .post-form{
50 | margin: 10px 10px;
51 | }
52 |
53 | .separator {
54 | border-bottom: 1px solid #222;
55 | }
56 |
57 | #chat-column ul{
58 | list-style-type: none;
59 | margin-left: 0px;
60 | }
61 |
62 | div.message-container{
63 | clear: both;
64 | position:relative;
65 | line-height: 20px;
66 | padding: 6px 10px;
67 | }
68 |
69 | div#chat-messages {
70 | overflow: auto;
71 | height:450px;
72 | }
73 |
74 | div.msg-time{
75 | position:absolute;
76 | top:7px;
77 | right:10px;
78 | font-size: 11px;
79 | color: #999;
80 | }
81 |
82 | div.message{
83 | margin-right:100px;
84 | }
85 |
86 | #chat-column .userpic{
87 | float:left;
88 | margin-right:5px;
89 | }
90 |
91 | li.user-online {
92 | margin-bottom: 5px;
93 | }
94 |
95 | li.user-online img {
96 | margin-right: 5px;
97 | }
98 |
99 | #group-column {
100 | left: 250px;
101 | position: fixed;
102 | }
103 |
104 | .chat-room{
105 | margin-bottom: 20px;
106 | }
107 | #left-userlist {
108 | margin-left: 10px;
109 | }
110 |
111 | #left-userlist li {
112 | float: left;
113 | height: 60px;
114 | list-style: none outside none;
115 | margin-top: 10px;
116 | width: 60px;
117 | margin-left: 0px;
118 | }
119 | #left-userlist li img{
120 | border-radius: 5px;
121 | }
122 |
123 | .chat_face {
124 | text-align:center;
125 | height:28px;
126 | line-height:28px;
127 | color:#333;
128 | cursor:pointer;
129 | overflow:hidden;
130 | display:inline-block;
131 | }
132 |
133 | .chat_face_hover {
134 | color:#f25000;
135 | height:28px;
136 | line-height:28px;
137 | overflow:hidden;
138 | }
139 |
140 | .show_face {
141 | background:#FFFFFF;
142 | border:#ccc 1px solid;
143 | width:600px;
144 | display:none;
145 | z-index:9999;
146 | }
147 | .show_face_hovers {
148 | display:block;
149 | position:absolute;
150 | }
151 |
152 | .face {
153 | width:30px;
154 | margin-right:1px;
155 | height:30px;
156 | line-height:30px;
157 | font-size:13px;
158 | text-align:center;
159 | background:#f6f6f6;
160 | vertical-align:top;
161 | margin-bottom:1px;
162 | display:inline-block;
163 | }
164 |
--------------------------------------------------------------------------------
/resources/static/css/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | }
3 |
4 |
5 | ul.dropdown-menu {
6 | min-width: 100px;
7 | }
8 |
9 | .login-box {
10 | padding: 50px 0;
11 | background-color: white;
12 | }
13 |
14 | div.container {
15 | width: 98%;
16 | }
17 |
18 | .c-sidebar-nav {
19 | padding: 9px 0;
20 | }
21 |
22 | #left-column {
23 | position:fixed;
24 | left: -10px;
25 | }
26 |
27 | #chat-column {
28 | position: fixed;
29 | background-color: whiteSmoke;
30 | left: 220px;
31 | overflow:auto;
32 | height:600px;
33 | }
34 |
35 | #right-column {
36 | position:fixed;
37 | right: 10px;
38 | }
39 |
40 | #input-box {
41 | position:fixed;
42 | bottom: 0px;
43 | left: 220px;
44 | height: 65px;
45 | background-color: #DDD;
46 | }
47 |
48 | .post-form{
49 | margin: 10px 10px;
50 | }
51 |
52 | .separator {
53 | border-bottom: 1px solid #222;
54 | }
55 |
56 | #chat-column ul{
57 | list-style-type: none;
58 | margin-left: 0px;
59 | }
60 |
61 | div.message-container{
62 | clear: both;
63 | position:relative;
64 | line-height: 20px;
65 | padding: 6px 10px;
66 | }
67 |
68 | div#chat-messages {
69 | overflow: auto;
70 | height:600px;
71 | }
72 |
73 | div.msg-time{
74 | position:absolute;
75 | top:7px;
76 | right:10px;
77 | font-size: 11px;
78 | color: #999;
79 | }
80 |
81 | div.message{
82 | margin-right:100px;
83 | }
84 |
85 | #chat-column .userpic{
86 | float:left;
87 | margin-right:5px;
88 | }
89 |
90 | li.user-online {
91 | margin-bottom: 5px;
92 | }
93 |
94 | li.user-online img {
95 | margin-right: 5px;
96 | }
97 |
98 | #group-column {
99 | left: 250px;
100 | position: fixed;
101 | }
102 |
103 | .chat-room{
104 | margin-bottom: 20px;
105 | }
106 | #left-userlist {
107 | margin-left: 10px;
108 | }
109 |
110 | #left-userlist li {
111 | float: left;
112 | height: 60px;
113 | list-style: none outside none;
114 | margin-top: 10px;
115 | width: 60px;
116 | margin-left: 0px;
117 | }
118 | #left-userlist li img{
119 | border-radius: 5px;
120 | }
--------------------------------------------------------------------------------
/resources/static/img/avatar1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/avatar1.jpg
--------------------------------------------------------------------------------
/resources/static/img/button.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/button.gif
--------------------------------------------------------------------------------
/resources/static/img/button.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/button.jpg
--------------------------------------------------------------------------------
/resources/static/img/button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/button.png
--------------------------------------------------------------------------------
/resources/static/img/button1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/button1.jpg
--------------------------------------------------------------------------------
/resources/static/img/button2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/button2.jpg
--------------------------------------------------------------------------------
/resources/static/img/default.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/default.jpg
--------------------------------------------------------------------------------
/resources/static/img/f1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/f1.png
--------------------------------------------------------------------------------
/resources/static/img/face/1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/1.gif
--------------------------------------------------------------------------------
/resources/static/img/face/10.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/10.gif
--------------------------------------------------------------------------------
/resources/static/img/face/11.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/11.gif
--------------------------------------------------------------------------------
/resources/static/img/face/12.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/12.gif
--------------------------------------------------------------------------------
/resources/static/img/face/13.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/13.gif
--------------------------------------------------------------------------------
/resources/static/img/face/14.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/14.gif
--------------------------------------------------------------------------------
/resources/static/img/face/15.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/15.gif
--------------------------------------------------------------------------------
/resources/static/img/face/16.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/16.gif
--------------------------------------------------------------------------------
/resources/static/img/face/17.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/17.gif
--------------------------------------------------------------------------------
/resources/static/img/face/18.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/18.gif
--------------------------------------------------------------------------------
/resources/static/img/face/19.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/19.gif
--------------------------------------------------------------------------------
/resources/static/img/face/2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/2.gif
--------------------------------------------------------------------------------
/resources/static/img/face/3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/3.gif
--------------------------------------------------------------------------------
/resources/static/img/face/4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/4.gif
--------------------------------------------------------------------------------
/resources/static/img/face/5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/5.gif
--------------------------------------------------------------------------------
/resources/static/img/face/6.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/6.gif
--------------------------------------------------------------------------------
/resources/static/img/face/7.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/7.gif
--------------------------------------------------------------------------------
/resources/static/img/face/8.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/8.gif
--------------------------------------------------------------------------------
/resources/static/img/face/9.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matyhtf/webim/62b3f879eacfe6edf8e72a20970b01247de4ecb1/resources/static/img/face/9.gif
--------------------------------------------------------------------------------
/resources/static/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap.js by @fat & @mdo
3 | * Copyright 2012 Twitter, Inc.
4 | * http://www.apache.org/licenses/LICENSE-2.0.txt
5 | */
6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||s.toggleClass("open"),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f
t |