",
5 | "version": "0.1.0",
6 | "scripts": {
7 | "start": "cross-env NODE_ENV=development node build/dev.js",
8 | "build": "cross-env NODE_ENV=production node build/prod.js"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/kenberkeley/react-demo.git"
13 | },
14 | "dependencies": {
15 | "history": "^2.0.0",
16 | "lodash": "^4.15.0",
17 | "react": "~15.3.1",
18 | "react-dom": "~15.3.1",
19 | "react-redux": "^4.4.5",
20 | "react-router": "^2.7.0",
21 | "react-router-redux": "^4.0.5",
22 | "redux": "^3.5.2",
23 | "redux-thunk": "^2.1.0"
24 | },
25 | "devDependencies": {
26 | "babel-core": "^6.0.0",
27 | "babel-eslint": "^4.1.3",
28 | "babel-loader": "^6.0.0",
29 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
30 | "babel-plugin-transform-runtime": "^6.0.0",
31 | "babel-preset-es2015": "^6.0.0",
32 | "babel-preset-react": "^6.3.13",
33 | "babel-preset-react-optimize": "^1.0.1",
34 | "babel-preset-stage-0": "^6.0.0",
35 | "babel-runtime": "^6.9.0",
36 | "browser-sync": "^2.11.1",
37 | "browser-sync-webpack-plugin": "^1.0.1",
38 | "clean-webpack-plugin": "^0.1.13",
39 | "connect-history-api-fallback": "^1.1.0",
40 | "copy-webpack-plugin": "^4.0.1",
41 | "cross-env": "^3.2.4",
42 | "css-loader": "^0.23.0",
43 | "eslint": "^1.10.3",
44 | "eslint-friendly-formatter": "^1.2.2",
45 | "eslint-loader": "^1.2.0",
46 | "eslint-plugin-babel": "^3.2.0",
47 | "eslint-plugin-react": "^5.0.0",
48 | "eventsource-polyfill": "^0.9.6",
49 | "express": "^4.13.3",
50 | "express-favicon": "^1.0.1",
51 | "extract-text-webpack-plugin": "^0.9.1",
52 | "file-loader": "^0.8.4",
53 | "html-loader": "^0.4.3",
54 | "html-webpack-plugin": "^1.7.0",
55 | "inject-loader": "^2.0.1",
56 | "json-loader": "^0.5.4",
57 | "less": "^2.6.0",
58 | "less-loader": "^2.2.2",
59 | "node-sass": "^3.4.2",
60 | "nyan-progress-webpack-plugin": "^1.1.4",
61 | "react-hot-loader": "^1.3.0",
62 | "redux-devtools": "^3.3.1",
63 | "redux-devtools-dock-monitor": "^1.1.1",
64 | "redux-devtools-log-monitor": "^1.0.11",
65 | "redux-logger": "^2.6.1",
66 | "sass-loader": "^3.2.0",
67 | "style-loader": "^0.13.1",
68 | "url-loader": "^0.5.7",
69 | "webpack": "^1.12.2",
70 | "webpack-dev-middleware": "^1.4.0",
71 | "webpack-hot-middleware": "^2.6.0",
72 | "why-did-you-update": "0.0.8"
73 | },
74 | "license": "MIT"
75 | }
76 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenberkeley/react-demo/01d0dcd04c1dcdfc36dc8cbb6f02285206a3f273/screenshot.png
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | /* 入口启动文件 */
2 | import React from 'react'
3 | import ReactDOM from 'react-dom'
4 | import { Provider } from 'react-redux'
5 | import { Router } from 'react-router'
6 | import store, { history } from 'STORE'
7 | import routes from 'ROUTE'
8 |
9 | /**
10 | * 下面这货用于检测不必要的重新渲染,详情请看其项目地址:
11 | * https://github.com/garbles/why-did-you-update
12 | *
13 | * 有关性能提升方面的问题
14 | * 诸如 PureComponent / shouldComponentUpdate / Immutable.js 等
15 | * 请自行查阅相关资料
16 | */
17 | if (__DEV__ && __WHY_DID_YOU_UPDATE__) {
18 | const { whyDidYouUpdate } = require('why-did-you-update')
19 | whyDidYouUpdate(React)
20 | }
21 | if (__DEV__) {
22 | console.info('[当前环境] 开发环境')
23 | }
24 | if (__PROD__) {
25 | console.info('[当前环境] 生产环境')
26 | }
27 |
28 | // ================================
29 | // 将根组件挂载到 DOM,启动!
30 | // ================================
31 | const MOUNT_NODE = document.getElementById('app')
32 |
33 | ReactDOM.render(
34 |
35 |
36 | ,
37 | MOUNT_NODE
38 | )
39 |
40 | // === Webpack 处理 assets,取消注释即可进行测试 === //
41 | /* 处理 less / sass */
42 | // import 'ASSET/less/normalize.less'
43 | // import 'ASSET/scss/normalize.scss'
44 |
45 | /* 处理 img,小于 10KB 的转为 base64,否则使用 URL */
46 | // import base64 from 'ASSET/img/smaller.png'
47 | // import url from 'ASSET/img/larger.png'
48 |
49 | // function appendImgToBody(content) {
50 | // const img = document.createElement('img')
51 | // img.src = content
52 | // document.body.appendChild(img)
53 | // }
54 |
55 | // appendImgToBody(base64)
56 | // appendImgToBody(url)
57 |
58 |
59 | /**
60 | * 【拓展】
61 | * react-redux 的 Provider 中传入的属性
62 | * 可以让全体组件轻松访问,避免繁琐累赘的层层下传。例子:
63 | *
64 | * class XXX extends Component {
65 | * static contextTypes = {
66 | * // 组件中需要这样子声明
67 | * store: PropTypes.object.isRequired
68 | * }
69 | * componentDidMount () {
70 | * // 之后就可以直接这样用
71 | * this.context.store.getState()
72 | * }
73 | * }
74 | *
75 | * 但上面这种官方的做法实在太麻烦,于是我们有更为直接的方式:
76 | * import store from 'STORE'
77 | * store.getState() // 只读,更改 state 只能通过 dispatch
78 | */
79 |
--------------------------------------------------------------------------------
/src/assets/README.md:
--------------------------------------------------------------------------------
1 | 放置需要经由 Webpack 处理的文件
2 | e.g. img css font 等
--------------------------------------------------------------------------------
/src/assets/img/larger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenberkeley/react-demo/01d0dcd04c1dcdfc36dc8cbb6f02285206a3f273/src/assets/img/larger.png
--------------------------------------------------------------------------------
/src/assets/img/smaller.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenberkeley/react-demo/01d0dcd04c1dcdfc36dc8cbb6f02285206a3f273/src/assets/img/smaller.png
--------------------------------------------------------------------------------
/src/assets/less/normalize.less:
--------------------------------------------------------------------------------
1 | // =============================================================================
2 | // Normalize.less based on Nicolas Gallagher and Jonathan Neal's
3 | // normalize.css v3.0.1 | MIT License | github.com/necolas/normalize.css
4 | // =============================================================================
5 |
6 | //
7 | // Variables
8 | // ====================================================================
9 |
10 | // Base font-family
11 | @base-font-family: sans-serif;
12 |
13 | // The base font size
14 | @base-font-size: 16px;
15 |
16 | // The base line height determines the basic unit of vertical rhythm.
17 | @base-line-height: 24px;
18 |
19 | // Heading sizes
20 | @h1-size: 36px;
21 |
22 |
23 | //
24 | // Mixins
25 | // ====================================================================
26 |
27 | //
28 | // Passing in a single value will create font-sizing in
29 | // pixels, rems as well as a proper line-height.
30 | //
31 | // `.font-size(24px, false);`
32 | //
33 |
34 | .font-size(@font-size, @line-height: true) {
35 | @px-value: (@font-size);
36 | @rem-value: (@font-size / @base-font-size) * 1rem;
37 | @line-height-value: ceil(@font-size / @base-line-height) * (@base-line-height / @font-size);
38 |
39 | font-size: ~"@{px-value}";
40 | font-size: @rem-value;
41 |
42 | .line-height(@boolean) when (@boolean = true) {
43 | line-height: unit(@line-height-value);
44 | }
45 |
46 | .line-height(@line-height);
47 | }
48 |
49 |
50 | //
51 | // 1. Set default font family to sans-serif.
52 | // 2. Prevent iOS and IE text size adjust after device orientation change,
53 | // without disabling user zoom.
54 | //
55 |
56 | html {
57 | font-size: 100%; // 1 //
58 | -ms-text-size-adjust: 100%; // 2 //
59 | -webkit-text-size-adjust: 100%; // 2 //
60 | }
61 |
62 |
63 | //
64 | // Remove default margin.
65 | //
66 |
67 | body { margin: 0; }
68 |
69 |
70 | //
71 | // HTML5 display definitions
72 | // ====================================================================
73 |
74 | //
75 | // Correct `block` display not defined for any HTML5 element in IE 8/9.
76 | // Correct `block` display not defined for `details` or `summary` in IE 10/11
77 | // and Firefox.
78 | // Correct `block` display not defined for `main` in IE 11.
79 | //
80 |
81 | article,
82 | aside,
83 | details,
84 | figcaption,
85 | figure,
86 | footer,
87 | header,
88 | hgroup,
89 | menu,
90 | main,
91 | nav,
92 | section,
93 | summary { display: block; }
94 |
95 |
96 | //
97 | // 1. Correct `inline-block` display not defined in IE 8/9.
98 | // 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
99 | //
100 |
101 | audio,
102 | canvas,
103 | progress,
104 | video {
105 | display: inline-block; // 1 //
106 | vertical-align: baseline; // 2 //
107 | }
108 |
109 |
110 | //
111 | // Prevent modern browsers from displaying `audio` without controls.
112 | // Remove excess height in iOS 5 devices.
113 | //
114 |
115 | audio:not([controls]) {
116 | display: none;
117 | height: 0;
118 | }
119 |
120 |
121 | //
122 | // Address `[hidden]` styling not present in IE 8/9/10.
123 | // Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
124 | //
125 |
126 | [hidden],
127 | template { display: none; }
128 |
129 |
130 | //
131 | // Links
132 | // ====================================================================
133 |
134 | //
135 | // Remove the gray background color from active links in IE 10.
136 | //
137 |
138 | a {
139 | background-color: transparent;
140 |
141 |
142 | //
143 | // Improve readability of focused elements when they are also in an
144 | // active/hover state.
145 | //
146 |
147 | &:focus { outline: thin dotted; }
148 |
149 | &:active,
150 | &:hover { outline: 0; }
151 | }
152 |
153 |
154 | //
155 | // Text-level semantics
156 | // ====================================================================
157 |
158 | //
159 | // Address styling not present in IE 8/9/10/11, Safari, and Chrome.
160 | //
161 |
162 | abbr[title] { border-bottom: 1px dotted; }
163 |
164 |
165 | //
166 | // Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
167 | //
168 |
169 | b,
170 | strong { font-weight: bold; }
171 |
172 |
173 | //
174 | // Address styling not present in Safari and Chrome.
175 | //
176 |
177 | dfn { font-style: italic; }
178 |
179 |
180 | //
181 | // Address variable `h1` font-size and margin within `section` and `article`
182 | // contexts in Firefox 4+, Safari, and Chrome.
183 | //
184 |
185 | h1 {
186 | .font-size(@h1-size);
187 | margin: 0.67em 0;
188 | }
189 |
190 |
191 | //
192 | // Address styling not present in IE 8/9.
193 | //
194 |
195 | mark {
196 | background: #ff0;
197 | color: #000;
198 | }
199 |
200 |
201 | //
202 | // Address inconsistent and variable font size in all browsers.
203 | //
204 |
205 | small { font-size: 80%; }
206 |
207 |
208 | //
209 | // Prevent `sub` and `sup` affecting `line-height` in all browsers.
210 | //
211 |
212 | sub,
213 | sup {
214 | font-size: 75%;
215 | line-height: 0;
216 | position: relative;
217 | vertical-align: baseline;
218 | }
219 |
220 | sup { top: -0.5em; }
221 |
222 | sub { bottom: -0.25em; }
223 |
224 |
225 | //
226 | // Embedded content
227 | // ====================================================================
228 |
229 | //
230 | // Remove border when inside `a` element in IE 8/9/10.
231 | //
232 |
233 | img { border: 0; }
234 |
235 |
236 | //
237 | // Correct overflow not hidden in IE 9/10/11.
238 | //
239 |
240 | svg:not(:root) { overflow: hidden; }
241 |
242 |
243 | //
244 | // Grouping content
245 | // ====================================================================
246 |
247 | //
248 | // Address margin not present in IE 8/9 and Safari.
249 | //
250 |
251 | figure { margin: 1em 40px; }
252 |
253 |
254 | //
255 | // Address differences between Firefox and other browsers.
256 | //
257 |
258 | hr {
259 | box-sizing: content-box;
260 | height: 0;
261 | }
262 |
263 |
264 | //
265 | // Contain overflow in all browsers.
266 | //
267 |
268 | pre { overflow: auto; }
269 |
270 |
271 | //
272 | // Address odd `em`-unit font size rendering in all browsers.
273 | //
274 |
275 | code,
276 | kbd,
277 | pre,
278 | samp {
279 | font-family: monospace, monospace;
280 | .font-size(@base-font-size);
281 | }
282 |
283 |
284 |
285 | //
286 | // Forms
287 | // ====================================================================
288 |
289 | //
290 | // Known limitation: by default, Chrome and Safari on OS X allow very limited
291 | // styling of `select`, unless a `border` property is set.
292 | //
293 |
294 | //
295 | // 1. Correct color not being inherited.
296 | // Known issue: affects color of disabled elements.
297 | // 2. Correct font properties not being inherited.
298 | // 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
299 | //
300 |
301 | button,
302 | input,
303 | optgroup,
304 | select,
305 | textarea {
306 | color: inherit; // 1 //
307 | font: inherit; // 1 //
308 | margin: 0; // 1 //
309 | }
310 |
311 |
312 | //
313 | // Address `overflow` set to `hidden` in IE 8/9/10/11.
314 | //
315 |
316 | button { overflow: visible; }
317 |
318 |
319 | //
320 | // Address inconsistent `text-transform` inheritance for `button` and `select`.
321 | // All other form control elements do not inherit `text-transform` values.
322 | // Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
323 | // Correct `select` style inheritance in Firefox.
324 | //
325 |
326 | button,
327 | select { text-transform: none; }
328 |
329 |
330 | //
331 | // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
332 | // and `video` controls.
333 | // 2. Correct inability to style clickable `input` types in iOS.
334 | // 3. Improve usability and consistency of cursor style between image-type
335 | // `input` and others.
336 | //
337 |
338 | button,
339 | html input[type="button"], // 1 //
340 | input[type="reset"],
341 | input[type="submit"] {
342 | -webkit-appearance: button; // 2 //
343 | cursor: pointer; // 3 //
344 | }
345 |
346 |
347 | //
348 | // Re-set default cursor for disabled elements.
349 | //
350 |
351 | button[disabled],
352 | html input[disabled] { cursor: default; }
353 |
354 |
355 | //
356 | // Remove inner padding and border in Firefox 4+.
357 | //
358 |
359 | button::-moz-focus-inner,
360 | input::-moz-focus-inner {
361 | border: 0;
362 | padding: 0;
363 | }
364 |
365 |
366 | //
367 | // Address Firefox 4+ setting `line-height` on `input` using `!important` in
368 | // the UA stylesheet.
369 | //
370 |
371 | input {
372 | line-height: normal;
373 |
374 |
375 | //
376 | // It's recommended that you don't attempt to style these elements.
377 | // Firefox's implementation doesn't respect box-sizing, padding, or width.
378 | //
379 | // 1. Address box sizing set to `content-box` in IE 8/9/10.
380 | // 2. Remove excess padding in IE 8/9/10.
381 | //
382 |
383 | &[type="checkbox"],
384 | &[type="radio"] {
385 | box-sizing: border-box; // 1 //
386 | padding: 0; // 2 //
387 | }
388 |
389 |
390 | //
391 | // Fix the cursor style for Chrome's increment/decrement buttons. For certain
392 | // `font-size` values of the `input`, it causes the cursor style of the
393 | // decrement button to change from `default` to `text`.
394 | //
395 |
396 | &[type="number"] {
397 | &::-webkit-inner-spin-button,
398 | &::-webkit-outer-spin-button { height: auto; }
399 | }
400 |
401 |
402 | //
403 | // 1. Address `appearance` set to `searchfield` in Safari and Chrome.
404 | // 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
405 | //
406 |
407 | &[type="search"] {
408 | -webkit-appearance: textfield; // 1 //
409 | box-sizing: content-box; // 2 //
410 |
411 |
412 | //
413 | // Remove inner padding and search cancel button in Safari and Chrome on OS X.
414 | // Safari (but not Chrome) clips the cancel button when the search input has
415 | // padding (and `textfield` appearance).
416 | //
417 |
418 | &::-webkit-search-cancel-button,
419 | &::-webkit-search-decoration { -webkit-appearance: none; }
420 | }
421 | }
422 |
423 |
424 | //
425 | // Define consistent border, margin, and padding.
426 | //
427 |
428 | fieldset {
429 | border: 1px solid #c0c0c0;
430 | margin: 0 2px;
431 | padding: 0.35em 0.625em 0.75em;
432 | }
433 |
434 |
435 | //
436 | // 1. Correct `color` not being inherited in IE 8/9/10/11.
437 | // 2. Remove padding so people aren't caught out if they zero out fieldsets.
438 | //
439 |
440 | legend {
441 | border: 0; // 1 //
442 | padding: 0; // 2 //
443 | }
444 |
445 |
446 | //
447 | // Remove default vertical scrollbar in IE 8/9/10/11.
448 | //
449 |
450 | textarea { overflow: auto; }
451 |
452 |
453 | //
454 | // Don't inherit the `font-weight` (applied by a rule above).
455 | // NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
456 | //
457 |
458 | optgroup { font-weight: bold; }
459 |
460 |
461 | //
462 | // Tables
463 | // ====================================================================
464 |
465 | //
466 | // Remove most spacing between table cells.
467 | //
468 |
469 | table {
470 | border-collapse: collapse;
471 | border-spacing: 0;
472 | }
473 |
474 | td,
475 | th { padding: 0; }
476 |
--------------------------------------------------------------------------------
/src/assets/scss/normalize.scss:
--------------------------------------------------------------------------------
1 | // =============================================================================
2 | // Normalize.scss based on Nicolas Gallagher and Jonathan Neal's
3 | // normalize.css v2.1.3 | MIT License | git.io/normalize
4 | // =============================================================================
5 |
6 | // =============================================================================
7 | // Normalize.scss settings
8 | // =============================================================================
9 |
10 |
11 | // Set to true if you want to add support for IE6 and IE7
12 | // Notice: setting to true might render some elements
13 | // slightly differently than when set to false
14 | $legacy_support_for_ie: false !default; // Used also in Compass
15 |
16 |
17 | // Set the default font family here so you don't have to override it later
18 | $normalized_font_family: sans-serif !default;
19 |
20 | $normalize_headings: true !default;
21 |
22 | $h1_font_size: 2em !default;
23 | $h2_font_size: 1.5em !default;
24 | $h3_font_size: 1.17em !default;
25 | $h4_font_size: 1em !default;
26 | $h5_font_size: 0.83em !default;
27 | $h6_font_size: 0.75em !default;
28 |
29 | $h1_margin: 0.67em 0 !default;
30 | $h2_margin: 0.83em 0 !default;
31 | $h3_margin: 1em 0 !default;
32 | $h4_margin: 1.33em 0 !default;
33 | $h5_margin: 1.67em 0 !default;
34 | $h6_margin: 2.33em 0 !default;
35 |
36 | $background: #fff !default;
37 | $color: #000 !default;
38 |
39 | // =============================================================================
40 | // HTML5 display definitions
41 | // =============================================================================
42 |
43 | // Corrects block display not defined in IE6/7/8/9 & FF3
44 |
45 | article,
46 | aside,
47 | details,
48 | figcaption,
49 | figure,
50 | footer,
51 | header,
52 | hgroup,
53 | nav,
54 | section,
55 | summary {
56 | display: block;
57 | }
58 |
59 | // Corrects inline-block display not defined in IE6/7/8/9 & FF3
60 |
61 | audio,
62 | canvas,
63 | video {
64 | display: inline-block;
65 | @if $legacy_support_for_ie {
66 | *display: inline;
67 | *zoom: 1;
68 | }
69 | }
70 |
71 | // 1. Prevents modern browsers from displaying 'audio' without controls
72 | // 2. Remove excess height in iOS5 devices
73 |
74 | audio:not([controls]) {
75 | display: none; // 1
76 | height: 0; // 2
77 | }
78 |
79 | //
80 | // Address `[hidden]` styling not present in IE 8/9.
81 | // Hide the `template` element in IE, Safari, and Firefox < 22.
82 | //
83 |
84 | [hidden], template {
85 | display: none;
86 | }
87 |
88 | // =============================================================================
89 | // Base
90 | // =============================================================================
91 |
92 | // 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units
93 | // http://clagnut.com/blog/348/#c790
94 | // 2. Prevents iOS text size adjust after orientation change, without disabling user zoom
95 | // www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/
96 |
97 | html {
98 | @if $legacy_support_for_ie {
99 | font-size: 100%; // 1
100 | }
101 | background: $background;
102 | color: $color;
103 | -webkit-text-size-adjust: 100%; // 2
104 | -ms-text-size-adjust: 100%; // 2
105 | }
106 |
107 | // Addresses font-family inconsistency between 'textarea' and other form elements.
108 |
109 | html,
110 | button,
111 | input,
112 | select,
113 | textarea {
114 | font-family: $normalized_font_family;
115 | }
116 |
117 | // Addresses margins handled incorrectly in IE6/7
118 |
119 | body {
120 | margin: 0;
121 | }
122 |
123 | // =============================================================================
124 | // Links
125 | // =============================================================================
126 |
127 | // 1. Remove the gray background color from active links in IE 10.
128 | // 2. Addresses outline displayed oddly in Chrome
129 | // 3. Improves readability when focused and also mouse hovered in all browsers
130 | // people.opera.com/patrickl/experiments/keyboard/test
131 |
132 | a {
133 | // 1
134 |
135 | background: transparent;
136 |
137 | // 2
138 |
139 | &:focus {
140 | outline: thin dotted;
141 | }
142 |
143 | // 3
144 |
145 | &:hover,
146 | &:active {
147 | outline: 0;
148 | }
149 | }
150 |
151 | // =============================================================================
152 | // Typography
153 | // =============================================================================
154 |
155 | // Addresses font sizes and margins set differently in IE6/7
156 | // Addresses font sizes within 'section' and 'article' in FF4+, Chrome, S5
157 |
158 | @if $normalize_headings == true {
159 | h1 {
160 | font-size: $h1_font_size;
161 | margin: $h1_margin;
162 | }
163 |
164 | h2 {
165 | font-size: $h2_font_size;
166 | margin: $h2_margin;
167 | }
168 |
169 | h3 {
170 | font-size: $h3_font_size;
171 | margin: $h3_margin;
172 | }
173 |
174 | h4 {
175 | font-size: $h4_font_size;
176 | margin: $h4_margin;
177 | }
178 |
179 | h5 {
180 | font-size: $h5_font_size;
181 | margin: $h5_margin;
182 | }
183 |
184 | h6 {
185 | font-size: $h6_font_size;
186 | margin: $h6_margin;
187 | }
188 | }
189 |
190 | // Addresses styling not present in IE 8/9, S5, Chrome
191 |
192 | abbr[title] {
193 | border-bottom: 1px dotted;
194 | }
195 |
196 | // Addresses style set to 'bolder' in FF3+, S4/5, Chrome
197 |
198 | b,
199 | strong {
200 | font-weight: bold;
201 | }
202 |
203 | @if $legacy_support_for_ie {
204 | blockquote {
205 | margin: 1em 40px;
206 | }
207 | }
208 |
209 | // Addresses styling not present in S5, Chrome
210 |
211 | dfn {
212 | font-style: italic;
213 | }
214 |
215 | // Addresses styling not present in IE6/7/8/9
216 |
217 | mark {
218 | background: #ff0;
219 | color: #000;
220 | }
221 |
222 | // Addresses margins set differently in IE6/7
223 | @if $legacy_support_for_ie {
224 | p,
225 | pre {
226 | margin: 1em 0;
227 | }
228 | }
229 |
230 | // Corrects font family set oddly in IE6, S4/5, Chrome
231 | // en.wikipedia.org/wiki/User:Davidgothberg/Test59
232 |
233 | code,
234 | kbd,
235 | pre,
236 | samp {
237 | font-family: monospace, serif;
238 | @if $legacy_support_for_ie {
239 | _font-family: 'courier new', monospace;
240 | }
241 | font-size: 1em;
242 | }
243 |
244 | // Improves readability of pre-formatted text in all browsers
245 |
246 | pre {
247 | white-space: pre;
248 | white-space: pre-wrap;
249 | word-wrap: break-word;
250 | }
251 |
252 | // Set consistent quote types.
253 |
254 | q {
255 | quotes: "\201C" "\201D" "\2018" "\2019";
256 | }
257 |
258 | // 1. Addresses CSS quotes not supported in IE6/7
259 | // 2. Addresses quote property not supported in S4
260 |
261 | // 1
262 | @if $legacy_support_for_ie {
263 | q {
264 | quotes: none;
265 | }
266 | }
267 |
268 | // 2
269 | q {
270 | &:before,
271 | &:after {
272 | content: '';
273 | content: none;
274 | }
275 | }
276 |
277 | // Address inconsistent and variable font size in all browsers.
278 |
279 | small {
280 | font-size: 80%;
281 | }
282 |
283 | // Prevents sub and sup affecting line-height in all browsers
284 | // gist.github.com/413930
285 |
286 | sub,
287 | sup {
288 | font-size: 75%;
289 | line-height: 0;
290 | position: relative;
291 | vertical-align: baseline;
292 | }
293 |
294 | sup {
295 | top: -0.5em;
296 | }
297 |
298 | sub {
299 | bottom: -0.25em;
300 | }
301 |
302 | // =============================================================================
303 | // Lists
304 | // =============================================================================
305 |
306 | // Addresses margins set differently in IE6/7
307 | @if $legacy_support_for_ie {
308 | dl,
309 | menu,
310 | ol,
311 | ul {
312 | margin: 1em 0;
313 | }
314 | }
315 |
316 | @if $legacy_support_for_ie {
317 | dd {
318 | margin: 0 0 0 40px;
319 | }
320 | }
321 |
322 | // Addresses paddings set differently in IE6/7
323 | @if $legacy_support_for_ie {
324 | menu,
325 | ol,
326 | ul {
327 | padding: 0 0 0 40px;
328 | }
329 | }
330 |
331 | // Corrects list images handled incorrectly in IE7
332 |
333 | nav {
334 | ul,
335 | ol {
336 | @if $legacy_support_for_ie {
337 | list-style-image: none;
338 | }
339 | }
340 | }
341 |
342 | // =============================================================================
343 | // Embedded content
344 | // =============================================================================
345 |
346 | // 1. Removes border when inside 'a' element in IE6/7/8/9, FF3
347 | // 2. Improves image quality when scaled in IE7
348 | // code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/
349 |
350 | img {
351 | border: 0; // 1
352 | @if $legacy_support_for_ie {
353 | -ms-interpolation-mode: bicubic; // 2
354 | }
355 | }
356 |
357 | // Corrects overflow displayed oddly in IE9
358 |
359 | svg:not(:root) {
360 | overflow: hidden;
361 | }
362 |
363 | // =============================================================================
364 | // Figures
365 | // =============================================================================
366 |
367 | // Addresses margin not present in IE6/7/8/9, S5, O11
368 |
369 | figure {
370 | margin: 0;
371 | }
372 |
373 | // =============================================================================
374 | // Forms
375 | // =============================================================================
376 |
377 | // Corrects margin displayed oddly in IE6/7
378 | @if $legacy_support_for_ie {
379 | form {
380 | margin: 0;
381 | }
382 | }
383 |
384 | // Define consistent border, margin, and padding
385 |
386 | fieldset {
387 | border: 1px solid #c0c0c0;
388 | margin: 0 2px;
389 | padding: 0.35em 0.625em 0.75em;
390 | }
391 |
392 | // 1. Corrects color not being inherited in IE6/7/8/9
393 | // 2. Remove padding so people aren't caught out if they zero out fieldsets.
394 | // 3. Corrects text not wrapping in FF3
395 | // 4. Corrects alignment displayed oddly in IE6/7
396 |
397 | legend {
398 | border: 0; // 1
399 | padding: 0; // 2
400 | white-space: normal; // 3
401 | @if $legacy_support_for_ie {
402 | *margin-left: -7px; // 4
403 | }
404 | }
405 |
406 | // 1. Correct font family not being inherited in all browsers.
407 | // 2. Corrects font size not being inherited in all browsers
408 | // 3. Addresses margins set differently in IE6/7, FF3+, S5, Chrome
409 | // 4. Improves appearance and consistency in all browsers
410 |
411 | button,
412 | input,
413 | select,
414 | textarea {
415 | font-family: inherit; // 1
416 | font-size: 100%; // 2
417 | margin: 0; // 3
418 | vertical-align: baseline; // 4
419 | @if $legacy_support_for_ie {
420 | *vertical-align: middle; // 4
421 | }
422 | }
423 |
424 | // Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet
425 |
426 | button, input {
427 | line-height: normal;
428 | }
429 |
430 | // Address inconsistent `text-transform` inheritance for `button` and `select`.
431 | // All other form control elements do not inherit `text-transform` values.
432 | // Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
433 | // Correct `select` style inheritance in Firefox 4+ and Opera.
434 |
435 | button,
436 | select {
437 | text-transform: none;
438 | }
439 |
440 | // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
441 | // and `video` controls
442 | // 2. Corrects inability to style clickable 'input' types in iOS
443 | // 3. Improves usability and consistency of cursor style between image-type
444 | // 'input' and others
445 | // 4. Removes inner spacing in IE7 without affecting normal text inputs
446 | // Known issue: inner spacing remains in IE6
447 |
448 | button,
449 | html input[type="button"], // 1
450 | input[type="reset"],
451 | input[type="submit"] {
452 | -webkit-appearance: button; // 2
453 | cursor: pointer; // 3
454 | @if $legacy_support_for_ie {
455 | *overflow: visible; // 4
456 | }
457 | }
458 |
459 | // Re-set default cursor for disabled elements
460 |
461 | button[disabled],
462 | input[disabled] {
463 | cursor: default;
464 | }
465 |
466 | // Removes inner padding and border in FF3+
467 | // www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/
468 |
469 | button, input {
470 | &::-moz-focus-inner {
471 | border: 0;
472 | padding: 0;
473 | }
474 | }
475 |
476 | // 1. Removes default vertical scrollbar in IE6/7/8/9
477 | // 2. Improves readability and alignment in all browsers
478 |
479 | textarea {
480 | overflow: auto; // 1
481 | vertical-align: top; // 2
482 | }
483 |
484 | // =============================================================================
485 | // Tables
486 | // =============================================================================
487 |
488 | // Remove most spacing between table cells
489 |
490 | table {
491 | border-collapse: collapse;
492 | border-spacing: 0;
493 | }
494 |
495 | input {
496 | // 1. Addresses appearance set to searchfield in S5, Chrome
497 | // 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof)
498 | &[type="search"] {
499 | -webkit-appearance: textfield; // 1
500 | -moz-box-sizing: content-box;
501 | -webkit-box-sizing: content-box; // 2
502 | box-sizing: content-box;
503 |
504 | // Remove inner padding and search cancel button in Safari 5 and Chrome
505 | // on OS X.
506 | &::-webkit-search-cancel-button,
507 | &::-webkit-search-decoration {
508 | -webkit-appearance: none;
509 | }
510 | }
511 |
512 | // 1. Address box sizing set to `content-box` in IE 8/9/10.
513 | // 2. Remove excess padding in IE 8/9/10.
514 | // 3. Removes excess padding in IE7
515 | // Known issue: excess padding remains in IE6
516 | &[type="checkbox"],
517 | &[type="radio"] {
518 | box-sizing: border-box; // 1
519 | padding: 0; // 2
520 | @if $legacy_support_for_ie {
521 | *height: 13px; // 3
522 | *width: 13px; // 3
523 | }
524 | }
525 | }
526 |
--------------------------------------------------------------------------------
/src/components/404.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | export default class NotFound extends Component {
4 | static contextTypes = {
5 | router: React.PropTypes.object.isRequired
6 | }
7 |
8 | componentWillMount() {
9 | alert('404 NOT FOUND')
10 | this.context.router.replace('/')
11 | }
12 |
13 | render () {
14 | // 非实体组件需显式返回 null
15 | return null
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Navbar from 'COMPONENT/Navbar/'
3 |
4 | let DevTools
5 | if (__DEV__ && __COMPONENT_DEVTOOLS__) {
6 | // 组件形式的 Redux DevTools
7 | DevTools = require('COMPONENT/DevTools').default
8 | }
9 |
10 | const App = ({ children, location }) => (
11 |
12 |
13 |
14 |
15 | {/* 相当于 Vue Demo 中的根 router-view */}
16 | { children }
17 |
18 |
19 | { DevTools &&
}
20 |
21 | )
22 |
23 | export default App
24 |
--------------------------------------------------------------------------------
/src/components/DevTools.js:
--------------------------------------------------------------------------------
1 | // 参考 https://github.com/gaearon/redux-devtools/blob/master/docs/Walkthrough.md
2 | import React from 'react'
3 | import { createDevTools } from 'redux-devtools'
4 | import LogMonitor from 'redux-devtools-log-monitor'
5 | import DockMonitor from 'redux-devtools-dock-monitor'
6 |
7 | // createDevTools takes a monitor and produces a DevTools component
8 | const DevTools = createDevTools(
9 | // Monitors are individually adjustable with props.
10 | // Consult their repositories to learn about those props.
11 | // Here, we put LogMonitor inside a DockMonitor.
12 | // Note: DockMonitor is visible by default.
13 |
18 |
19 |
20 | )
21 |
22 | export default DevTools
23 |
--------------------------------------------------------------------------------
/src/components/Msg/MsgDetail.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import { Link } from 'react-router'
3 | import OptBtnGroup from 'COMPONENT/Msg/OptBtnGroup'
4 | import dateTimeFormatter from 'UTIL/dateTimeFormatter'
5 | import msgService from 'SERVICE/msgService'
6 |
7 | export default class MsgDetail extends Component {
8 | /**
9 | * 有关 Context 的用法请看文档:https://facebook.github.io/react/docs/context.html
10 | * 实际上可不引入 this.context.router,直接使用 this.props.history 即可
11 | * 但控制台会报 Warning: [react-router] `props.history` and `context.history` are deprecated. Please use `context.router`. http://tiny.cc/router-contextchanges
12 | */
13 | static contextTypes = {
14 | router: PropTypes.object.isRequired
15 | }
16 |
17 | constructor (props, context) {
18 | super(props, context)
19 | this.state = { msg: {} }
20 | }
21 |
22 | componentWillMount() {
23 | // P.S: 在 Vue Demo 中,数据都是直接从后端 API 中获取
24 | // 而这里可以先直接从 state 中获取,获取不到才从服务器获取
25 | // (强制刷新页面会导致 state 被清空)
26 | let { msg: { msgs }, params: { msgId } } = this.props
27 |
28 | let msg = msgs.filter(({ id }) => id === msgId)[0]
29 | msg ? this.setState({ msg }) : this.fetchMsgFromAPI(msgId)
30 | }
31 |
32 | fetchMsgFromAPI (msgId) {
33 | msgService.fetch({ msgId }).then(msg => {
34 | if (!msg) return this.context.router.replace('/msg')
35 | this.setState({ msg })
36 | })
37 | }
38 |
39 | render () {
40 | let { userData, delMsg } = this.props
41 | let msg = this.state.msg
42 |
43 | return (
44 |
45 |
46 | 标题:
47 | { msg.title }
48 |
49 | { dateTimeFormatter(msg.time) }
50 |
51 |
52 | 发布者:
53 |
54 | { msg.author }
55 |
56 |
57 |
62 |
67 |
68 |
69 |
70 | { msg.content }
71 |
72 |
73 | )
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/components/Msg/MsgForm/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import msgService from 'SERVICE/msgService'
3 | import handleChange from 'MIXIN/handleChange'
4 | import tpl from './msg-form.jsx' // 分拆写 JSX 模板以减少单文件代码量
5 |
6 | /* 为什么不直接 const initState = { ... } 而是用函数返回呢?
7 | 皆因直接传 initState 仅是传引用,initState 本身可被修改 */
8 | const getInitState = () => ({ id: '', title: '', content: '' })
9 |
10 | /* 由于本组件由 /msg/add 与 /msg/:msgId 所公用
11 | 因此需要判断当前是“新增模式”还是“修改模式” */
12 | const isAddMode = pathname => pathname.startsWith('/msg/add')
13 |
14 | export default class MsgForm extends Component {
15 | static contextTypes = {
16 | router: PropTypes.object.isRequired
17 | }
18 |
19 | constructor (props, context) {
20 | // 既然用到了 context,显然需要 super 一下咯
21 | // 实际上最完善的形式的确就是如下写法
22 | super(props, context)
23 |
24 | // 初始 state 必须定义,否则会报错
25 | // 就像在 Vue 中需要在 data 中定义默认值
26 | this.state = getInitState()
27 |
28 | this.handleChange = handleChange.bind(this) // mixin
29 | }
30 |
31 | componentDidMount() {
32 | this.updateState()
33 | }
34 |
35 | /**
36 | * 由于本组件为共用组件,但 React 本身不提供类似 Vue 的 canReuse 属性
37 | * 在 /msg/add <==> /msg/modify/:msgId 之间的跳转,组件保持挂载状态
38 | * 故需要利用本函数更新 state。不在乎性能者可利用我们的 hack:Redirect 组件
39 | */
40 | componentWillReceiveProps(nextProps) {
41 | this.updateState(nextProps) // 传入 nextProps
42 | }
43 |
44 | /* 不传入 props 则默认使用当前 props */
45 | updateState ({ location, params: { msgId }, userData: { username }, msg: { msgs } } = this.props) {
46 | // 情况1:处于 /msg/add,直接就是还原初始状态
47 | if (isAddMode(location.pathname)) {
48 | return this.setState(getInitState())
49 | }
50 |
51 | // 情况2:处于 /msg/modify/:msgId,且 state 中 msgs 不为空
52 | if (msgs.length) {
53 | let nextState = msgs.filter(({ id }) => id === msgId)[0]
54 | if (!nextState || nextState.author !== username) {
55 | return this.handleIllegal()
56 | }
57 | return this.setState(nextState)
58 | }
59 |
60 | // 情况3:强制刷新 /msg/detail/:msgId 后,跳转到 /msg/modify/:msgId
61 | // 此时 state 中 msgs 为空,需要立即从后端 API 获取
62 | msgService.fetch({ msgId }).then(msg => {
63 | let { id, title, content, author } = msg
64 | if (!msg || author !== username) {
65 | return this.handleIllegal()
66 | }
67 | this.setState({ id, title, content })
68 | })
69 | }
70 |
71 | handleIllegal () {
72 | // 使用 setTimeout 防止阻塞跳转
73 | setTimeout(() => alert('非法访问'))
74 | location.replace('/msg')
75 |
76 | // 为什么不使用如下代码跳转?
77 | // this.context.router.replace('/msg')
78 | // 因为会触发 componentWillReceiveProps 无限循环
79 | }
80 |
81 | /* 同样地,提交表单前需要根据当前 mode 进行对应的操作 */
82 | handleSubmit (evt) {
83 | evt.preventDefault()
84 | let { pathname } = this.props.location
85 | let opt = isAddMode(pathname) ? 'addMsg' : 'modMsg'
86 |
87 | // 提交后,由于会触发 componentWillReceiveProps
88 | // 因此这里需要把该函数“清空”,避免浪费性能
89 | this.updateState = () => {}
90 |
91 | this.props[opt](this.state).then(({ id }) => {
92 | this.context.router.replace(`/msg/detail/${id}`)
93 | })
94 | }
95 |
96 | render () {
97 | // 使用 call/apply,让 tpl 中的上下文与当前一致
98 | // (最佳实践应该跟 mixin 一样,在构造函数中使用 bind 绑定)
99 | return tpl.call(this)
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/components/Msg/MsgForm/msg-form.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 无论有没有用到 React 这个变量都要导入
3 | * 因为这只是 JSX,等转为 JS 后
4 | * 您会看到到处都是 React.createElement
5 | */
6 | import React from 'react'
7 |
8 | // 由于 render 中用是 call/apply,因此这里不能使用箭头函数
9 | // (因为箭头函数没有自己的 this!无法通过 call/apply 改变)
10 | export default function () {
11 | return (
12 |
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/Msg/MsgList/DisplayControl.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Link } from 'react-router'
3 | import handleChange from 'MIXIN/handleChange'
4 |
5 | export default class DisplayControl extends Component {
6 | constructor (props) {
7 | super(props)
8 | this.state = { _quantity: props.quantity }
9 | this.handleChange = handleChange.bind(this) // mixin
10 | }
11 |
12 | /* 【拓展阅读】setState 的“异步”坑:https://zhuanlan.zhihu.com/p/20328570 */
13 | componentWillReceiveProps(nextProps) {
14 | if (this.state._quantity !== nextProps.quantity) {
15 | this.setState({ _quantity: nextProps.quantity })
16 | }
17 | }
18 |
19 | _changeQuantity () {
20 | this.props.changeQuantity(this.state._quantity)
21 | }
22 |
23 | render () {
24 | let { msgsLen, pageIdx, resetDisplayControl } = this.props
25 | return (
26 |
27 |
74 |
75 | )
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/components/Msg/MsgList/NoticeBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const NoticeBar = () => (
4 |
6 |
12 | 暂无更多信息
13 |
14 | )
15 |
16 | export default NoticeBar
17 |
--------------------------------------------------------------------------------
/src/components/Msg/MsgList/Pagination.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ExampleHoC from 'HOC/ExampleHoC'
3 |
4 | const Pagination = ({ msgsLen, pageIdx, quantity, goPrevPage, goNextPage }) => (
5 |
31 | )
32 |
33 | export default ExampleHoC(Pagination)
34 |
--------------------------------------------------------------------------------
/src/components/Msg/MsgList/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Link } from 'react-router'
3 | import Pagination from './Pagination'
4 | import NoticeBar from './NoticeBar'
5 | import DisplayControl from './DisplayControl'
6 | import OptBtnGroup from 'COMPONENT/Msg/OptBtnGroup'
7 | import dateTimeFormatter from 'UTIL/dateTimeFormatter'
8 |
9 | export default class MsgList extends Component {
10 | componentWillMount () {
11 | let { author } = this.props.location.query
12 | this.props.specifyAuthor(author)
13 | this.updateMsgList()
14 | }
15 | /**
16 | * 类似于 Vue 中的 route: { data: Function } 属性,监听路由变化加载数据
17 | * 【拓展阅读】无限循环坑 http://stackoverflow.com/questions/36189775
18 | */
19 | componentWillReceiveProps (nextProps) {
20 | // query string 变化
21 | if (nextProps.location.search !== this.props.location.search) {
22 | return this.props.specifyAuthor(nextProps.location.query.author)
23 | }
24 |
25 | const nextDisplayControl = nextProps.msg.displayControl
26 | if (
27 | JSON.stringify(nextDisplayControl) ===
28 | JSON.stringify(this.props.msg.displayControl)
29 | ) return
30 |
31 | this.updateMsgList(nextDisplayControl)
32 | }
33 |
34 | updateMsgList (displayControl = this.props.msg.displayControl) {
35 | let { pageIdx, quantity, authorSpecified: author } = displayControl
36 | this.props.fetchMsg({ pageIdx, quantity, author })
37 | }
38 |
39 | render () {
40 | let { msg: { msgs, displayControl }, userData,
41 | delMsg, goPrevPage, goNextPage,
42 | changeQuantity, resetDisplayControl } = this.props
43 |
44 | return (
45 |
46 |
51 |
52 | { !msgs.length &&
}
53 |
54 |
59 |
60 |
61 | { msgs.map(msg =>
62 | -
63 |
64 | { msg.title }
65 |
66 |
67 | { dateTimeFormatter(msg.time, 3) }
68 |
69 |
70 | by
71 |
72 | { msg.author }
73 |
74 |
75 |
79 |
82 | 查看详情
83 |
84 |
85 |
86 | )}
87 |
88 |
89 | )
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/components/Msg/OptBtnGroup.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import { Link } from 'react-router'
3 |
4 | /* 本组件全称 Operation Button Group,操作按钮组 */
5 | export default class OptBtnGroup extends Component {
6 | static contextTypes = {
7 | router: PropTypes.object.isRequired
8 | }
9 |
10 | delMsg () {
11 | if (!confirm('确认删除?')) return
12 |
13 | let { msgId, delMsg, parentName } = this.props
14 | delMsg(msgId)
15 |
16 | if (parentName === 'MsgDetail') {
17 | // 若是在详情页删除,显然应该跳转回列表页
18 | this.context.router.replace('/msg')
19 | }
20 | }
21 |
22 | render () {
23 | let { isAuthor, msgId, children } = this.props
24 |
25 | return ( // 请使用括号包含 JSX
26 |
29 | {/* 这里的作用有点类似于 Vue 的 slot
30 | MsgList 中,
包的是“详情”链接
31 | MsgDetail 中, 包的是“返回”按钮 */
32 | children }
33 |
34 | {/* 鉴于使用父标签包含后无法匹配 BootStrap 样式
35 | 因此下面只能分离判断 */}
36 | { isAuthor &&
37 |
40 | 修改
41 |
42 | }
43 | { isAuthor &&
44 | this.delMsg()}>
47 | 删除
48 |
49 | }
50 |
51 | )
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/Navbar/LoginForm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import handleChange from 'MIXIN/handleChange'
3 |
4 | export default class LoginForm extends Component {
5 | constructor (props) {
6 | super(props)
7 | // P.S: 仅能在构造函数中设置 state
8 | // 在其他地方绝不能使用 this.state.XXX = XXX
9 | // 只能使用 this.setState({ XXX: XXX })
10 | this.state = { username: '' }
11 |
12 | this.handleChange = handleChange.bind(this) // mixin
13 | }
14 |
15 | handleSubmit () {
16 | let username = this.state.username
17 | if (!username) return alert('用户名为空')
18 | this.props.login({ username })
19 | }
20 |
21 | render () {
22 | /* 由于 ES6 中 React 不会自动绑定this,直接 onSubmit={this.handleSubmit} 会报错
23 | 详情请参考 https://facebook.github.io/react/docs/reusable-components.html#no-autobinding */
24 | return (
25 |
53 | )
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/Navbar/LogoutDropdown.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 |
3 | export default class LogoutDropdown extends Component {
4 | static contextTypes = {
5 | router: PropTypes.object.isRequired
6 | }
7 |
8 | logout () {
9 | // 退出登录后直接返回首页
10 | this.props.logout()
11 | this.context.router.replace('/')
12 | }
13 |
14 | render () {
15 | return (
16 |
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/Navbar/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 | import { IndexLink, Link } from 'react-router'
4 | import LoginForm from './LoginForm'
5 | import LogoutDropdown from './LogoutDropdown'
6 |
7 | /* 导航栏全局显示,控制着用户的登录注销 */
8 |
9 | @connect( // 功能同 UTIL/createContainer
10 | ({ userData }) => ({ userData }),
11 | require('ACTION/user').default
12 | )
13 | export default class Navbar extends Component {
14 | componentWillMount () {
15 | console.info('[Navbar] 初始化:检查用户是否已经登录')
16 | console.info('[TIPS] 由于有Redux Logger,故之后就不手动打印动作了')
17 | this.props.checkLogin()
18 | }
19 |
20 | render () {
21 | let {
22 | userData, login, logout, // 通过 connect 获取
23 | location: { pathname } // 通过 App 传入
24 | } = this.props
25 |
26 | return (
27 |
28 |
29 |
69 |
70 |
71 | )
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/Redirect.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { withRouter } from 'react-router' // v2.4 新增的 HoC
3 | /**
4 | * 设计初衷:
5 | * replaceState 的 API 被废弃
6 | * 且要恢复原状很麻烦的情况下
7 | * 宁可重新进入该组件
8 | * 但 this.context.router.replace(当前路径) 是无效的
9 | * 因为 React 能判断出当前组件可复用
10 | * 因此只能跳转到别的路径之后再跳回来
11 | *
12 | * 使用方法:(当前路径为 /foo)
13 | * 在 JSX 中:重载本页
14 | * 在 JS 中:this.context.router.replace('/redirect?dest=/foo')
15 | */
16 | class Redirect extends Component {
17 | componentWillMount() {
18 | this.props.router.replace( // 可以直接通过 props 获取 router
19 | this.props.location.query.dest
20 | )
21 | }
22 |
23 | render () {
24 | // 非实体组件需显式返回 null
25 | return null
26 | }
27 | }
28 |
29 | export default withRouter(Redirect)
30 |
--------------------------------------------------------------------------------
/src/components/Todo/TodoInput.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import handleChange from 'MIXIN/handleChange'
3 |
4 | export default class TodoInput extends Component {
5 | constructor (props) {
6 | super(props)
7 | this.state = { inputVal: '' }
8 | this.handleChange = handleChange.bind(this)
9 | }
10 |
11 | handleSubmit () {
12 | let content = this.state.inputVal.trim()
13 | if (!content) return
14 |
15 | this.props.addTodo(content)
16 | this.setState({ inputVal: '' }) // 清空输入框
17 | }
18 |
19 | render () {
20 | return (
21 |
39 | )
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/Todo/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import TodoInput from './TodoInput'
3 | import dateTimeFormatter from 'UTIL/dateTimeFormatter'
4 |
5 | export default class Todo extends Component {
6 | delTodo (todoId) {
7 | if (!confirm('确认删除?')) return
8 | this.props.delTodo(todoId)
9 | }
10 |
11 | render () {
12 | let { todos, addTodo, toggleTodo } = this.props
13 | return (
14 |
15 |
16 | { todos.map(todo =>
17 | - toggleTodo(todo.id)}>
18 |
19 | { todo.content }
20 |
21 | this.delTodo(todo.id)}>
25 | ⊗
26 |
27 |
28 | { dateTimeFormatter(todo.createdAt) }
29 |
30 |
31 | )}
32 |
33 |
34 |
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/Welcome.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router'
3 | /**
4 | * 本组件为欢迎页(首页)
5 | * 由于几乎没有交互逻辑
6 | * 因此可以不使用类的写法
7 | *
8 | * 实际上,ES6 的类经由 Babel 转码后
9 | * 其实还是返回一个类似的函数
10 | */
11 | const Welcome = () => (
12 |
13 |
欢迎使用
React Demo
14 |
15 |
19 | 前往留言板 >
20 |
21 |
22 |
26 | 前往待办事项(新功能) >
27 |
28 |
29 |
30 | )
31 |
32 | export default Welcome
33 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Demo
6 |
7 |
8 | {% for (var css in o.htmlWebpackPlugin.files.css) { %}
9 |
10 | {% } %}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {% for (var chunk in o.htmlWebpackPlugin.files.chunks) { %}
19 |
20 | {% } %}
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/redux/actions/msg/displayControl.js:
--------------------------------------------------------------------------------
1 | import initState from 'STORE/initState'
2 | // ================================
3 | // Action Type
4 | // ================================
5 | const SPECIFY_AUTHOR = 'SPECIFY_AUTHOR'
6 | const PREV_PAGE = 'PREV_PAGE'
7 | const NEXT_PAGE = 'NEXT_PAGE'
8 | const CHANGE_QUANTITY = 'CHANGE_QUANTITY'
9 | const RESET_DISPLAY_CONTROL = 'RESET_DISPLAY_CONTROL'
10 |
11 | // ================================
12 | // Action Creator
13 | // ================================
14 | const specifyAuthor = (authorSpecified) => ({
15 | type: SPECIFY_AUTHOR,
16 | payload: authorSpecified
17 | })
18 |
19 | const goPrevPage = () => ({
20 | type: PREV_PAGE
21 | })
22 |
23 | const goNextPage = () => ({
24 | type: NEXT_PAGE
25 | })
26 |
27 | const changeQuantity = (quantity) => ({
28 | type: CHANGE_QUANTITY,
29 | payload: quantity
30 | })
31 |
32 | const resetDisplayControl = () => ({
33 | type: RESET_DISPLAY_CONTROL
34 | })
35 |
36 | /* default 导出所有 Action Creators */
37 | export default {
38 | specifyAuthor, goPrevPage, goNextPage,
39 | changeQuantity, resetDisplayControl
40 | }
41 |
42 | // ================================
43 | // Action handlers for Reducer
44 | // 本来更新 state 是 Reducer 的责任
45 | // 但要把 ActionType 导出又引入实在太麻烦
46 | // 且在 Reducer 中写 switch-case 实在太不优雅
47 | // 故在此直接给出处理逻辑
48 | // ================================
49 | export const ACTION_HANDLERS = {
50 | [SPECIFY_AUTHOR]: (displayControl, { payload: authorSpecified }) => (
51 | { ...displayControl, authorSpecified }
52 | ),
53 | [PREV_PAGE]: (displayControl, action) => {
54 | let pageIdx = displayControl.pageIdx
55 | pageIdx = pageIdx <= 1 ? 1 : pageIdx - 1
56 | return { ...displayControl, pageIdx }
57 | },
58 | [NEXT_PAGE]: (displayControl, action) => (
59 | { ...displayControl, pageIdx: displayControl.pageIdx + 1 }
60 | ),
61 | [CHANGE_QUANTITY]: (displayControl, { payload: quantity }) => {
62 | quantity = ~~quantity || 10
63 | return { ...displayControl, quantity }
64 | },
65 | [RESET_DISPLAY_CONTROL]: (displayControl, action) => (
66 | { ...initState.msg.displayControl }
67 | )
68 | }
69 |
--------------------------------------------------------------------------------
/src/redux/actions/msg/index.js:
--------------------------------------------------------------------------------
1 | import { default as msgActionCreators } from './msg'
2 | import { default as displayControlActionCreator } from './displayControl'
3 |
4 | export default {
5 | ...msgActionCreators,
6 | ...displayControlActionCreator
7 | }
8 |
--------------------------------------------------------------------------------
/src/redux/actions/msg/msg.js:
--------------------------------------------------------------------------------
1 | import msgService from 'SERVICE/msgService'
2 | // ================================
3 | // Action Type
4 | // ================================
5 | const FETCH_MSG = 'FETCH_MSG'
6 | const ADD_MSG = 'ADD_MSG'
7 | const MOD_MSG = 'MOD_MSG'
8 | const DEL_MSG = 'DEL_MSG'
9 |
10 | // ================================
11 | // Action Creator
12 | // ================================
13 | const fetchMsg = queryBody => dispatch =>
14 | msgService
15 | .fetch(queryBody)
16 | .then(msgs => dispatch({
17 | type: FETCH_MSG,
18 | payload: msgs
19 | }))
20 |
21 | const addMsg = msgBody => dispatch =>
22 | msgService
23 | .add(msgBody)
24 | .then(msg => {
25 | dispatch({
26 | type: ADD_MSG,
27 | payload: msg
28 | })
29 | return msg
30 | })
31 |
32 | const modMsg = msgBody => dispatch =>
33 | msgService
34 | .mod(msgBody)
35 | .then(msg => {
36 | dispatch({
37 | type: MOD_MSG,
38 | payload: msg
39 | })
40 | return msg // 便于链式调用
41 | })
42 |
43 | const delMsg = msgId => dispatch =>
44 | msgService
45 | .del(msgId)
46 | .then(() => dispatch({
47 | type: DEL_MSG,
48 | payload: msgId
49 | }))
50 |
51 | /* default 导出所有 Action Creators */
52 | export default {
53 | fetchMsg, addMsg, modMsg, delMsg
54 | }
55 |
56 | // ================================
57 | // Action handlers for Reducer
58 | // 本来更新 state 是 Reducer 的责任
59 | // 但要把 ActionType 导出又引入实在太麻烦
60 | // 且在 Reducer 中写 switch-case 实在太不优雅
61 | // 故在此直接给出处理逻辑
62 | // ================================
63 | export const ACTION_HANDLERS = {
64 | [FETCH_MSG]: (msgs, { payload }) => payload,
65 | [ADD_MSG]: (msgs, { payload }) => [ ...msgs, payload ],
66 | [MOD_MSG]: (msgs, { payload }) => msgs.map(
67 | msg => msg.id === payload.id ? payload : msg
68 | ),
69 | [DEL_MSG]: (msgs, { payload }) => msgs.filter(
70 | msg => msg.id !== payload // payload is msgId
71 | )
72 | }
73 |
--------------------------------------------------------------------------------
/src/redux/actions/todo.js:
--------------------------------------------------------------------------------
1 | // ================================
2 | // Action Type
3 | // ================================
4 | const ADD_TODO = 'ADD_TODO'
5 | const DEL_TODO = 'DEL_TODO'
6 | const TOGGLE_TODO = 'TOGGLE_TODO'
7 |
8 | // ================================
9 | // Action Creator
10 | // ================================
11 | const addTodo = (content) => ({
12 | type: ADD_TODO,
13 | payload: {
14 | id: setTimeout(() => {}), // 生成唯一 ID 的一种方式
15 | content,
16 | completed: false,
17 | createdAt: Date.now()
18 | }
19 | })
20 |
21 | const toggleTodo = (todoId) => ({
22 | type: TOGGLE_TODO,
23 | payload: todoId
24 | })
25 |
26 | const delTodo = (todoId) => ({
27 | type: DEL_TODO,
28 | payload: todoId
29 | })
30 |
31 | /* default 导出所有 Action Creators */
32 | export default {
33 | // 虽然是同步的函数,但请不要自行 bindActionCreators
34 | // 皆因调用 connect 后,react-redux 已经帮我们做了,见:
35 | // https://github.com/reactjs/react-redux/blob/master/src/utils/wrapActionCreators.js
36 | addTodo, toggleTodo, delTodo
37 | }
38 |
39 | // ================================
40 | // Action handlers for Reducer
41 | // 本来更新 state 是 Reducer 的责任
42 | // 但要把 ActionType 导出又引入实在太麻烦
43 | // 且在 Reducer 中写 switch-case 实在太不优雅
44 | // 故在此直接给出处理逻辑
45 | // ================================
46 | export const ACTION_HANDLERS = {
47 | [ADD_TODO]: (todos, { payload }) => [ ...todos, payload ],
48 | [TOGGLE_TODO]: (todos, { payload: todoId }) => todos.map(
49 | todo => todo.id === todoId ? { ...todo, completed: !todo.completed } : todo
50 | ),
51 | [DEL_TODO]: (todos, { payload: todoId }) => todos.filter(
52 | todo => todo.id !== todoId
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/src/redux/actions/user.js:
--------------------------------------------------------------------------------
1 | import userService from 'SERVICE/userService'
2 | // ================================
3 | // Action Type
4 | // ================================
5 | const LOG_IN = 'LOG_IN'
6 | const LOG_OUT = 'LOG_OUT'
7 |
8 | // ================================
9 | // Action Creator
10 | // ================================
11 | const loginDone = (userData) => ({
12 | type: LOG_IN,
13 | payload: userData
14 | })
15 |
16 | const login = (formData) => {
17 | return dispatch => {
18 | userService
19 | .login(formData)
20 | .then(
21 | re => dispatch(loginDone(re))
22 | )
23 | }
24 | }
25 |
26 | const checkLogin = () => {
27 | return dispatch => {
28 | userService
29 | .checkLogin()
30 | .then((re) => {
31 | if (!re) return
32 | dispatch(loginDone(re))
33 | })
34 | }
35 | }
36 |
37 | const logout = () => {
38 | return dispatch => {
39 | userService
40 | .logout()
41 | .then(() =>
42 | dispatch({
43 | type: LOG_OUT
44 | })
45 | )
46 | }
47 | }
48 | /* default 导出所有 Actions Creator */
49 | export default {
50 | login, checkLogin, logout
51 | }
52 |
53 | // ================================
54 | // Action handlers for Reducer
55 | // 本来更新 state 是 Reducer 的责任
56 | // 但要把 ActionType 导出又引入实在太麻烦
57 | // 且在 Reducer 中写 switch-case 实在太不优雅
58 | // 故在此直接给出处理逻辑
59 | // ================================
60 | export const ACTION_HANDLERS = {
61 | [LOG_IN]: (userData, { payload }) => payload, // payload is userData
62 | [LOG_OUT]: () => null
63 | }
64 |
--------------------------------------------------------------------------------
/src/redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import { routerReducer } from 'react-router-redux'
3 | import store from 'STORE'
4 | import userReducer from 'REDUCER/user'
5 |
6 | // ================================
7 | // 同步的 Reducers(即应用初始化所必需的)
8 | // ================================
9 | const syncReducers = {
10 | router: routerReducer,
11 | userData: userReducer
12 | }
13 |
14 | // ================================
15 | // 异步加载的 Reducers(Code Splitting 按需加载的)
16 | // ================================
17 | const asyncReducers = {}
18 |
19 | /**
20 | * @return {Function} rootReducer
21 | */
22 | export function createRootReducer() {
23 | return combineReducers({
24 | ...syncReducers,
25 | ...asyncReducers
26 | })
27 | }
28 |
29 | /**
30 | * 按需加载时,立即注入对应的 Reducer
31 | * @param {String} key
32 | * @param {Function} reducer
33 | */
34 | export function injectReducer(key, reducer) {
35 | asyncReducers[key] = reducer
36 | store.replaceReducer(createRootReducer()) // 替换当前的 rootReducer
37 | }
38 |
--------------------------------------------------------------------------------
/src/redux/reducers/msg/displayControl.js:
--------------------------------------------------------------------------------
1 | import createReducer from 'UTIL/createReducer'
2 | import { ACTION_HANDLERS } from 'ACTION/msg/displayControl'
3 | import initState from 'STORE/initState'
4 |
5 | export default createReducer(initState.msg.displayControl, ACTION_HANDLERS)
6 |
--------------------------------------------------------------------------------
/src/redux/reducers/msg/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import msgsReducer from './msgs'
3 | import displayControlReducer from './displayControl'
4 |
5 | export default combineReducers({
6 | msgs: msgsReducer,
7 | displayControl: displayControlReducer
8 | })
9 |
--------------------------------------------------------------------------------
/src/redux/reducers/msg/msgs.js:
--------------------------------------------------------------------------------
1 | import createReducer from 'UTIL/createReducer'
2 | import { ACTION_HANDLERS } from 'ACTION/msg/msg'
3 | import initState from 'STORE/initState'
4 |
5 | export default createReducer(initState.msg.msgs, ACTION_HANDLERS)
6 |
--------------------------------------------------------------------------------
/src/redux/reducers/todo.js:
--------------------------------------------------------------------------------
1 | import createReducer from 'UTIL/createReducer'
2 | import { ACTION_HANDLERS } from 'ACTION/todo'
3 | import initState from 'STORE/initState'
4 |
5 | export default createReducer(initState.todos, ACTION_HANDLERS)
6 |
--------------------------------------------------------------------------------
/src/redux/reducers/user.js:
--------------------------------------------------------------------------------
1 | import createReducer from 'UTIL/createReducer'
2 | import { ACTION_HANDLERS } from 'ACTION/user'
3 | import initState from 'STORE/initState'
4 |
5 | export default createReducer(initState.userData, ACTION_HANDLERS)
6 |
--------------------------------------------------------------------------------
/src/redux/store/enhancers.js:
--------------------------------------------------------------------------------
1 | // ======================================================
2 | // 配置 Store 增强器
3 | // ======================================================
4 | const enhancers = []
5 |
6 | if (__DEV__) {
7 | /** Redux DevTools **/
8 |
9 | /* 1. Chrome 插件 Redux DevTools(默认)
10 | P.S: 独立窗口可调用 window.devToolsExtension.open() */
11 | if (!__COMPONENT_DEVTOOLS__) {
12 | const devToolsExtension = window.devToolsExtension
13 | if (typeof devToolsExtension === 'function') {
14 | enhancers.push(devToolsExtension())
15 | }
16 | }
17 |
18 | /* 2. 内嵌在页面中的 Redux DevTools 组件 */
19 | if (__COMPONENT_DEVTOOLS__) {
20 | const DevTools = require('COMPONENT/DevTools').default
21 | enhancers.push(DevTools.instrument())
22 | }
23 | }
24 |
25 | export default enhancers
26 |
--------------------------------------------------------------------------------
/src/redux/store/index.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, compose, createStore } from 'redux'
2 | import { createRootReducer } from 'REDUCER'
3 | import middlewares from './middlewares'
4 | import enhancers from './enhancers'
5 | import syncHistoryWithStore from './syncHistoryWithStore'
6 |
7 | // ======================================================
8 | // 实例化 Store
9 | // ======================================================
10 | const store = createStore(
11 | createRootReducer(),
12 | window.__INITIAL_STATE__ || {}, // 前后端同构(服务端渲染)数据同步
13 | compose(
14 | applyMiddleware(...middlewares),
15 | ...enhancers
16 | )
17 | )
18 | export default store
19 |
20 | // ======================================================
21 | // 增强版 history
22 | // ======================================================
23 | export const history = syncHistoryWithStore(store)
24 |
--------------------------------------------------------------------------------
/src/redux/store/initState.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 本文件的作用就是直观呈现 整个应用状态结构树 及其 初始值
3 | */
4 | export default {
5 | /* 用户 session */
6 | userData: null,
7 |
8 | /* 留言板模块(按需加载) */
9 | msg: {
10 | msgs: [], // 当前显示的留言列表
11 | displayControl: { // 查询条件
12 | pageIdx: 1, // 默认是第 1 页
13 | quantity: 10, // 默认每页显示 10 条记录
14 | authorSpecified: '' // 是否有指定发布者
15 | }
16 | },
17 |
18 | /* 待办事项模块(按需加载) */
19 | todos: [
20 | // {
21 | // id: 123,
22 | // content: '待办事项1',
23 | // completed: false,
24 | // createdAt: 1473499991348
25 | // }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/src/redux/store/middlewares.js:
--------------------------------------------------------------------------------
1 | // ======================================================
2 | // 配置中间件
3 | // ======================================================
4 | import thunk from 'redux-thunk'
5 | import { historyMiddleware } from './syncHistoryWithStore'
6 |
7 | const middlewares = [thunk, historyMiddleware]
8 |
9 | if (__DEV__) {
10 | /** Redux Logger (P.S: 打印日志会造成轻微的卡顿) **/
11 | const createLogger = require('redux-logger')
12 | middlewares.push(createLogger())
13 | }
14 |
15 | export default middlewares
16 |
--------------------------------------------------------------------------------
/src/redux/store/syncHistoryWithStore.js:
--------------------------------------------------------------------------------
1 | // ========================================================
2 | // 同步 history 配置
3 | // ========================================================
4 | import { useRouterHistory } from 'react-router'
5 | import createHashHistory from 'history/lib/createHashHistory'
6 | import { routerMiddleware, syncHistoryWithStore } from 'react-router-redux'
7 |
8 | const browserHistory = useRouterHistory(createHashHistory)({
9 | basename: '', // 相当于 rootPath
10 | queryKey: false // 去除随机标识符
11 | })
12 |
13 | export const historyMiddleware = routerMiddleware(browserHistory)
14 |
15 | /**
16 | * @param {Store}
17 | * @return {History} 增强版 history
18 | */
19 | export default function (store) {
20 | return syncHistoryWithStore(
21 | browserHistory,
22 | store,
23 | { selectLocationState: (state) => state.router }
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | path: '/',
3 |
4 | component: require('COMPONENT/App').default,
5 |
6 | indexRoute: {
7 | component: require('COMPONENT/Welcome').default
8 | },
9 |
10 | childRoutes: [
11 | // 路由按模块组织分离,避免单文件代码量过大
12 | require('./msg').default,
13 | require('./todo').default,
14 |
15 | // 强制“刷新”页面的 hack
16 | { path: 'redirect', component: require('COMPONENT/Redirect').default },
17 |
18 | // 无路由匹配的情况一定要放到最后,否则会拦截所有路由
19 | { path: '*', component: require('COMPONENT/404').default }
20 | ]
21 | }
22 |
23 | /*
24 | 当前路由树如下
25 | ├ /
26 | |
27 | ├ /msg
28 | ├ /msg/add
29 | ├ /msg/detail/:msgId
30 | ├ /msg/modify/:msgId
31 | |
32 | ├ /todo
33 | |
34 | ├ /redirect
35 | */
36 |
--------------------------------------------------------------------------------
/src/routes/msg.js:
--------------------------------------------------------------------------------
1 | import { injectReducer } from 'REDUCER'
2 | import userAuth from 'UTIL/userAuth' // 用户访问拦截器
3 | import createContainer from 'UTIL/createContainer'
4 |
5 | const connectComponent = createContainer(
6 | ({ userData, msg }) => ({ userData, msg }), // mapStateToProps
7 | require('ACTION/msg').default // mapActionCreators
8 | )
9 |
10 | export default {
11 | path: 'msg',
12 |
13 | /* 布局基页 */
14 | getComponent (nextState, cb) {
15 | require.ensure([], (require) => {
16 | // 立即注入 Reducer
17 | injectReducer('msg', require('REDUCER/msg/').default)
18 |
19 | cb(null, require('VIEW/msg').default)
20 | }, 'msgView')
21 | },
22 |
23 | indexRoute: { // 对应 /msg
24 | getComponent (nextState, cb) {
25 | require.ensure([], (require) => {
26 | cb(null, connectComponent(require('COMPONENT/Msg/MsgList').default))
27 | }, 'msgList')
28 | }
29 | },
30 |
31 | childRoutes: [
32 | { // 对应 /msg/detail/:msgId
33 | path: 'detail/:msgId',
34 | getComponent (nextState, cb) {
35 | require.ensure([], (require) => {
36 | cb(null, connectComponent(require('COMPONENT/Msg/MsgDetail').default))
37 | }, 'msgDetail')
38 | }
39 | },
40 | { // 对应 /msg/add
41 | path: 'add',
42 | getComponent (nextState, cb) {
43 | require.ensure([], (require) => {
44 | cb(null, connectComponent(require('COMPONENT/Msg/MsgForm').default))
45 | }, 'msgForm')
46 | },
47 | onEnter: userAuth
48 | },
49 | { // 对应 /msg/:msgId
50 | path: 'modify/:msgId',
51 | getComponent (nextState, cb) {
52 | require.ensure([], (require) => {
53 | cb(null, connectComponent(require('COMPONENT/Msg/MsgForm').default))
54 | }, 'msgForm')
55 | },
56 | onEnter: userAuth
57 | }]
58 | }
59 |
--------------------------------------------------------------------------------
/src/routes/todo.js:
--------------------------------------------------------------------------------
1 | import { injectReducer } from 'REDUCER'
2 | import createContainer from 'UTIL/createContainer'
3 |
4 | export default {
5 | path: 'todo',
6 |
7 | /* 布局基页 */
8 | getComponent (nextState, cb) {
9 | require.ensure([], (require) => {
10 | cb(null, require('VIEW/todo').default)
11 | }, 'todoView')
12 | },
13 |
14 | indexRoute: {
15 | getComponent (nextState, cb) {
16 | require.ensure([], (require) => {
17 | // 注入 Reducer
18 | injectReducer('todos', require('REDUCER/todo').default)
19 |
20 | /* 组件连接 state */
21 | const TodoContainer = createContainer(
22 | ({ todos }) => ({ todos }), // mapStateToProps,
23 | require('ACTION/todo').default, // mapActionCreators,
24 | require('COMPONENT/Todo/').default // 木偶组件
25 | )
26 |
27 | cb(null, TodoContainer)
28 | }, 'todo')
29 | }
30 | }
31 | }
32 |
33 | /**
34 | * 【拓展】
35 | * 在 msg 的路由中,Reducer 是在 布局基页 中注入
36 | * 而在这里就可以在 indexRoute 中注入
37 | * 这主要取决于 Reducer 的作用范围
38 | */
39 |
--------------------------------------------------------------------------------
/src/services/msgService.js:
--------------------------------------------------------------------------------
1 | import xhr from './xhr/'
2 | /**
3 | * 对应后端的 /msg/* 所有 API
4 | */
5 | class MsgService {
6 | /**
7 | * 取 msg(命名为 fetch 而非 get 主要是因为是远程操作)
8 | * @param {String} options.author 作者名
9 | * @param {Number} options.pageIdx 目标页码(默认是第 1 页)
10 | * @param {Number} options.quantity 单页请求 msg 的数量(默认每页显示 10 条)
11 | * @param {Number} options.msgId
12 | * @return {Promise}
13 | */
14 | fetch ({ author = '', pageIdx = 1, quantity = 10, msgId } = {}) {
15 | let url = '/msg/'
16 |
17 | if (msgId) {
18 | url += msgId
19 | } else {
20 | url = `${url}?author=${author}&pageIdx=${pageIdx}&quantity=${quantity}`
21 | }
22 |
23 | return xhr({ url })
24 | }
25 |
26 | /**
27 | * 新增 msg
28 | * @param {Object} msgBody { title:{String}, content:{String} }
29 | * @return {Promise}
30 | */
31 | add (msgBody) {
32 | return xhr({
33 | method: 'post',
34 | url: '/msg',
35 | body: msgBody
36 | })
37 | }
38 |
39 | /**
40 | * 修改 msg
41 | * @param {Object} msgBody { title:{String}, content:{String} }
42 | * @return {Promise}
43 | */
44 | mod (msgBody) {
45 | let msgId = msgBody.id
46 | delete msgBody.msgId
47 |
48 | return xhr({
49 | method: 'put',
50 | url: `/msg/${msgId}`,
51 | body: msgBody
52 | })
53 | }
54 |
55 | /**
56 | * 删除 msg
57 | * @param {Number} msgId
58 | * @return {Promise}
59 | */
60 | del (msgId) {
61 | return xhr({
62 | method: 'delete',
63 | url: `/msg/${msgId}`
64 | })
65 | }
66 |
67 | }
68 |
69 | // 实例化后再导出
70 | export default new MsgService()
71 |
--------------------------------------------------------------------------------
/src/services/userService.js:
--------------------------------------------------------------------------------
1 | import xhr from './xhr/'
2 |
3 | /**
4 | * 对应后端涉及到用户认证的 API
5 | */
6 | class UserService {
7 |
8 | checkLogin () {
9 | return xhr({ url: '/user' })
10 | }
11 |
12 | /**
13 | * @param {Object} userData
14 | * @return {Promise}
15 | */
16 | login (userData) {
17 | return xhr({
18 | method: 'post',
19 | url: '/login',
20 | body: userData
21 | })
22 | }
23 |
24 | logout () {
25 | return xhr({ url: '/logout' })
26 | }
27 |
28 | }
29 |
30 | // 实例化后再导出
31 | export default new UserService()
32 |
--------------------------------------------------------------------------------
/src/services/xhr/config.js:
--------------------------------------------------------------------------------
1 | // 此处配置 根访问路径 以及 全局错误处理
2 | // 更多配置请根据业务逻辑自行实现
3 |
4 | // 后端 API 地址,最好以 http(s):// 打头
5 | export const rootPath = 'http://localhost:8989'
6 |
7 | export const errHandler = (e) => {
8 | alert('[ XHR:Failed ] 详情请看控制台')
9 | console.error(e)
10 | }
11 |
--------------------------------------------------------------------------------
/src/services/xhr/index.js:
--------------------------------------------------------------------------------
1 | import xhr from './jquery'
2 |
3 | /**
4 | * XHR 请求接口定义
5 | * @param {String} options.method 请求方法,默认为 get。支持 post、put、patch、delete 等
6 | * @param {String} options.url 请求路径,基于 rootPath 地址。例:欲请求 http://localhost:9000/user,仅需要填写 /user 即可
7 | * @param {Object} options.body 请求体。后端 Express 使用 req.body 获取该对象
8 | * @return {Promise}
9 | *
10 | * 使用例子 xhr({ method: 'post', url: 'XXX', body: {Object} })
11 | * 最简单的例子 xhr({ url: '/user' })
12 | */
13 | export default xhr
14 |
--------------------------------------------------------------------------------
/src/services/xhr/jquery.js:
--------------------------------------------------------------------------------
1 | import { rootPath, errHandler } from './config'
2 |
3 | const xhr = ({ url, body = null, method = 'get' }) => {
4 | const defer = $.Deferred()
5 |
6 | $.ajax({
7 | type: method,
8 | url: rootPath + url,
9 | data: body
10 | // xhrFields: { // 跨域允许带上 cookie
11 | // withCredentials: [域名]
12 | // },
13 | // crossDomain: true
14 | })
15 | .done(defer.resolve)
16 | .fail(errHandler)
17 |
18 | return defer.promise()
19 | }
20 |
21 | export default xhr
22 |
--------------------------------------------------------------------------------
/src/utils/HoC/ExampleHoC.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | /* 高阶组件 High Order Component 例子 */
4 | const ExampleHoC = WrappedComponent => class extends Component {
5 | componentWillMount() {
6 | console.info('[HoC] componentWillMount')
7 | }
8 |
9 | componentDidMount() {
10 | console.info('[HoC] componentDidMount')
11 | }
12 |
13 | render () {
14 | return
15 | }
16 | }
17 |
18 | export default ExampleHoC
19 |
20 | /**
21 | *【拓展】
22 | * React Router 2.4 新增 withRouter 这个 HoC
23 | * 这是除 context 外另一种获取 router 的推荐方式
24 | * https://github.com/reactjs/react-router/blob/master/upgrade-guides/v2.4.0.md
25 | *
26 | * 本项目中的 Redirect 组件用到了该高阶组件
27 | */
28 |
--------------------------------------------------------------------------------
/src/utils/createContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 |
3 | /**
4 | * 将木偶组件变成智能组件
5 | * @param {Function} mapStateToProps
6 | * @param {Object} mapActionCreators
7 | * @param {Component?}
8 | * @return {Connect : Container}
9 | */
10 | export default function createContainer(mapStateToProps, mapActionCreators, component) {
11 | const connectComponent = connect(mapStateToProps, mapActionCreators)
12 | return component ? connectComponent(component) : connectComponent
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/createReducer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {initState}
3 | * @param {Object} handlers
4 | * @return {Reducer}
5 | */
6 | export default function createReducer(initState, handlers) {
7 | return function reducer(state = initState, action) {
8 | const handler = handlers[action.type]
9 | return handler ? handler(state, action) : state
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/dateTimeFormatter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 个位数前加零
3 | * @param {Number} val
4 | * @return {String/Number}
5 | */
6 | const zerofill = val => val >= 10 ? val : '0' + val
7 | /* 相当于:
8 | var zerofill = function (val) {
9 | return val >=10 ? val : '0' + val
10 | };
11 | */
12 |
13 | /**
14 | * 格式化时间
15 | * @param {Number} time 时间戳
16 | * @param {Number} type 格式化类型
17 | * @return {String}
18 | */
19 | export default function dataTimeFormatter(time, type) {
20 | let date = new Date(time)
21 | let year = date.getFullYear()
22 | let month = date.getMonth() + 1
23 | let day = date.getDate()
24 | let hours = date.getHours()
25 | let minutes = date.getMinutes()
26 | let second = date.getSeconds()
27 |
28 | switch (type) {
29 | case 0: // 01-05
30 | return `${zerofill(month)}-${zerofill(day)}`
31 | case 1: // 11:12
32 | return `${zerofill(hours)}-${zerofill(minutes)}`
33 | case 2: // 2015-01-05
34 | return `${year}-${zerofill(month)}-${zerofill(day)}`
35 | case 3: // 2015-01-05 11:12
36 | return `${year}-${zerofill(month)}-${zerofill(day)} ${zerofill(hours)}:${zerofill(minutes)}`
37 | default: // 2015-01-05 11:12:13
38 | return `${year}-${zerofill(month)}-${zerofill(day)} ${zerofill(hours)}:${zerofill(minutes)}:${zerofill(second)}`
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/utils/mixins/handleChange.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React 不像 Angular/Vue 等具备双向绑定能力
3 | * 因此需要手动监听输入框 change 事件同步 state
4 | * 您可使用成熟的 redux-form(https://github.com/erikras/redux-form) 替代
5 | * @param {DOM Event} evt
6 | *
7 | * 使用例子:
8 | * 例子1:直接使用
9 | * // value 值同步 state
14 | *
15 | * 例子2:使用::绑定(例子1的语法糖)
16 | * onClick={this::handleChange}
17 | *
18 | * 例子3:在构造函数中声明(推荐,避免每次render都重复绑定)
19 | * constructor (props) {
20 | * super(props)
21 | * this.handleChange = handleChange.bind(this)
22 | * }
23 | * 之后就可以这样写了
24 | * onClick={this.handleChange}
25 | */
26 | export default function handleChange(evt) {
27 | this.setState({
28 | [evt.target.name]: evt.target.value.trim()
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/userAuth.js:
--------------------------------------------------------------------------------
1 | import store, { history } from 'STORE'
2 |
3 | /**
4 | * 用户访问权限拦截器
5 | * @export {Function} onEnter,详见以下文档:
6 | * https://github.com/reactjs/react-router/blob/master/docs/API.md#onEnter
7 | */
8 | export default function userAuth(nextState, replace, next) {
9 | let { userData } = store.getState()
10 | if (userData) return next()
11 |
12 | alert('请先登录后再访问')
13 | history.goBack()
14 | // next(replace('/loginPage')) # 举例:跳转到登录页的写法
15 | }
16 |
--------------------------------------------------------------------------------
/src/views/msg.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router'
3 |
4 | /* 留言板 布局基页 */
5 | const MsgView = ({ children, location }) => (
6 |
7 | { !location.pathname.startsWith('/msg/add') &&
8 |
11 | 添加消息
12 |
13 |
14 | }
15 |
16 | { children }
17 |
18 | )
19 |
20 | export default MsgView
21 |
--------------------------------------------------------------------------------
/src/views/todo.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | /* 待办事项 布局基页 */
4 | const TodoView = ({ children }) => (
5 |
6 |
8 |
14 | 该 Todo 示例无需 后端 RESTful API 支持
15 |
16 | { children }
17 |
18 | )
19 |
20 | export default TodoView
21 |
--------------------------------------------------------------------------------
/static/README.md:
--------------------------------------------------------------------------------
1 | 放置高度静态但不经由 Webpack 处理的文件
2 | e.g. jQuery 等
3 |
--------------------------------------------------------------------------------
/static/bootstrap/css/bootstrap-theme.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.5 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 | .btn-default,
7 | .btn-primary,
8 | .btn-success,
9 | .btn-info,
10 | .btn-warning,
11 | .btn-danger {
12 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
13 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
14 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
15 | }
16 | .btn-default:active,
17 | .btn-primary:active,
18 | .btn-success:active,
19 | .btn-info:active,
20 | .btn-warning:active,
21 | .btn-danger:active,
22 | .btn-default.active,
23 | .btn-primary.active,
24 | .btn-success.active,
25 | .btn-info.active,
26 | .btn-warning.active,
27 | .btn-danger.active {
28 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
29 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
30 | }
31 | .btn-default.disabled,
32 | .btn-primary.disabled,
33 | .btn-success.disabled,
34 | .btn-info.disabled,
35 | .btn-warning.disabled,
36 | .btn-danger.disabled,
37 | .btn-default[disabled],
38 | .btn-primary[disabled],
39 | .btn-success[disabled],
40 | .btn-info[disabled],
41 | .btn-warning[disabled],
42 | .btn-danger[disabled],
43 | fieldset[disabled] .btn-default,
44 | fieldset[disabled] .btn-primary,
45 | fieldset[disabled] .btn-success,
46 | fieldset[disabled] .btn-info,
47 | fieldset[disabled] .btn-warning,
48 | fieldset[disabled] .btn-danger {
49 | -webkit-box-shadow: none;
50 | box-shadow: none;
51 | }
52 | .btn-default .badge,
53 | .btn-primary .badge,
54 | .btn-success .badge,
55 | .btn-info .badge,
56 | .btn-warning .badge,
57 | .btn-danger .badge {
58 | text-shadow: none;
59 | }
60 | .btn:active,
61 | .btn.active {
62 | background-image: none;
63 | }
64 | .btn-default {
65 | text-shadow: 0 1px 0 #fff;
66 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
67 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
68 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
69 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
70 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
71 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
72 | background-repeat: repeat-x;
73 | border-color: #dbdbdb;
74 | border-color: #ccc;
75 | }
76 | .btn-default:hover,
77 | .btn-default:focus {
78 | background-color: #e0e0e0;
79 | background-position: 0 -15px;
80 | }
81 | .btn-default:active,
82 | .btn-default.active {
83 | background-color: #e0e0e0;
84 | border-color: #dbdbdb;
85 | }
86 | .btn-default.disabled,
87 | .btn-default[disabled],
88 | fieldset[disabled] .btn-default,
89 | .btn-default.disabled:hover,
90 | .btn-default[disabled]:hover,
91 | fieldset[disabled] .btn-default:hover,
92 | .btn-default.disabled:focus,
93 | .btn-default[disabled]:focus,
94 | fieldset[disabled] .btn-default:focus,
95 | .btn-default.disabled.focus,
96 | .btn-default[disabled].focus,
97 | fieldset[disabled] .btn-default.focus,
98 | .btn-default.disabled:active,
99 | .btn-default[disabled]:active,
100 | fieldset[disabled] .btn-default:active,
101 | .btn-default.disabled.active,
102 | .btn-default[disabled].active,
103 | fieldset[disabled] .btn-default.active {
104 | background-color: #e0e0e0;
105 | background-image: none;
106 | }
107 | .btn-primary {
108 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
109 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
110 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
111 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
112 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
113 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
114 | background-repeat: repeat-x;
115 | border-color: #245580;
116 | }
117 | .btn-primary:hover,
118 | .btn-primary:focus {
119 | background-color: #265a88;
120 | background-position: 0 -15px;
121 | }
122 | .btn-primary:active,
123 | .btn-primary.active {
124 | background-color: #265a88;
125 | border-color: #245580;
126 | }
127 | .btn-primary.disabled,
128 | .btn-primary[disabled],
129 | fieldset[disabled] .btn-primary,
130 | .btn-primary.disabled:hover,
131 | .btn-primary[disabled]:hover,
132 | fieldset[disabled] .btn-primary:hover,
133 | .btn-primary.disabled:focus,
134 | .btn-primary[disabled]:focus,
135 | fieldset[disabled] .btn-primary:focus,
136 | .btn-primary.disabled.focus,
137 | .btn-primary[disabled].focus,
138 | fieldset[disabled] .btn-primary.focus,
139 | .btn-primary.disabled:active,
140 | .btn-primary[disabled]:active,
141 | fieldset[disabled] .btn-primary:active,
142 | .btn-primary.disabled.active,
143 | .btn-primary[disabled].active,
144 | fieldset[disabled] .btn-primary.active {
145 | background-color: #265a88;
146 | background-image: none;
147 | }
148 | .btn-success {
149 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
150 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
151 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
152 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
153 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
154 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
155 | background-repeat: repeat-x;
156 | border-color: #3e8f3e;
157 | }
158 | .btn-success:hover,
159 | .btn-success:focus {
160 | background-color: #419641;
161 | background-position: 0 -15px;
162 | }
163 | .btn-success:active,
164 | .btn-success.active {
165 | background-color: #419641;
166 | border-color: #3e8f3e;
167 | }
168 | .btn-success.disabled,
169 | .btn-success[disabled],
170 | fieldset[disabled] .btn-success,
171 | .btn-success.disabled:hover,
172 | .btn-success[disabled]:hover,
173 | fieldset[disabled] .btn-success:hover,
174 | .btn-success.disabled:focus,
175 | .btn-success[disabled]:focus,
176 | fieldset[disabled] .btn-success:focus,
177 | .btn-success.disabled.focus,
178 | .btn-success[disabled].focus,
179 | fieldset[disabled] .btn-success.focus,
180 | .btn-success.disabled:active,
181 | .btn-success[disabled]:active,
182 | fieldset[disabled] .btn-success:active,
183 | .btn-success.disabled.active,
184 | .btn-success[disabled].active,
185 | fieldset[disabled] .btn-success.active {
186 | background-color: #419641;
187 | background-image: none;
188 | }
189 | .btn-info {
190 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
191 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
192 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
193 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
194 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
195 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
196 | background-repeat: repeat-x;
197 | border-color: #28a4c9;
198 | }
199 | .btn-info:hover,
200 | .btn-info:focus {
201 | background-color: #2aabd2;
202 | background-position: 0 -15px;
203 | }
204 | .btn-info:active,
205 | .btn-info.active {
206 | background-color: #2aabd2;
207 | border-color: #28a4c9;
208 | }
209 | .btn-info.disabled,
210 | .btn-info[disabled],
211 | fieldset[disabled] .btn-info,
212 | .btn-info.disabled:hover,
213 | .btn-info[disabled]:hover,
214 | fieldset[disabled] .btn-info:hover,
215 | .btn-info.disabled:focus,
216 | .btn-info[disabled]:focus,
217 | fieldset[disabled] .btn-info:focus,
218 | .btn-info.disabled.focus,
219 | .btn-info[disabled].focus,
220 | fieldset[disabled] .btn-info.focus,
221 | .btn-info.disabled:active,
222 | .btn-info[disabled]:active,
223 | fieldset[disabled] .btn-info:active,
224 | .btn-info.disabled.active,
225 | .btn-info[disabled].active,
226 | fieldset[disabled] .btn-info.active {
227 | background-color: #2aabd2;
228 | background-image: none;
229 | }
230 | .btn-warning {
231 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
232 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
233 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
234 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
235 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
236 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
237 | background-repeat: repeat-x;
238 | border-color: #e38d13;
239 | }
240 | .btn-warning:hover,
241 | .btn-warning:focus {
242 | background-color: #eb9316;
243 | background-position: 0 -15px;
244 | }
245 | .btn-warning:active,
246 | .btn-warning.active {
247 | background-color: #eb9316;
248 | border-color: #e38d13;
249 | }
250 | .btn-warning.disabled,
251 | .btn-warning[disabled],
252 | fieldset[disabled] .btn-warning,
253 | .btn-warning.disabled:hover,
254 | .btn-warning[disabled]:hover,
255 | fieldset[disabled] .btn-warning:hover,
256 | .btn-warning.disabled:focus,
257 | .btn-warning[disabled]:focus,
258 | fieldset[disabled] .btn-warning:focus,
259 | .btn-warning.disabled.focus,
260 | .btn-warning[disabled].focus,
261 | fieldset[disabled] .btn-warning.focus,
262 | .btn-warning.disabled:active,
263 | .btn-warning[disabled]:active,
264 | fieldset[disabled] .btn-warning:active,
265 | .btn-warning.disabled.active,
266 | .btn-warning[disabled].active,
267 | fieldset[disabled] .btn-warning.active {
268 | background-color: #eb9316;
269 | background-image: none;
270 | }
271 | .btn-danger {
272 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
273 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
274 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
275 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
276 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
277 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
278 | background-repeat: repeat-x;
279 | border-color: #b92c28;
280 | }
281 | .btn-danger:hover,
282 | .btn-danger:focus {
283 | background-color: #c12e2a;
284 | background-position: 0 -15px;
285 | }
286 | .btn-danger:active,
287 | .btn-danger.active {
288 | background-color: #c12e2a;
289 | border-color: #b92c28;
290 | }
291 | .btn-danger.disabled,
292 | .btn-danger[disabled],
293 | fieldset[disabled] .btn-danger,
294 | .btn-danger.disabled:hover,
295 | .btn-danger[disabled]:hover,
296 | fieldset[disabled] .btn-danger:hover,
297 | .btn-danger.disabled:focus,
298 | .btn-danger[disabled]:focus,
299 | fieldset[disabled] .btn-danger:focus,
300 | .btn-danger.disabled.focus,
301 | .btn-danger[disabled].focus,
302 | fieldset[disabled] .btn-danger.focus,
303 | .btn-danger.disabled:active,
304 | .btn-danger[disabled]:active,
305 | fieldset[disabled] .btn-danger:active,
306 | .btn-danger.disabled.active,
307 | .btn-danger[disabled].active,
308 | fieldset[disabled] .btn-danger.active {
309 | background-color: #c12e2a;
310 | background-image: none;
311 | }
312 | .thumbnail,
313 | .img-thumbnail {
314 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
315 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
316 | }
317 | .dropdown-menu > li > a:hover,
318 | .dropdown-menu > li > a:focus {
319 | background-color: #e8e8e8;
320 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
321 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
322 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
323 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
324 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
325 | background-repeat: repeat-x;
326 | }
327 | .dropdown-menu > .active > a,
328 | .dropdown-menu > .active > a:hover,
329 | .dropdown-menu > .active > a:focus {
330 | background-color: #2e6da4;
331 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
332 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
333 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
334 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
336 | background-repeat: repeat-x;
337 | }
338 | .navbar-default {
339 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
340 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
341 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
342 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
343 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
344 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
345 | background-repeat: repeat-x;
346 | border-radius: 4px;
347 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
348 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
349 | }
350 | .navbar-default .navbar-nav > .open > a,
351 | .navbar-default .navbar-nav > .active > a {
352 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
353 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
354 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
355 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
356 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
357 | background-repeat: repeat-x;
358 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
359 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
360 | }
361 | .navbar-brand,
362 | .navbar-nav > li > a {
363 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
364 | }
365 | .navbar-inverse {
366 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
367 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
368 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
369 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
370 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
371 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
372 | background-repeat: repeat-x;
373 | border-radius: 4px;
374 | }
375 | .navbar-inverse .navbar-nav > .open > a,
376 | .navbar-inverse .navbar-nav > .active > a {
377 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
378 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
379 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
380 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
381 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
382 | background-repeat: repeat-x;
383 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
384 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
385 | }
386 | .navbar-inverse .navbar-brand,
387 | .navbar-inverse .navbar-nav > li > a {
388 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
389 | }
390 | .navbar-static-top,
391 | .navbar-fixed-top,
392 | .navbar-fixed-bottom {
393 | border-radius: 0;
394 | }
395 | @media (max-width: 767px) {
396 | .navbar .navbar-nav .open .dropdown-menu > .active > a,
397 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
398 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
399 | color: #fff;
400 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
401 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
403 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
405 | background-repeat: repeat-x;
406 | }
407 | }
408 | .alert {
409 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
410 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
411 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
412 | }
413 | .alert-success {
414 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
415 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
416 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
417 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
418 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
419 | background-repeat: repeat-x;
420 | border-color: #b2dba1;
421 | }
422 | .alert-info {
423 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
424 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
425 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
426 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
427 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
428 | background-repeat: repeat-x;
429 | border-color: #9acfea;
430 | }
431 | .alert-warning {
432 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
433 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
434 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
435 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
436 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
437 | background-repeat: repeat-x;
438 | border-color: #f5e79e;
439 | }
440 | .alert-danger {
441 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
442 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
443 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
444 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
445 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
446 | background-repeat: repeat-x;
447 | border-color: #dca7a7;
448 | }
449 | .progress {
450 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
451 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
452 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
453 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
454 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
455 | background-repeat: repeat-x;
456 | }
457 | .progress-bar {
458 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
459 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
460 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
461 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
462 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
463 | background-repeat: repeat-x;
464 | }
465 | .progress-bar-success {
466 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
467 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
468 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
469 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
470 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
471 | background-repeat: repeat-x;
472 | }
473 | .progress-bar-info {
474 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
475 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
476 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
477 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
478 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
479 | background-repeat: repeat-x;
480 | }
481 | .progress-bar-warning {
482 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
483 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
484 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
485 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
486 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
487 | background-repeat: repeat-x;
488 | }
489 | .progress-bar-danger {
490 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
491 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
492 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
493 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
494 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
495 | background-repeat: repeat-x;
496 | }
497 | .progress-bar-striped {
498 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
499 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
500 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
501 | }
502 | .list-group {
503 | border-radius: 4px;
504 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
505 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
506 | }
507 | .list-group-item.active,
508 | .list-group-item.active:hover,
509 | .list-group-item.active:focus {
510 | text-shadow: 0 -1px 0 #286090;
511 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
512 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
513 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
514 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
515 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
516 | background-repeat: repeat-x;
517 | border-color: #2b669a;
518 | }
519 | .list-group-item.active .badge,
520 | .list-group-item.active:hover .badge,
521 | .list-group-item.active:focus .badge {
522 | text-shadow: none;
523 | }
524 | .panel {
525 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
526 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
527 | }
528 | .panel-default > .panel-heading {
529 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
530 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
531 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
532 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
533 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
534 | background-repeat: repeat-x;
535 | }
536 | .panel-primary > .panel-heading {
537 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
538 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
539 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
540 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
541 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
542 | background-repeat: repeat-x;
543 | }
544 | .panel-success > .panel-heading {
545 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
546 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
547 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
548 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
549 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
550 | background-repeat: repeat-x;
551 | }
552 | .panel-info > .panel-heading {
553 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
554 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
555 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
556 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
557 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
558 | background-repeat: repeat-x;
559 | }
560 | .panel-warning > .panel-heading {
561 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
562 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
563 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
564 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
565 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
566 | background-repeat: repeat-x;
567 | }
568 | .panel-danger > .panel-heading {
569 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
570 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
571 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
572 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
573 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
574 | background-repeat: repeat-x;
575 | }
576 | .well {
577 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
578 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
579 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
580 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
581 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
582 | background-repeat: repeat-x;
583 | border-color: #dcdcdc;
584 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
585 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
586 | }
587 | /*# sourceMappingURL=bootstrap-theme.css.map */
588 |
--------------------------------------------------------------------------------
/static/bootstrap/css/bootstrap-theme.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.5 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}
--------------------------------------------------------------------------------
/static/bootstrap/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenberkeley/react-demo/01d0dcd04c1dcdfc36dc8cbb6f02285206a3f273/static/bootstrap/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/static/bootstrap/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenberkeley/react-demo/01d0dcd04c1dcdfc36dc8cbb6f02285206a3f273/static/bootstrap/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/static/bootstrap/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenberkeley/react-demo/01d0dcd04c1dcdfc36dc8cbb6f02285206a3f273/static/bootstrap/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/static/bootstrap/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenberkeley/react-demo/01d0dcd04c1dcdfc36dc8cbb6f02285206a3f273/static/bootstrap/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/static/bootstrap/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.5 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under the MIT license
5 | */
6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
--------------------------------------------------------------------------------
/static/bootstrap/js/npm.js:
--------------------------------------------------------------------------------
1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
2 | require('../../js/transition.js')
3 | require('../../js/alert.js')
4 | require('../../js/button.js')
5 | require('../../js/carousel.js')
6 | require('../../js/collapse.js')
7 | require('../../js/dropdown.js')
8 | require('../../js/modal.js')
9 | require('../../js/tooltip.js')
10 | require('../../js/popover.js')
11 | require('../../js/scrollspy.js')
12 | require('../../js/tab.js')
13 | require('../../js/affix.js')
--------------------------------------------------------------------------------