├── src ├── assets │ ├── icons │ │ ├── 404.png │ │ ├── top.png │ │ ├── arrow.png │ │ ├── exit.png │ │ ├── gplus.png │ │ ├── weibo.png │ │ ├── douban.png │ │ ├── facebook.png │ │ ├── search.png │ │ ├── setting.png │ │ ├── telegram.png │ │ ├── twitter.png │ │ └── search@x1.png │ ├── images │ │ ├── logo@1x.png │ │ └── logo@2x.png │ ├── favicon │ │ ├── favicon.ico │ │ ├── apple-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── ms-icon-70x70.png │ │ ├── apple-icon-57x57.png │ │ ├── apple-icon-60x60.png │ │ ├── apple-icon-72x72.png │ │ ├── apple-icon-76x76.png │ │ ├── ms-icon-144x144.png │ │ ├── ms-icon-150x150.png │ │ ├── ms-icon-310x310.png │ │ ├── android-icon-36x36.png │ │ ├── android-icon-48x48.png │ │ ├── android-icon-72x72.png │ │ ├── android-icon-96x96.png │ │ ├── apple-icon-114x114.png │ │ ├── apple-icon-120x120.png │ │ ├── apple-icon-144x144.png │ │ ├── apple-icon-152x152.png │ │ ├── apple-icon-180x180.png │ │ ├── android-icon-144x144.png │ │ ├── android-icon-192x192.png │ │ ├── apple-icon-precomposed.png │ │ ├── browserconfig.xml │ │ └── manifest.json │ └── css │ │ ├── entry.css │ │ ├── filter.css │ │ ├── style.css │ │ └── search.css ├── module │ ├── controlbar.jsx │ ├── version.js │ ├── entry.jsx │ ├── filter.jsx │ └── search.jsx ├── index.jsx ├── vender │ ├── mduikit │ │ ├── waves.js │ │ ├── dialog.jsx │ │ ├── switch.jsx │ │ ├── button.jsx │ │ ├── tooltip.jsx │ │ ├── tabs.jsx │ │ ├── textfield.jsx │ │ ├── fab.jsx │ │ ├── sidebar.jsx │ │ ├── selectfield.jsx │ │ └── list.jsx │ ├── waves │ │ └── waves.min.css │ ├── notify │ │ ├── notify.css │ │ └── notify.js │ └── pangu.min.js └── index.html ├── .gitignore ├── LICENSE ├── package.json ├── npmw.cmd ├── README.md └── webpack.config.js /src/assets/icons/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/404.png -------------------------------------------------------------------------------- /src/assets/icons/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/top.png -------------------------------------------------------------------------------- /src/assets/icons/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/arrow.png -------------------------------------------------------------------------------- /src/assets/icons/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/exit.png -------------------------------------------------------------------------------- /src/assets/icons/gplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/gplus.png -------------------------------------------------------------------------------- /src/assets/icons/weibo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/weibo.png -------------------------------------------------------------------------------- /src/assets/icons/douban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/douban.png -------------------------------------------------------------------------------- /src/assets/icons/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/facebook.png -------------------------------------------------------------------------------- /src/assets/icons/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/search.png -------------------------------------------------------------------------------- /src/assets/icons/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/setting.png -------------------------------------------------------------------------------- /src/assets/icons/telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/telegram.png -------------------------------------------------------------------------------- /src/assets/icons/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/twitter.png -------------------------------------------------------------------------------- /src/assets/images/logo@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/images/logo@1x.png -------------------------------------------------------------------------------- /src/assets/images/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/images/logo@2x.png -------------------------------------------------------------------------------- /src/assets/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/favicon.ico -------------------------------------------------------------------------------- /src/assets/icons/search@x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/icons/search@x1.png -------------------------------------------------------------------------------- /src/assets/favicon/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/apple-icon.png -------------------------------------------------------------------------------- /src/assets/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /src/assets/favicon/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/ms-icon-70x70.png -------------------------------------------------------------------------------- /src/assets/favicon/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/apple-icon-57x57.png -------------------------------------------------------------------------------- /src/assets/favicon/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/apple-icon-60x60.png -------------------------------------------------------------------------------- /src/assets/favicon/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/apple-icon-72x72.png -------------------------------------------------------------------------------- /src/assets/favicon/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/apple-icon-76x76.png -------------------------------------------------------------------------------- /src/assets/favicon/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/ms-icon-144x144.png -------------------------------------------------------------------------------- /src/assets/favicon/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/ms-icon-150x150.png -------------------------------------------------------------------------------- /src/assets/favicon/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/ms-icon-310x310.png -------------------------------------------------------------------------------- /src/assets/favicon/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/android-icon-36x36.png -------------------------------------------------------------------------------- /src/assets/favicon/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/android-icon-48x48.png -------------------------------------------------------------------------------- /src/assets/favicon/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/android-icon-72x72.png -------------------------------------------------------------------------------- /src/assets/favicon/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/android-icon-96x96.png -------------------------------------------------------------------------------- /src/assets/favicon/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/apple-icon-114x114.png -------------------------------------------------------------------------------- /src/assets/favicon/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/apple-icon-120x120.png -------------------------------------------------------------------------------- /src/assets/favicon/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/apple-icon-144x144.png -------------------------------------------------------------------------------- /src/assets/favicon/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/apple-icon-152x152.png -------------------------------------------------------------------------------- /src/assets/favicon/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/apple-icon-180x180.png -------------------------------------------------------------------------------- /src/assets/favicon/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/android-icon-144x144.png -------------------------------------------------------------------------------- /src/assets/favicon/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/android-icon-192x192.png -------------------------------------------------------------------------------- /src/assets/favicon/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kenshin/sov2ex/HEAD/src/assets/favicon/apple-icon-precomposed.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #ignore node_modules 2 | node_modules/** 3 | src/bundle/** 4 | publish/** 5 | bynil/** 6 | 7 | # for mac 8 | **/.DS_Store 9 | -------------------------------------------------------------------------------- /src/module/controlbar.jsx: -------------------------------------------------------------------------------- 1 | console.log( "==== sov2ex module: Controlbar ====" ) 2 | 3 | export default class Controlbar extends React.Component{ 4 | 5 | render() { 6 | return ( 7 |
Controlbar
8 | ) 9 | } 10 | } -------------------------------------------------------------------------------- /src/assets/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | console.log( "==== sov2ex Index ====" ) 2 | 3 | import './assets/css/style.css'; 4 | import './vender/notify/notify.css'; 5 | 6 | import Entry from 'entry'; 7 | import Search from 'search'; 8 | import Controlbar from 'controlbar'; 9 | import * as vers from 'version'; 10 | 11 | import * as waves from 'waves'; 12 | 13 | vers.Init(); 14 | waves.Render({ root: "body" }); 15 | ReactDOM.render( 16 | location.search.startsWith( "?q=" ) ? : , 17 | $( ".main" )[0] 18 | ); -------------------------------------------------------------------------------- /src/module/version.js: -------------------------------------------------------------------------------- 1 | console.log( "==== sov2ex module: Version ====" ) 2 | 3 | const version = "1.0.1", 4 | cur_ver = localStorage["version"], 5 | details = { 6 | "1.0.0": `一个便捷的 v2ex 站内搜索引擎,详细请看 功能介绍`, 7 | "1.0.1": `修复了字重、配色等一些细节的问题,详细请看 更新日志`, 8 | } 9 | 10 | function Init() { 11 | if ( !cur_ver ) { 12 | localStorage[ "version" ] = version; 13 | new Notify().Render( "欢迎使用", details["1.0.0"] ); 14 | } else if ( cur_ver != version ) { 15 | localStorage[ "version" ] = version; 16 | new Notify().Render( `${version} 升级提示`, details[version] ); 17 | } 18 | } 19 | 20 | export { 21 | Init, 22 | } -------------------------------------------------------------------------------- /src/assets/favicon/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kenshin Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/vender/mduikit/waves.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Material Design: Waves 3 | * 4 | * @version : 0.0.1 5 | * @update : 2017/04/19 6 | * @homepage: https://github.com/kenshin/react-md-ui 7 | * @license : MIT https://github.com/kenshin/react-md/blob/master/LICENSE 8 | * @author : Kenshin Wang 9 | * @modules : http://fian.my.id/Waves 10 | * 11 | * @copyright 2017 12 | */ 13 | 14 | console.log( "==== simpread component: Waves ====" ) 15 | 16 | import '../waves/waves.min.css'; 17 | import Waves from '../waves/waves.js'; 18 | 19 | const wavesopts = { 20 | root : undefined, 21 | duration : 500, 22 | delay : 200, 23 | }; 24 | 25 | /** 26 | * Waves 27 | * 28 | * External library: 29 | * - http://fian.my.id/Waves/ 30 | * 31 | * @param {object} option object 32 | */ 33 | export function Render( options ) { 34 | if ( options && options.root ) { 35 | const ops = { ...wavesopts, ...options }; 36 | ops.root = $( ops.root )[0]; 37 | Waves.init( ops ); 38 | } else { 39 | console.error( "options param error" ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sov2ex_project_workflow", 3 | "version": "1.0.0", 4 | "description": "sov2ex develop/deploy", 5 | "author": "Kenshin Wang ", 6 | "license": "MIT", 7 | "homepage": "https://www.sov2ex.com/", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/kenshin/sov2ex.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/bynil/sov2ex/issues", 14 | "email": "kenshin@ksria.com" 15 | }, 16 | "engines": { 17 | "node": ">= 0.10.0" 18 | }, 19 | "scripts": { 20 | "develop": "NODE_ENV=development webpack-dev-server --hot --progress --colors --devtool=source-map", 21 | "publish": "NODE_ENV=production webpack -p --progress --colors" 22 | }, 23 | "devDependencies": { 24 | "autoprefixer": "^6.7.7", 25 | "babel-core": "^6.21.0", 26 | "babel-loader": "^6.2.10", 27 | "babel-polyfill": "^6.20.0", 28 | "babel-preset-es2015": "^6.18.0", 29 | "babel-preset-react": "^6.16.0", 30 | "babel-preset-stage-0": "^6.16.0", 31 | "css-loader": "^0.26.1", 32 | "expose-loader": "^0.7.1", 33 | "extract-text-webpack-plugin": "^1.0.1", 34 | "file-loader": "^0.9.0", 35 | "html-webpack-plugin": "^2.30.1", 36 | "import-postcss": "^8.0.2", 37 | "open-browser-webpack-plugin": "0.0.5", 38 | "postcss-cssnext": "^2.10.0", 39 | "postcss-loader": "^1.3.3", 40 | "react": "^0.14.8", 41 | "react-dom": "^0.14.8", 42 | "style-loader": "^0.13.1", 43 | "url-loader": "^0.5.7", 44 | "webpack": "^1.14.0", 45 | "webpack-dev-server": "^1.16.2" 46 | }, 47 | "dependencies": { 48 | "clean-webpack-plugin": "^0.1.14", 49 | "copy-webpack-plugin": "^4.0.1" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /npmw.cmd: -------------------------------------------------------------------------------- 1 | ::=========================================================== 2 | :: sov2ex : sov2ex development/publish environment 3 | :: HOST : https://github.com/kenshin/sov2ex 4 | :: Author : Kenshin 5 | :: Version : 0.0.1 6 | ::=========================================================== 7 | 8 | @echo off 9 | 10 | ::=========================================================== 11 | :: Initialize 12 | ::=========================================================== 13 | if "%1" == "run" ( 14 | if "%2" == "develop" ( 15 | goto develop 16 | ) else if "%2" == "publish" ( 17 | goto publish 18 | ) else ( 19 | @echo npmw noly support command: `npmw run develop` and `npmw run publish`. 20 | goto quit 21 | ) 22 | ) else ( 23 | @echo npmw noly support command: `npmw run develop` and `npmw run publish`. 24 | goto quit 25 | ) 26 | 27 | ::=========================================================== 28 | :: develop : Development environment 29 | ::=========================================================== 30 | :develop 31 | @echo webpack-dev-server --hot --progress --colors --devtool=source-map 32 | set NODE_ENV=development 33 | webpack-dev-server --hot --progress --colors --devtool=source-map 34 | goto quit 35 | 36 | ::=========================================================== 37 | :: publish : Production environment 38 | ::=========================================================== 39 | :publish 40 | @echo webpack -p --progress --colors 41 | set NODE_ENV=production 42 | webpack -p --progress --colors 43 | goto quit 44 | 45 | ::=========================================================== 46 | :: quit : Quit batch script. 47 | ::=========================================================== 48 | :quit 49 | @echo npmw batch quit. 50 | exit /b 0 51 | -------------------------------------------------------------------------------- /src/assets/css/entry.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Entry page 3 | */ 4 | 5 | .entry { 6 | display: flex; 7 | flex-direction: column; 8 | 9 | width: 100%; 10 | height: 100%; 11 | } 12 | 13 | .entry .logo { 14 | margin-top: 200px; 15 | margin-bottom: 50px; 16 | text-align: center; 17 | } 18 | 19 | .entry .search { 20 | position: relative; 21 | width: 400px!important; 22 | } 23 | 24 | .entry .searchbar { 25 | display: flex; 26 | justify-content: center; 27 | 28 | margin-bottom: 50px; 29 | } 30 | 31 | .entry .searchbar .bar { 32 | display: block; 33 | position: absolute; 34 | 35 | right: 5px; 36 | bottom: 15px; 37 | 38 | width: 20px; 39 | height: 20px; 40 | 41 | background-image: url( '../icons/search.png' ); 42 | background-position: center; 43 | background-repeat: no-repeat; 44 | 45 | cursor: pointer; 46 | } 47 | 48 | .entry .searchbar .arrow { 49 | display: block; 50 | position: absolute; 51 | 52 | right: 35px; 53 | bottom: 15px; 54 | 55 | width: 20px; 56 | height: 20px; 57 | 58 | background-image: url( '../icons/arrow.png' ); 59 | background-position: center; 60 | background-repeat: no-repeat; 61 | 62 | cursor: pointer; 63 | } 64 | 65 | .entry .desc { 66 | color: color( var(--text-color) alpha(-20%)); 67 | font-size: .9rem; 68 | text-align: center; 69 | } 70 | 71 | .entry .desc a { 72 | color: var(--primary-color); 73 | text-decoration: none; 74 | } 75 | 76 | .entry .searchbar text-field text-field-state { 77 | border-top: none var(--primary-color) !important; 78 | border-left: none var(--primary-color) !important; 79 | border-right: none var(--primary-color) !important; 80 | border-bottom: 2px solid var(--primary-color) !important; 81 | } 82 | -------------------------------------------------------------------------------- /src/assets/css/filter.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Filter 3 | */ 4 | 5 | .filtergp { 6 | position: absolute; 7 | 8 | top: 40px; 9 | left: 0; 10 | right: 0; 11 | 12 | padding: 5px; 13 | 14 | background-color: var(--search-primary-color); 15 | box-shadow: var(--shadow); 16 | 17 | border-radius: 2px; 18 | 19 | opacity: 0; 20 | transform: scaleY(0); 21 | transform-origin: left top 0px; 22 | transition: transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms; 23 | } 24 | 25 | .filtergp-top { 26 | opacity: 1; 27 | transform: scaleY(1); 28 | } 29 | 30 | .filter { 31 | padding: 20px 5px 0; 32 | } 33 | 34 | .filter .horiz { 35 | display: flex; 36 | flex-direction: row; 37 | } 38 | 39 | text-field input::-webkit-input-placeholder { 40 | font-size: 13px!important; 41 | } 42 | 43 | .filter text-field { 44 | margin-bottom: 25px!important; 45 | } 46 | 47 | .filter text-field:first-child, 48 | .filter select-field:first-child { 49 | margin-right: 5px!important; 50 | } 51 | 52 | .filter text-field-float, 53 | .filter select-float { 54 | color: var(--primary-color)!important; 55 | } 56 | 57 | .entry .searchbar .search .filter .horiz text-field text-field-error { 58 | transform: scale(0.75) translate( -43px, 0 )!important; 59 | } 60 | 61 | .entry .searchbar .search .filter text-field text-field-error { 62 | padding-left: 10px; 63 | } 64 | 65 | .searchpage .searchbar .filter text-field text-field-state { 66 | border-top: none var(--primary-color) !important; 67 | border-left: none var(--primary-color) !important; 68 | border-right: none var(--primary-color) !important; 69 | border-bottom: 2px solid var(--primary-color) !important; 70 | } 71 | 72 | .searchpage .searchbar .filter input { 73 | font-size: 14px!important; 74 | color: color( var(--text-color) alpha(-13%))!important; 75 | } 76 | 77 | .searchpage .searchbar .filter input::-webkit-input-placeholder { 78 | color: color( var(--text-color) alpha(-13%))!important; 79 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

SOV2EX - 一个便捷的 v2ex 站内搜索引擎

3 | 4 | *** 5 | 6 | #### 马上使用: 7 | * https://sov2ex.com 8 | 9 | #### 主要功能: 10 | - Metarial design 风格; 11 | ![](https://i.imgur.com/jh8FNXRm.png) ![](https://i.imgur.com/w9tjfARm.png) 12 | - 查询 v2ex 站内的 `标题` `正文` `留言` `附言` 的内容; 13 | - 高级搜索,包括:`每页查询的数量` `指定具体的查询节点` `发帖的起止日期` `排序` 等; 14 | ![](https://i.imgur.com/EGAkPSk.png) 15 | - 支持手机端; 16 | 17 | #### 下一步: 18 | - [ ] 更多的搜索条件; 19 | - [ ] 定制化; 20 | 21 | #### API: 22 | - https://github.com/bynil/sov2ex/blob/master/API.md 23 | 24 | #### 更新日志: 25 | - version `beta 1.0.0` at 2017-09-24 26 | * 上线简单的关键词查询,开放源码和 API; 27 | 28 | - version `1.0.0` at 2017-10-16 29 | * 完成 `Metarial design 风格` `高级查询` `手机端` 支持; 30 | 31 | - version `1.0.1` at 2017-10-18 32 | - [x] 调整字重与字体颜色; 33 | - [x] 修复分页逻辑,当前可搜索的最大页数为 100; 34 | - [x] 首页自动聚焦到搜索框; 35 | - [x] 优化 url 为非法参数时的错误处理流程; 36 | - [x] 搜索链接统一为 `https`; 37 | 38 | #### 作者: 39 | - 后端 via [默默](http://www.gexiao.me/) 40 | - 前端 via [Kenshin Wang](https://github.com/Kenshin/sov2ex) 41 | 42 | #### 相关链接: 43 | * [更新日志](https://github.com/bynil/sov2ex/blob/master/README.md#更新日志) 44 | * [反馈](https://github.com/bynil/sov2ex/issues) 45 | 46 | #### SOV2EX 的诞生离不开它们: 47 | - [Elasticsearch](https://www.elastic.co/) 48 | - [IK Analysis for Elasticsearch](https://github.com/medcl/elasticsearch-analysis-ik) 49 | - [Flask](http://flask.pocoo.org/) 50 | - [MongoDB](https://www.mongodb.com/) 51 | - [Node.js](https://nodejs.org/) · [NPM](https://www.npmjs.com) 52 | - [Webpack](https://webpack.github.io/) 53 | - [React](https://facebook.github.io/react) 54 | - [ES6](http://es6-features.org/) · [Babel](https://babeljs.io) 55 | - [PostCSS](http://postcss.org/) · [cssnext](http://cssnext.io/) 56 | - [jQuery](https://jquery.com/) · [pangu.js](https://github.com/vinta/pangu.js) · [Velocity.js](http://velocityjs.org/) 57 | - [Visual Studio Code](https://code.visualstudio.com/) 58 | - [Sketch](https://www.sketchapp.com/) · [Pixelmator](http://www.pixelmator.com/) 59 | - Icon from 60 | - Material Design usage 61 | - 咖啡 · 网易音乐 · Google Chrome · rMBP 62 | 63 | #### 许可: 64 | [![license-badge]][license-link] 65 | 66 | 67 | [license-badge]: https://img.shields.io/github/license/mashape/apistatus.svg 68 | [license-link]: https://opensource.org/licenses/MIT 69 | -------------------------------------------------------------------------------- /src/module/entry.jsx: -------------------------------------------------------------------------------- 1 | console.log( "==== sov2ex module: Entry ====" ) 2 | 3 | import TextField from 'textfield'; 4 | 5 | import * as filter from 'filter'; 6 | 7 | export default class Entry extends React.Component{ 8 | 9 | onKeyDown( event ) { 10 | event.keyCode == 13 && 11 | this.search( event.target.value ); 12 | } 13 | 14 | onClick() { 15 | this.search( this.refs.search.refs.target.value ); 16 | } 17 | 18 | arrowOnClick() { 19 | filter.Render( $( ".filtergp" )[0] ); 20 | $( ".filtergp" ).toggleClass( "filtergp-top" ); 21 | } 22 | 23 | search( value ) { 24 | if ( value.trim() != "" ) { 25 | let url = window.location.origin + window.location.pathname + `?q=${value}`; 26 | Object.keys( sessionStorage ).forEach( key => url += `&${key}=${sessionStorage[key]}`); 27 | sessionStorage.clear(); 28 | console.log( sessionStorage, url ) 29 | window.location.href = url; 30 | } else { 31 | new Notify().Render( "不能为空,请输入正确的值。" ); 32 | } 33 | } 34 | 35 | componentWillMount() { 36 | sessionStorage.clear(); 37 | } 38 | 39 | componentDidMount() { 40 | $( this.refs.search.refs.target ).focus(); 41 | } 42 | 43 | render() { 44 | return ( 45 |
46 |
47 | 48 |
49 |
50 |
51 | this.onKeyDown(e) } 55 | /> 56 | this.onClick() }> 57 | this.arrowOnClick() }> 58 |
59 |
60 |
61 |
62 | 一个便捷的 v2ex 站内搜索引擎,了解更多 。 63 |
64 |
65 | ) 66 | } 67 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | SOV2EX - 一个便捷的 v2ex 站内搜索引擎 25 | 26 | 27 |
28 | 29 | 30 | 38 | 39 | -------------------------------------------------------------------------------- /src/assets/css/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: rgba(3, 169, 244, 1); 3 | --text-color: rgba(0, 0, 0, 1); 4 | 5 | --ellipsis : { 6 | overflow: hidden; 7 | text-overflow: ellipsis; 8 | display: -webkit-box; 9 | -webkit-line-clamp: 2; 10 | -webkit-box-orient: vertical; 11 | } 12 | 13 | --shadow : rgba(0, 0, 0, .12) 0px 1px 6px, rgba(0, 0, 0, .12) 0px 1px 4px; 14 | } 15 | 16 | /** 17 | * Golbal style 18 | */ 19 | 20 | html { 21 | font: 300 16px/1.8 -apple-system, PingFang SC, Microsoft Yahei, Lantinghei SC, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif; 22 | 23 | color: var(--text-color); 24 | 25 | text-rendering: optimizelegibility; 26 | -webkit-text-size-adjust: 100%; 27 | -webkit-font-smoothing: antialiased; 28 | } 29 | 30 | body { 31 | margin: 0; 32 | padding: 0; 33 | 34 | background-color: #fafafa; 35 | } 36 | 37 | @import 'entry.css'; 38 | @import 'search.css'; 39 | @import 'filter.css'; 40 | 41 | @media (max-width: 1024px) { 42 | .searchpage .top { 43 | flex-direction: column; 44 | } 45 | 46 | .searchpage .top .logo { 47 | text-align: center; 48 | } 49 | 50 | .searchpage .top .logo img { 51 | padding-right: 5px; 52 | } 53 | 54 | text-field-error { 55 | zoom: .5; 56 | } 57 | 58 | .entry .searchbar .search .filter .horiz text-field text-field-error { 59 | transform: scale(0.75) translate( -73px, 0 )!important; 60 | } 61 | 62 | } 63 | 64 | @media (min-width: 768px) and (max-width: 1023px) { 65 | .searchpage .searchbar, 66 | .searchpage .cost span, 67 | .searchpage .searchresults .resultcard, 68 | .searchpage .pagingbg .paginghr, 69 | .searchpage .paging a, 70 | .footer .groups { 71 | min-width: initial; 72 | width: 90%; 73 | } 74 | 75 | .empty { 76 | zoom: .8; 77 | } 78 | 79 | text-field-error { 80 | zoom: .4; 81 | } 82 | } 83 | 84 | @media (max-width: 767px) { 85 | .searchpage .searchbar, 86 | .searchpage .cost span, 87 | .searchpage .searchresults .resultcard, 88 | .searchpage .pagingbg .paginghr, 89 | .searchpage .paging a, 90 | .footer .groups { 91 | min-width: initial; 92 | width: 85%; 93 | } 94 | 95 | .empty { 96 | zoom: .8; 97 | } 98 | 99 | text-field-error { 100 | zoom: .3; 101 | } 102 | } 103 | 104 | @media (max-width: 700px) { 105 | .searchpage .searchbar, 106 | .searchpage .cost span, 107 | .searchpage .searchresults .resultcard, 108 | .searchpage .pagingbg .paginghr, 109 | .searchpage .paging a, 110 | .footer .groups { 111 | min-width: initial; 112 | width: 80%; 113 | } 114 | .searchpage .pagingbg .paginghr { 115 | zoom: .8 116 | } 117 | } 118 | 119 | @media (max-width: 540px) { 120 | .searchpage .searchbar, 121 | .searchpage .cost span, 122 | .searchpage .searchresults .resultcard, 123 | .searchpage .pagingbg .paginghr, 124 | .searchpage .paging a, 125 | .footer .groups { 126 | min-width: initial; 127 | width: 75%; 128 | } 129 | } 130 | 131 | @media (max-width: 425px) { 132 | .entry .searchbar { 133 | zoom: .9; 134 | } 135 | .entry .searchbar .search input { 136 | font-size: 13px!important; 137 | } 138 | .searchpage .searchbar, 139 | .searchpage .cost span, 140 | .searchpage .searchresults .resultcard, 141 | .searchpage .pagingbg .paginghr, 142 | .searchpage .paging a { 143 | min-width: initial; 144 | width: 90%; 145 | } 146 | .searchpage .searchbar input { 147 | font-size: 13px!important; 148 | } 149 | .footer { 150 | zoom: .8; 151 | } 152 | } 153 | 154 | @media (max-width: 379px) { 155 | .notify-modal { 156 | zoom: .5; 157 | } 158 | .empty { 159 | zoom: .6; 160 | } 161 | 162 | } 163 | 164 | @media (max-width: 368px) { 165 | body { 166 | zoom: .8; 167 | } 168 | .entry .searchbar { 169 | zoom: initial; 170 | } 171 | .entry .search { 172 | width: 90%!important; 173 | } 174 | } 175 | 176 | @media (max-width: 350px) { 177 | .searchpage .searchresults .resultcard .details { 178 | @apply --ellipsis; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/vender/waves/waves.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Waves v0.7.5 3 | * http://fian.my.id/Waves 4 | * 5 | * Copyright 2014-2016 Alfiana E. Sibuea and other contributors 6 | * Released under the MIT license 7 | * https://github.com/fians/Waves/blob/master/LICENSE 8 | */ 9 | .md-waves-effect { 10 | position: relative; 11 | cursor: pointer; 12 | display: inline-block; 13 | overflow: hidden; 14 | -webkit-user-select: none; 15 | -moz-user-select: none; 16 | -ms-user-select: none; 17 | user-select: none; 18 | -webkit-tap-highlight-color: transparent; 19 | } 20 | .md-waves-effect .md-waves-ripple { 21 | position: absolute; 22 | border-radius: 50%; 23 | width: 100px; 24 | height: 100px; 25 | margin-top: -50px; 26 | margin-left: -50px; 27 | opacity: 0; 28 | background: rgba(0, 0, 0, 0.2); 29 | background: -webkit-radial-gradient(rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 30 | background: -o-radial-gradient(rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 31 | background: -moz-radial-gradient(rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 32 | background: radial-gradient(rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 33 | -webkit-transition: all 0.5s ease-out; 34 | -moz-transition: all 0.5s ease-out; 35 | -o-transition: all 0.5s ease-out; 36 | transition: all 0.5s ease-out; 37 | -webkit-transition-property: -webkit-transform, opacity; 38 | -moz-transition-property: -moz-transform, opacity; 39 | -o-transition-property: -o-transform, opacity; 40 | transition-property: transform, opacity; 41 | -webkit-transform: scale(0) translate(0, 0); 42 | -moz-transform: scale(0) translate(0, 0); 43 | -ms-transform: scale(0) translate(0, 0); 44 | -o-transform: scale(0) translate(0, 0); 45 | transform: scale(0) translate(0, 0); 46 | pointer-events: none; 47 | } 48 | .md-waves-effect.md-waves-light .md-waves-ripple { 49 | background: rgba(255, 255, 255, 0.4); 50 | background: -webkit-radial-gradient(rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 51 | background: -o-radial-gradient(rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 52 | background: -moz-radial-gradient(rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 53 | background: radial-gradient(rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 54 | } 55 | .md-waves-effect.md-waves-classic .md-waves-ripple { 56 | background: rgba(0, 0, 0, 0.2); 57 | } 58 | .md-waves-effect.md-waves-classic.md-waves-light .md-waves-ripple { 59 | background: rgba(255, 255, 255, 0.4); 60 | } 61 | .md-waves-notransition { 62 | -webkit-transition: none !important; 63 | -moz-transition: none !important; 64 | -o-transition: none !important; 65 | transition: none !important; 66 | } 67 | .md-waves-button, 68 | .md-waves-circle { 69 | -webkit-transform: translateZ(0); 70 | -moz-transform: translateZ(0); 71 | -ms-transform: translateZ(0); 72 | -o-transform: translateZ(0); 73 | transform: translateZ(0); 74 | -webkit-mask-image: -webkit-radial-gradient(circle, #ffffff 100%, #000000 100%); 75 | } 76 | .md-waves-button, 77 | .md-waves-button:hover, 78 | .md-waves-button:visited, 79 | .md-waves-button-input { 80 | white-space: nowrap; 81 | vertical-align: middle; 82 | cursor: pointer; 83 | border: none; 84 | outline: none; 85 | color: inherit; 86 | background-color: rgba(0, 0, 0, 0); 87 | font-size: 1em; 88 | line-height: 1em; 89 | text-align: center; 90 | text-decoration: none; 91 | z-index: 1; 92 | } 93 | .md-waves-button { 94 | padding: 0.85em 1.1em; 95 | border-radius: 0.2em; 96 | } 97 | .md-waves-button-input { 98 | margin: 0; 99 | padding: 0.85em 1.1em; 100 | } 101 | .md-waves-input-wrapper { 102 | border-radius: 0.2em; 103 | vertical-align: bottom; 104 | } 105 | .md-waves-input-wrapper.md-waves-button { 106 | padding: 0; 107 | } 108 | .md-waves-input-wrapper .md-waves-button-input { 109 | position: relative; 110 | top: 0; 111 | left: 0; 112 | z-index: 1; 113 | } 114 | .md-waves-circle { 115 | text-align: center; 116 | width: 2.5em; 117 | height: 2.5em; 118 | line-height: 2.5em; 119 | border-radius: 50%; 120 | } 121 | .md-waves-float { 122 | -webkit-mask-image: none; 123 | -webkit-box-shadow: 0px 1px 1.5px 1px rgba(0, 0, 0, 0.12); 124 | box-shadow: 0px 1px 1.5px 1px rgba(0, 0, 0, 0.12); 125 | -webkit-transition: all 300ms; 126 | -moz-transition: all 300ms; 127 | -o-transition: all 300ms; 128 | transition: all 300ms; 129 | } 130 | .md-waves-float:active { 131 | -webkit-box-shadow: 0px 8px 20px 1px rgba(0, 0, 0, 0.3); 132 | box-shadow: 0px 8px 20px 1px rgba(0, 0, 0, 0.3); 133 | } 134 | .md-waves-block { 135 | display: block; 136 | } 137 | -------------------------------------------------------------------------------- /src/vender/mduikit/dialog.jsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Material Design: Dialog 3 | * 4 | * @version : 0.0.1 5 | * @update : 2017/05/11 6 | * @homepage: https://github.com/kenshin/react-md-ui 7 | * @license : MIT https://github.com/kenshin/react-md/blob/master/LICENSE 8 | * @author : Kenshin Wang 9 | * 10 | * @copyright 2017 11 | */ 12 | 13 | console.log( "==== simpread component: Dialog ====" ) 14 | 15 | let root, rootjq; 16 | const style = { 17 | 18 | bg: { 19 | display: '-webkit-flex', 20 | justifyContent: 'center', 21 | alignItems: 'center', 22 | 23 | position: 'fixed', 24 | 25 | top: '-100px', 26 | left: 0, 27 | width: '100%', 28 | height: '100%', 29 | 30 | color: '#fff', 31 | 32 | textShadow: '0 1px rgba(0,0,0,0.3)', 33 | 34 | opacity: 0, 35 | transition: 'all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms', 36 | 37 | zIndex: 2147483647, 38 | }, 39 | 40 | root: { 41 | display: '-webkit-flex', 42 | flexDirection: 'column', 43 | 44 | minWidth: '480px', 45 | minHeight: '300px', 46 | 47 | margin: 0, 48 | padding: 0, 49 | 50 | color: 'rgba(0, 0, 0, 0.870588)', 51 | backgroundColor: 'rgb(255, 255, 255)', 52 | 53 | borderRadius: '3px', 54 | 55 | boxSizing: 'border-box', 56 | boxShadow: 'rgba(0, 0, 0, 0.247059) 0px 14px 45px, rgba(0, 0, 0, 0.219608) 0px 10px 18px', 57 | }, 58 | 59 | content: { 60 | display: 'block', 61 | 62 | width: '100%', 63 | height: '100%', 64 | 65 | minHeight: '244px', 66 | 67 | padding: '39px 24px 0', 68 | 69 | overflowY: 'auto', 70 | boxSizing: 'border-box', 71 | }, 72 | 73 | footer: { 74 | display: '-webkit-flex', 75 | flexFlow: 'row nowrap', 76 | justifyContent: 'flex-end', 77 | 78 | width: '100%', 79 | 80 | boxSizing: 'border-box', 81 | }, 82 | 83 | }; 84 | 85 | /** 86 | * Custom component: Dialog 87 | * 88 | * Reference: 89 | * - https://material.io/guidelines/components/dialogs.html 90 | * - http://www.material-ui.com/#/components/dialog 91 | * 92 | * @class 93 | */ 94 | class Dialog extends React.Component { 95 | 96 | static defaultProps = { 97 | onOpen : undefined, 98 | onClose: undefined, 99 | } 100 | 101 | static propTypes = { 102 | onOpen : React.PropTypes.func, 103 | onClose: React.PropTypes.func, 104 | } 105 | 106 | componentDidMount() { 107 | $( "dialog-content" ).height() < 585 && $( "dialog-footer" ).css( "border-top", "none" ); 108 | $( rootjq ).css({ opacity: 1, top: 0 }); 109 | this.props.onOpen && this.props.onOpen(); 110 | } 111 | 112 | componentWillUnmount() { 113 | this.props.onClose && this.props.onClose(); 114 | } 115 | 116 | render() { 117 | 118 | let content, footer; 119 | 120 | if ( this.props.children && !$.isArray( this.props.children )) { 121 | content = this.props.children; 122 | } else if ( this.props.children && $.isArray( this.props.children )) { 123 | content = this.props.children[0]; 124 | footer = this.props.children[1]; 125 | } 126 | 127 | return ( 128 | 129 | { content } 130 | { footer } 131 | 132 | ) 133 | } 134 | 135 | } 136 | 137 | /** 138 | * React stateless component 139 | * 140 | * @param {object} props, include: children 141 | */ 142 | const Content = props => { props.children }, 143 | Footer = props => { props.children }; 144 | 145 | /** 146 | * get Dialog root html element 147 | * 148 | * @param {jquery} jquery query selector 149 | * @param {string} class name 150 | * 151 | * @return {elem} html element 152 | */ 153 | function getRoot( $target, cls ) { 154 | [ root, rootjq ] = [ cls, `.${cls}` ]; 155 | $target.find( rootjq ).length == 0 && $target.append( `
` ); 156 | Object.keys( style.bg ).forEach( key => $( rootjq )[0].style[ key ] = style.bg[ key ] ); 157 | return $( rootjq )[0]; 158 | } 159 | 160 | /** 161 | * Open Dialog 162 | * 163 | * @param {react} react dom 164 | * @param {string} dialog background class name 165 | * @param {jquery} jquery query selector 166 | */ 167 | function Open( reactDom, cls, $target = $("html") ) { 168 | ReactDOM.render( reactDom, getRoot( $target, cls ) ); 169 | } 170 | 171 | /** 172 | * Close Dialog 173 | */ 174 | function Close() { 175 | $( rootjq ) 176 | .css({ top: "-100px" }) 177 | .velocity({ opacity: 0 }, { complete: ()=>{ 178 | ReactDOM.unmountComponentAtNode( $( rootjq )[0] ); 179 | $( rootjq ).remove(); 180 | }}); 181 | } 182 | 183 | /** 184 | * Verify dialog is popup 185 | * 186 | * @param {string} jquery selector 187 | * @return {boolean} 188 | */ 189 | function Popup( clsjq ) { 190 | return $( clsjq ).children().length == 0 ? false : true; 191 | } 192 | 193 | export { 194 | Dialog, 195 | Content, 196 | Footer, 197 | Open, 198 | Close, 199 | Popup, 200 | } -------------------------------------------------------------------------------- /src/module/filter.jsx: -------------------------------------------------------------------------------- 1 | console.log( "==== sov2ex module: Filter ====" ) 2 | 3 | import TextField from 'textfield'; 4 | import SelectField from 'selectfield'; 5 | 6 | const sort = [{ 7 | value : "sumup", 8 | name : "权重", 9 | },{ 10 | value : "created", 11 | name : "发帖时间", 12 | }], 13 | order = [{ 14 | value : "0", 15 | name : "降序", 16 | },{ 17 | value : "1", 18 | name : "升序", 19 | }]; 20 | 21 | class Filter extends React.Component { 22 | 23 | state = { 24 | size_error : "", 25 | gte_error : "", 26 | lte_error : "", 27 | order_disable: true, 28 | }; 29 | 30 | onSizeChange( event ) { 31 | const value = event.target.value.trim(); 32 | if ( value == "" ) { 33 | this.setState({ size_error : "" }); 34 | sessionStorage.removeItem( "size" ); 35 | } 36 | else if ( !/^\d+$/.test( value ) || value < 1 || value > 50 ) { 37 | this.setState({ 38 | size_error: "取值范围 1 ~ 50 的正整数" 39 | }); 40 | } else { 41 | console.log( value ) 42 | this.setState({ size_error : "" }); 43 | sessionStorage.setItem( "size", value ); 44 | } 45 | } 46 | 47 | onNodeChange( event ) { 48 | console.log( event.target.value.trim()) 49 | event.target.value.trim() == "" ? sessionStorage.removeItem( "node" ) : 50 | sessionStorage.setItem( "node", event.target.value.trim() ); 51 | } 52 | 53 | onSortChange( value, name ) { 54 | console.log( value, name ) 55 | value == sort[0].value ? sessionStorage.removeItem( "sort" ) : 56 | sessionStorage.setItem( "sort", value ); 57 | this.setState({ 58 | order_disable: value == sort[0].value 59 | }); 60 | } 61 | 62 | onOrderChange( value, name ) { 63 | console.log( value, name ) 64 | value == order[0].value ? sessionStorage.removeItem( "order" ) : 65 | sessionStorage.setItem( "order", value ); 66 | } 67 | 68 | getName( filter, value ) { 69 | if ( !value ) { 70 | return filter[0].name; 71 | } else { 72 | const result = filter.find( item => item.value == value ); 73 | return result.name; 74 | } 75 | } 76 | 77 | getDay( value ) { 78 | if ( !value ) return ""; 79 | else if ( !/\d+$/.test( value ) ) { 80 | return ""; 81 | } 82 | else { 83 | const date = new Date( parseInt( value )), 84 | format = value => value = value < 10 ? "0" + value : value; 85 | return date.getFullYear() + "-" + format( date.getUTCMonth() + 1 ) + "-" + format( date.getUTCDate() ); 86 | } 87 | } 88 | 89 | onDateChange( type, event ) { 90 | console.log( type, event.target.value ) 91 | const value = event.target.value.trim(), 92 | error = `${type}_error`; 93 | if ( value == "" ) { 94 | this.setState({ [error] : "" }); 95 | sessionStorage.removeItem( type ); 96 | } 97 | else if ( /\w{4}-\w{2}-\w{2}/.test( value ) ) { 98 | const day = new Date( value ).getTime(); 99 | if ( day ) { 100 | this.setState({ [error] : "" }); 101 | sessionStorage.setItem( type, day ); 102 | } else { 103 | this.setState({ 104 | [error]: "格式错误,如 2017-10-13" 105 | }); 106 | } 107 | } else { 108 | this.setState({ 109 | [error]: "格式错误,如 2017-10-13" 110 | }); 111 | } 112 | } 113 | 114 | componentWillMount() { 115 | if ( location.search.startsWith( "?q=" ) ) { 116 | const query = window.location.search.replace( "?", "" ).split( "&" ); 117 | query && query.length > 0 && query.forEach( item => { 118 | const [ key, value ] = item.split( "=" ); 119 | key != "q" && sessionStorage.setItem( key, decodeURI( value ) ); 120 | }); 121 | console.log( sessionStorage ) 122 | } 123 | } 124 | 125 | render() { 126 | return ( 127 |
128 | this.onSizeChange(e) } 133 | /> 134 | this.onNodeChange(e) } 138 | /> 139 |
140 | this.onDateChange( "gte", evt ) } 145 | /> 146 | this.onDateChange( "lte", evt ) } 151 | /> 152 |
153 |
154 | this.onSortChange(v,n) } 158 | /> 159 | this.onOrderChange(v,n) } 164 | /> 165 |
166 |
167 | ) 168 | } 169 | } 170 | 171 | function Render( target ) { 172 | ReactDOM.render( , target ); 173 | } 174 | 175 | export { 176 | Render, 177 | } -------------------------------------------------------------------------------- /src/vender/notify/notify.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Notify Group 4 | */ 5 | notify-gp { 6 | font: 300 14px -apple-system, PingFang SC, Microsoft Yahei, Lantinghei SC, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif; 7 | text-rendering: optimizelegibility; 8 | -webkit-text-size-adjust: 100%; 9 | -webkit-font-smoothing: antialiased; 10 | 11 | display: -webkit-flex; 12 | flex-flow: column nowrap; 13 | align-items: flex-end; 14 | 15 | position: fixed; 16 | 17 | top: 0; 18 | right: 0; 19 | 20 | margin: 0 15px 0 0; 21 | padding: 0; 22 | 23 | text-transform: none; 24 | 25 | pointer-events: none; 26 | } 27 | 28 | notify-gp notify { 29 | display: -webkit-flex; 30 | align-items: center; 31 | 32 | margin: 0; 33 | margin-top: 15px; 34 | padding: 14px 24px; 35 | 36 | min-width: 288px; 37 | max-width: 568px; 38 | 39 | height: 48px; 40 | max-height: 48px; 41 | 42 | color: rgba(255, 255, 255, .7); 43 | background-color: rgba(50, 50, 50, 1); 44 | 45 | box-sizing: border-box; 46 | border-radius: 2px; 47 | pointer-events: auto; 48 | user-select: none; 49 | 50 | opacity: 0; 51 | transform: scaleY(0); 52 | transform-origin: left top 0px; 53 | transition: transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms; 54 | } 55 | 56 | notify-gp notify-title { 57 | font-size: 13px; 58 | font-weight: bold; 59 | } 60 | 61 | notify-gp notify-content { 62 | display: block; 63 | 64 | font-size: 14px; 65 | text-align: left; 66 | 67 | overflow: hidden; 68 | text-overflow: ellipsis; 69 | white-space: nowrap; 70 | } 71 | 72 | notify-gp notify-content a, 73 | notify-gp notify-content a:link, 74 | notify-gp notify-content a:visited, 75 | notify-gp notify-content a:active { 76 | margin: inherit; 77 | padding-bottom: 5px; 78 | 79 | color: #fff; 80 | font-size: inherit; 81 | 82 | text-decoration: none; 83 | 84 | transition: color .5s; 85 | } 86 | 87 | notify-gp notify-content a:hover { 88 | margin: initial; 89 | padding: initial; 90 | 91 | color: inherit; 92 | font-size: inherit; 93 | 94 | text-decoration: none; 95 | } 96 | 97 | notify-gp notify-i { 98 | display: none; 99 | 100 | margin: 0 10px 0 0; 101 | 102 | width: 24px; 103 | height: 24px; 104 | 105 | background-position: center; 106 | background-repeat: no-repeat; 107 | } 108 | 109 | notify-gp notify-action { 110 | display: none; 111 | 112 | margin: 0 0 0 24px; 113 | 114 | max-width: 80px; 115 | min-width: 56px; 116 | 117 | color: rgba(255, 238, 88, 1); 118 | 119 | font-size: inherit; 120 | text-transform: uppercase; 121 | text-align: right; 122 | 123 | overflow: hidden; 124 | text-overflow: ellipsis; 125 | white-space: nowrap; 126 | 127 | cursor: pointer; 128 | } 129 | 130 | notify-gp notify-a { 131 | display: block; 132 | position: absolute; 133 | 134 | top: 5px; 135 | right: 5px; 136 | 137 | cursor: pointer; 138 | } 139 | 140 | notify-gp notify-a notify-span { 141 | display: block; 142 | width: 16px; 143 | height: 16px; 144 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABpFBMVEUAAADl5eXj4+NSVFRTVVVaXFxLTU1hY2NdX18pLCwhJCSdnp5sbm6HiYmjpKSDhYX///+rrKytrq6XmJgzNTUoKyt3eXlydHRlZ2dbXV1ucHB4enrv7++KjIyIiort7e1oamosLy8aHR0VGBgUFxcbHh4rLi5oamprbGwgIyMKDQ0KDQ0iJSVjZWWfoaEkJiYICwsLDg4KDQ0MDw8iJSWMjo41ODgMDw8JDAw2OTkvMTELDg4LDg4xMzM1NzcJDAwLDg40NjYeISEHCgoeISFkZmYtLy8yNDRvcXEWGRkHCgoaHR3///8RFBQHCgohJCShoqLIyMgaHR0HCgoZGxv4+PgRFBQLDg4xMzOWl5eam5ssLi4bHh7///8fIiIJDAwwMzNzdHQXGhoeISFlZmYsLi4KDQ0gIiI6PDwOEREuMDAXGhoHCgodHx8pLCwNEBA1ODj///8nKSkICwsICwsJDAwnKSnZ2dl9fn4pKysNDw8OEREpLCxyc3ORkpIzNTUjJSUVGBgUFxcgIyM5PDyanJwEBwcDBwcDBgYFCAgGCQn///+5RDDmAAAAhnRSTlMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQUFAQU+i7S0jkAGEYrw8Y8SBIn++Pr3jQQ67fx8dvX6iWr27z+B/YsOcoMPdPiEAaL7iAgEcfWsA6D7iAkGcawDef2LDnOFD3T4gTLnfHb6iWrqNQJ4+ff7fQILd+ToewsCLHWZmXUwAyFsKwcAAAABYktHRBCVsg0sAAAAzElEQVQY02NgwAoYZWTl5JngXGYFRSVlFVU1dRYIn1VDU6sNCLR1dNlAfHY9fQNDw/YOI2MDE1MOoACnmbmFpZW1ja2dvYMjFwMDN4NTp7OLq5u7h6dXpzcDDwOvj29bm59/QGBQcFtbSCgfA79AWFtHeERkVLR1W1tMrCCDEENcZ3xCYlJySmpaZzqDMAODSEamRVZ2cE5unn1+gSjQFrHCIqNir7a2nJLSsnJxkEMkKiqrutrauqpraiUhTpWqq29obGpuaZVmIAYAAO06McffKEk8AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTA0LTAzVDE3OjE4OjAzKzA4OjAwRdgB9wAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wNS0xOFQyMDowMTowMCswODowMB0r3XkAAABNdEVYdHNvZnR3YXJlAEltYWdlTWFnaWNrIDYuOC44LTcgUTE2IHg4Nl82NCAyMDE0LTAyLTI4IGh0dHA6Ly93d3cuaW1hZ2VtYWdpY2sub3JnWaRffwAAABh0RVh0VGh1bWI6OkRvY3VtZW50OjpQYWdlcwAxp/+7LwAAABh0RVh0VGh1bWI6OkltYWdlOjpIZWlnaHQAMTI4Q3xBgAAAABd0RVh0VGh1bWI6OkltYWdlOjpXaWR0aAAxMjjQjRHdAAAAGXRFWHRUaHVtYjo6TWltZXR5cGUAaW1hZ2UvcG5nP7JWTgAAABd0RVh0VGh1bWI6Ok1UaW1lADEzMDU3MjAwNjArP9HVAAAAE3RFWHRUaHVtYjo6U2l6ZQAxLjAzS0JCZtQvXwAAAFx0RVh0VGh1bWI6OlVSSQBmaWxlOi8vL2hvbWUvZnRwLzE1MjAvZWFzeWljb24uY24vZWFzeWljb24uY24vY2RuLWltZy5lYXN5aWNvbi5jbi9wbmcvMTcvMTc4Ni5wbmcRsze7AAAAAElFTkSuQmCC); 145 | } 146 | 147 | notify-gp .notify-show { 148 | opacity: 1; 149 | transform: scaleY(1); 150 | } 151 | 152 | notify-gp .notify-hide { 153 | opacity: 0; 154 | transform: scaleY(0); 155 | } 156 | 157 | notify-gp .notify-success { 158 | color: rgba(118, 255, 3, .8); 159 | } 160 | 161 | notify-gp .notify-warning { 162 | color: rgba(255, 238, 88, 1); 163 | } 164 | 165 | notify-gp .notify-error { 166 | color: rgba(239, 83, 80, 1); 167 | } 168 | 169 | notify-gp .notify-modal { 170 | flex-flow: column nowrap; 171 | align-items: flex-start; 172 | 173 | height: auto; 174 | max-height: 200px; 175 | 176 | box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2); 177 | } 178 | 179 | notify-gp .notify-modal .notify-modal-content { 180 | margin-top: 5px; 181 | font-size: 13px; 182 | white-space: normal; 183 | } 184 | 185 | notify-gp .notify-modal .notify-modal-content a { 186 | margin: 0; 187 | padding: 0; 188 | 189 | color: inherit; 190 | 191 | font-size: inherit; 192 | text-decoration: underline; 193 | 194 | cursor: pointer; 195 | } 196 | 197 | notify-gp .notify-modal .notify-modal-content a:hover, 198 | notify-gp .notify-modal .notify-modal-content a:active, 199 | notify-gp .notify-modal .notify-modal-content a:visited, 200 | notify-gp .notify-modal .notify-modal-content a:focus { 201 | color: inherit; 202 | } 203 | 204 | notify-gp .notify-snackbar { 205 | position: fixed; 206 | bottom: 0; 207 | left: 50%; 208 | transform-origin: left bottom 0px; 209 | } 210 | 211 | .notify-position-left-top-corner { 212 | align-items: flex-start; 213 | 214 | margin: 0 0 0 15px; 215 | 216 | left: 0; 217 | right: initial; 218 | } 219 | 220 | .notify-position-left-bottom-corner { 221 | flex-flow: column-reverse wrap-reverse; 222 | 223 | margin: 0 0 15px 15px; 224 | 225 | right: initial; 226 | top: initial; 227 | 228 | left: 0; 229 | bottom: 0; 230 | } 231 | 232 | .notify-position-right-bottom-corner { 233 | flex-flow: column-reverse wrap-reverse; 234 | align-items: flex-start; 235 | 236 | margin: 0 15px 15px 0; 237 | 238 | top: initial; 239 | left: initial; 240 | 241 | bottom: 0; 242 | right: 0; 243 | } 244 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const ExtractTextPlugin = require( 'extract-text-webpack-plugin' ), 2 | HtmlWebpackPlugin = require( 'html-webpack-plugin' ) 3 | webpack = require( 'webpack' ), 4 | plugins = [ 5 | 6 | // omit import xxx 7 | new webpack.ProvidePlugin({ 8 | React : 'react', 9 | ReactDOM : 'react-dom', 10 | Notify : 'notify', 11 | jQuery : 'jquery', 12 | }), 13 | 14 | // chunk files 15 | new webpack.optimize.CommonsChunkPlugin({ 16 | names : [ 'vendors' ], 17 | minChunks : Infinity 18 | }), 19 | 20 | // defined environment variable 21 | new webpack.DefinePlugin({ 22 | 'process.env.NODE_ENV': JSON.stringify( 'production' ) // or development 23 | }), 24 | 25 | // extract css files 26 | new ExtractTextPlugin( '[name].css' ), 27 | 28 | // minify html files 29 | new HtmlWebpackPlugin({ 30 | filename: 'index.html', 31 | template: 'src/index.html', 32 | inject: false, 33 | minify: { 34 | collapseWhitespace: true, 35 | }, 36 | }), 37 | 38 | ], 39 | 40 | // conditions environment 41 | isProduction = function () { 42 | return process.env.NODE_ENV === 'production'; 43 | }, 44 | 45 | // only when environment variable is 'development' call 46 | develop = ( function () { 47 | const OpenBrowserPlugin = require('open-browser-webpack-plugin'); 48 | if ( !isProduction() ) { 49 | plugins.push( 50 | new webpack.HotModuleReplacementPlugin(), 51 | new OpenBrowserPlugin({ url: 'http://localhost:8080' }) 52 | ); 53 | } 54 | })(), 55 | 56 | // only when environment variable is 'production' call 57 | deploy = ( function () { 58 | const CopyWebpackPlugin = require( 'copy-webpack-plugin' ), 59 | CleanWebpackPlugin = require( 'clean-webpack-plugin' ); 60 | 61 | // environment verify 62 | if ( isProduction() ) { 63 | 64 | // delete publish folder 65 | plugins.push( 66 | new CleanWebpackPlugin([ 'publish' ], { 67 | verbose: true, 68 | dry : false, 69 | }) 70 | ); 71 | 72 | // copy files 73 | plugins.push( 74 | new CopyWebpackPlugin([ 75 | { context: 'src/assets/images/', from : '*' , to : './assets/images' }, 76 | { context: 'src/assets/favicon/', from : '*' , to : './assets/favicon' }, 77 | ]) 78 | ); 79 | 80 | // call uglifyjs plugin 81 | plugins.push( 82 | new webpack.optimize.UglifyJsPlugin({ 83 | compress: { 84 | sequences: true, 85 | dead_code: true, 86 | conditionals: true, 87 | booleans: true, 88 | unused: true, 89 | if_return: true, 90 | join_vars: true, 91 | drop_console: true 92 | }, 93 | mangle: { 94 | except: [ '$', 'exports', 'require' ] 95 | }, 96 | output: { 97 | comments: false 98 | } 99 | }) 100 | ); 101 | 102 | } 103 | })(), 104 | 105 | bundle = ( function () { 106 | const files = [ 107 | './src/index.jsx' 108 | ]; 109 | if ( !isProduction() ) { 110 | files.push( 111 | 'webpack/hot/dev-server', 112 | 'webpack-dev-server/client?http://localhost:8080' 113 | ); 114 | } 115 | return files; 116 | }), 117 | 118 | // webpack config 119 | config = { 120 | entry: { 121 | 122 | vendors : [ 123 | 124 | // react 125 | './node_modules/react/dist/react.min.js', 126 | './node_modules/react-dom/dist/react-dom.min.js', 127 | 128 | // vendors 129 | 'jquery', 130 | 'pangu', 131 | 'velocity', 132 | 133 | 'wavess', 134 | 'notify', 135 | 136 | // component 137 | 'textfield', 138 | 'button', 139 | 'selectfield', 140 | /* 141 | 'fab', 142 | 'switch', 143 | 'tabs', 144 | 'sidebar', 145 | 'list', 146 | 'dialog', 147 | */ 148 | 'tooltip', 149 | 'waves' 150 | ], 151 | 152 | bundle: bundle(), 153 | 154 | }, 155 | 156 | output: { 157 | path : isProduction() ? './publish/' : './', 158 | filename : '[name].js' 159 | }, 160 | 161 | devServer: { 162 | contentBase: './src', 163 | port: 8080, 164 | historyApiFallback: true, 165 | hot: true, 166 | inline: true, 167 | progress: true, 168 | }, 169 | 170 | plugins: plugins, 171 | 172 | module: { 173 | loaders: [ 174 | { 175 | test: /\.js[x]?$/, 176 | exclude: /node_modules/, 177 | loader: 'babel', 178 | query: { 179 | presets: [ 'es2015', 'stage-0', 'react' ] 180 | } 181 | }, 182 | 183 | // css in js 184 | //{ test: /\.css$/, loader: 'style!css!postcss' }, 185 | 186 | // extract css files 187 | { test: /\.css$/, loader: ExtractTextPlugin.extract( 'style', 'css!postcss' ) }, 188 | 189 | // image in js 190 | { test: /\.(png|jpg|gif)$/, loader: 'url?limit=12288' }, 191 | 192 | // expose $ 193 | { 194 | test : require.resolve( './src/vender/jquery-2.1.1.min.js' ), 195 | loader: 'expose?jQuery!expose?$' 196 | }, 197 | 198 | ] 199 | }, 200 | 201 | postcss: function () { 202 | return [ 203 | require( 'import-postcss' )(), 204 | require( 'postcss-cssnext' )() 205 | ] 206 | }, 207 | 208 | resolve: { 209 | alias : { 210 | jquery : __dirname + '/src/vender/jquery-2.1.1.min.js', 211 | pangu : __dirname + '/src/vender/pangu.min.js', 212 | velocity : __dirname + '/src/vender/velocity.min.js', 213 | 214 | wavess : __dirname + '/src/vender/waves/waves.js', 215 | notify : __dirname + '/src/vender/notify/notify.js', 216 | 217 | textfield : __dirname + '/src/vender/mduikit/textfield.jsx', 218 | fab : __dirname + '/src/vender/mduikit/fab.jsx', 219 | button : __dirname + '/src/vender/mduikit/button.jsx', 220 | selectfield: __dirname + '/src/vender/mduikit/selectfield.jsx', 221 | switch : __dirname + '/src/vender/mduikit/switch.jsx', 222 | tabs : __dirname + '/src/vender/mduikit/tabs.jsx', 223 | sidebar : __dirname + '/src/vender/mduikit/sidebar.jsx', 224 | list : __dirname + '/src/vender/mduikit/list.jsx', 225 | dialog : __dirname + '/src/vender/mduikit/dialog.jsx', 226 | tooltip : __dirname + '/src/vender/mduikit/tooltip.jsx', 227 | waves : __dirname + '/src/vender/mduikit/waves.js', 228 | 229 | index : __dirname + '/src/index.jsx', 230 | entry : __dirname + '/src/module/entry.jsx', 231 | search : __dirname + '/src/module/search.jsx', 232 | filter : __dirname + '/src/module/filter.jsx', 233 | version : __dirname + '/src/module/version.js', 234 | controlbar : __dirname + '/src/module/controlbar.jsx', 235 | 236 | } 237 | } 238 | 239 | }; 240 | 241 | module.exports = config; 242 | -------------------------------------------------------------------------------- /src/vender/mduikit/switch.jsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Material Design: Switch 3 | * 4 | * @version : 0.0.1 5 | * @update : 2017/08/25 6 | * @homepage: https://github.com/kenshin/react-md-ui 7 | * @license : MIT https://github.com/kenshin/react-md/blob/master/LICENSE 8 | * @author : Kenshin Wang 9 | * 10 | * @copyright 2017 11 | */ 12 | 13 | console.log( "==== simpread component: Switch ====" ) 14 | 15 | let styles = new Map(); 16 | 17 | const color = "rgba(51, 51, 51, .87)", 18 | secondary_color = "rgba(204, 204, 204, 1)", 19 | 20 | thumb_color = "rgba(245, 245, 245, 1)", 21 | thumbed_color = "rgba(0, 137, 123, 1)", 22 | 23 | track_color = "rgba(189, 189, 189, 1)", 24 | tracked_color = "rgba(0, 137, 123, .5)"; 25 | 26 | const cssinjs = () => { 27 | 28 | const styles = { 29 | hidden: 'none', 30 | root: { 31 | display: 'flex', 32 | alignItems: 'center', 33 | position: 'relative', 34 | 35 | width: '100%', 36 | height: '37px', 37 | 38 | margin: '8px 0 0 0', 39 | padding: 0, 40 | 41 | overflow: 'visible', 42 | }, 43 | 44 | enable: { 45 | color: color, 46 | cursor: 'pointer', 47 | }, 48 | 49 | disable: { 50 | color: secondary_color, 51 | cursor: 'not-allowed', 52 | }, 53 | 54 | label: { 55 | display: 'block', 56 | width: '100%', 57 | 58 | fontFamily: 'sans-serif', 59 | fontSize: '14px', 60 | fontWeight: 400, 61 | 62 | userSelect: 'none', 63 | pointerEvents: 'none', 64 | }, 65 | 66 | label_after: { 67 | textAlign: 'right', 68 | order: 2, 69 | }, 70 | 71 | label_before: { 72 | textAlign: 'left', 73 | order: -1, 74 | }, 75 | 76 | range: { 77 | display: 'block', 78 | position: 'relative', 79 | float: 'left', 80 | flexShrink: 0, 81 | 82 | width: '36px', 83 | 84 | margin: '0 0 0 8px', 85 | padding: '4px 0px 6px 2px', 86 | 87 | transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', 88 | }, 89 | 90 | thumb: {}, 91 | 92 | thumb_normal: { 93 | display: 'block', 94 | position: 'absolute', 95 | top: '1px', 96 | left: '0px', 97 | 98 | width: '20px', 99 | height: '20px', 100 | color: color, 101 | backgroundColor: thumb_color, 102 | 103 | boxShadow: 'rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px', 104 | boxSizing: 'border-box', 105 | borderRadius: '50%', 106 | 107 | transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', 108 | }, 109 | 110 | thumbed: { 111 | left: '100%', 112 | marginLeft: '-20px', 113 | backgroundColor: thumbed_color, 114 | }, 115 | 116 | thumb_disable: { 117 | left: 0, 118 | marginLeft: 0, 119 | backgroundColor: secondary_color, 120 | }, 121 | 122 | track: {}, 123 | 124 | track_normal: { 125 | display: 'block', 126 | width: '100%', 127 | height: '14px', 128 | 129 | borderRadius: '30px', 130 | backgroundColor: track_color, 131 | 132 | transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', 133 | }, 134 | 135 | tracked: { 136 | backgroundColor: tracked_color, 137 | }, 138 | 139 | }; 140 | 141 | return styles; 142 | } 143 | 144 | /** 145 | * Custom component: Switich 146 | * 147 | * Reference: 148 | * - https://material.io/guidelines/components/selection-controls.html 149 | * - http://www.material-ui.com/#/components/toggle 150 | * 151 | * @class 152 | */ 153 | export default class Switch extends React.Component { 154 | 155 | static defaultProps = { 156 | checked : false, 157 | disable : false, 158 | width : undefined, 159 | label : "", 160 | order : "before", 161 | thumbColor : undefined, 162 | thumbedColor : undefined, 163 | trackColor : undefined, 164 | trackedColor : undefined, 165 | waves : "", 166 | tooltip : "", 167 | }; 168 | 169 | static propTypes = { 170 | checked : React.PropTypes.bool, 171 | disable : React.PropTypes.bool, 172 | width : React.PropTypes.string, 173 | label : React.PropTypes.string, 174 | order : React.PropTypes.string, 175 | thumbColor : React.PropTypes.string, 176 | thumbedColor : React.PropTypes.string, 177 | trackColor : React.PropTypes.string, 178 | trackedColor : React.PropTypes.string, 179 | waves : React.PropTypes.string, 180 | tooltip : React.PropTypes.string, 181 | onChange : React.PropTypes.func, 182 | }; 183 | 184 | state = { 185 | id : Math.round(+new Date()), 186 | checked : this.props.checked, 187 | }; 188 | 189 | onClick() { 190 | !this.props.disable && this.setState({ 191 | checked: !this.state.checked, 192 | }); 193 | !this.props.disable && this.props.onChange && this.props.onChange( !this.state.checked ); 194 | } 195 | 196 | componentWillReceiveProps( nextProps ) { 197 | this.setState({ checked: nextProps.checked }); 198 | } 199 | 200 | render() { 201 | const style = { ...cssinjs() }; 202 | styles.set( this.state.id, style ); 203 | 204 | this.props.thumbColor && ( style.thumb_normal.backgroundColor = this.props.thumbColor ); 205 | this.props.thumbedColor && ( style.thumbed.backgroundColor = this.props.thumbedColor ); 206 | this.props.trackColor && ( style.track_normal.backgroundColor = this.props.trackColor ); 207 | this.props.trackedColor && ( style.tracked.backgroundColor = this.props.trackedColor ); 208 | 209 | if ( this.state.checked ) { 210 | style.thumb = { ...style.thumb_normal, ...style.thumbed }; 211 | style.track = { ...style.track_normal, ...style.tracked }; 212 | } else { 213 | style.thumb = { ...style.thumb_normal }; 214 | style.track = { ...style.track_normal }; 215 | } 216 | 217 | if ( this.props.disable ) { 218 | style.root = { ...style.root, ...style.disable }; 219 | style.thumb = { ...style.thumb, ...style.thumb_disable }; 220 | style.track = { ...style.track_normal }; 221 | } else { 222 | style.root = { ...style.root, ...style.enable }; 223 | } 224 | 225 | style.label = this.props.order == "before" ? { ...style.label, ...style.label_before } : { ...style.label, ...style.label_after }; 226 | 227 | this.props.label == "" && ( style.label.display = style.hidden ); 228 | this.props.width && ( style.root.width = this.props.width ); 229 | 230 | const tooltip = this.props.tooltip; 231 | 232 | return ( 233 | this.onClick() }> 236 | { this.props.label } 237 | 238 | 239 | 240 | 241 | 242 | ) 243 | } 244 | } -------------------------------------------------------------------------------- /src/vender/pangu.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * pangu.js 3 | * -------- 4 | * @version: 3.3.0 5 | * @homepage: https://github.com/vinta/pangu.js 6 | * @license: MIT 7 | * @author: Vinta Chen (https://github.com/vinta) 8 | */ 9 | !function(e,u){"object"==typeof exports&&"object"==typeof module?module.exports=u():"function"==typeof define&&define.amd?define("pangu",[],u):"object"==typeof exports?exports.pangu=u():e.pangu=u()}(this,function(){return function(e){function u(a){if(f[a])return f[a].exports;var t=f[a]={exports:{},id:a,loaded:!1};return e[a].call(t.exports,t,t.exports,u),t.loaded=!0,t.exports}var f={};return u.m=e,u.c=f,u.p="",u(0)}([function(e,u,f){"use strict";function a(e,u){if(!(e instanceof u))throw new TypeError("Cannot call a class as a function")}function t(e,u){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!u||"object"!=typeof u&&"function"!=typeof u?e:u}function n(e,u){if("function"!=typeof u&&null!==u)throw new TypeError("Super expression must either be null or a function, not "+typeof u);e.prototype=Object.create(u&&u.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),u&&(Object.setPrototypeOf?Object.setPrototypeOf(e,u):e.__proto__=u)}var i=function(){function e(e,u){for(var f=0;f=0||u.isContentEditable||"true"===u.getAttribute("g_editable"))return!0;u=u.parentNode}return!1}},{key:"isFirstTextChild",value:function(e,u){for(var f=e.childNodes,a=0;a-1;a--){var t=f[a];if(t.nodeType!==o&&t.textContent)return t===u}return!1}},{key:"spacingNodeByXPath",value:function(e,u){for(var f=document.evaluate(e,u,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null),a=void 0,t=void 0,n=f.snapshotLength-1;n>-1;--n)if(a=f.snapshotItem(n),this.canIgnoreNode(a))t=a;else{var i=this.spacing(a.data);if(a.data!==i&&(a.data=i),t){if(a.nextSibling&&a.nextSibling.nodeName.search(this.spaceLikeTags)>=0){t=a;continue}var r=a.data.toString().substr(-1)+t.data.toString().substr(0,1),o=this.spacing(r);if(o!==r){for(var s=t;s.parentNode&&s.nodeName.search(this.spaceSensitiveTags)===-1&&this.isFirstTextChild(s.parentNode,s);)s=s.parentNode;for(var c=a;c.parentNode&&c.nodeName.search(this.spaceSensitiveTags)===-1&&this.isLastTextChild(c.parentNode,c);)c=c.parentNode;if(c.nextSibling&&c.nextSibling.nodeName.search(this.spaceLikeTags)>=0){t=a;continue}if(c.nodeName.search(this.blockTags)===-1)if(s.nodeName.search(this.spaceSensitiveTags)===-1)s.nodeName.search(this.ignoreTags)===-1&&s.nodeName.search(this.blockTags)===-1&&(t.previousSibling?t.previousSibling.nodeName.search(this.spaceLikeTags)===-1&&(t.data=" "+t.data):this.canIgnoreNode(t)||(t.data=" "+t.data));else if(c.nodeName.search(this.spaceSensitiveTags)===-1)a.data=a.data+" ";else{var d=document.createElement("pangu");d.innerHTML=" ",s.previousSibling?s.previousSibling.nodeName.search(this.spaceLikeTags)===-1&&s.parentNode.insertBefore(d,s):s.parentNode.insertBefore(d,s),d.previousElementSibling||d.parentNode&&d.parentNode.removeChild(d)}}}t=a}}},{key:"spacingNode",value:function(e){var u=".//*/text()[normalize-space(.)]";this.spacingNodeByXPath(u,e)}},{key:"spacingElementById",value:function(e){var u='id("'+e+'")//text()';this.spacingNodeByXPath(u,document)}},{key:"spacingElementByClassName",value:function(e){var u='//*[contains(concat(" ", normalize-space(@class), " "), "'+e+'")]//text()';this.spacingNodeByXPath(u,document)}},{key:"spacingElementByTagName",value:function(e){var u="//"+e+"//text()";this.spacingNodeByXPath(u,document)}},{key:"spacingPageTitle",value:function(){var e="/html/head/title/text()";this.spacingNodeByXPath(e,document)}},{key:"spacingPageBody",value:function(){for(var e="/html/body//*/text()[normalize-space(.)]",u=["script","style","textarea"],f=0;f])([A-Za-z0-9])/g,p=/([A-Za-z0-9])([\+\-\*\/=&\\|<>])([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])/g,l=/([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])([\(\[\{<\u201c]+(.*?)[\)\]\}>\u201d]+)([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])/g,g=/([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])([\(\[\{<\u201c>])/g,h=/([\)\]\}>\u201d<])([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])/g,v=/([\(\[\{<\u201c]+)(\s*)(.+?)(\s*)([\)\]\}>\u201d]+)/,b=/([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])([~!;:,\.\?\u2026])([A-Za-z0-9])/g,y=/([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])([A-Za-z0-9`\$%\^&\*\-=\+\\\|\/@\u00a1-\u00ff\u2022\u2027\u2150-\u218f])/g,m=/([A-Za-z0-9`~\$%\^&\*\-=\+\\\|\/!;:,\.\?\u00a1-\u00ff\u2022\u2026\u2027\u2150-\u218f])([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])/g,$=function(){function e(){f(this,e)}return a(e,[{key:"spacing",value:function(e){var u=e;u=u.replace(t,"$1 $2"),u=u.replace(n,"$1 $2"),u=u.replace(i,"$1$3$5"),u=u.replace(r,"$1$3$4"),u=u.replace(o,"$1 $2$3$4 $5"),u=u.replace(s,"$1 $2"),u=u.replace(c,"$1 $3"),u=u.replace(d,"$1 $2 $3"),u=u.replace(p,"$1 $2 $3");var f=u,a=u.replace(l,"$1 $2 $4");return u=a,f===a&&(u=u.replace(g,"$1 $2"),u=u.replace(h,"$1 $2")),u=u.replace(v,"$1$3$5"),u=u.replace(b,"$1$2 $3"),u=u.replace(y,"$1 $2"),u=u.replace(m,"$1 $2")}},{key:"spacingText",value:function(e){var u=arguments.length<=1||void 0===arguments[1]?function(){}:arguments[1];try{var f=this.spacing(e);u(null,f)}catch(a){u(a)}}}]),e}(),N=new $;u=e.exports=N,u.Pangu=$}])}); 10 | //# sourceMappingURL=pangu.min.js.map -------------------------------------------------------------------------------- /src/vender/mduikit/button.jsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Material Design: Button 3 | * 4 | * @version : 0.0.2 5 | * @update : 2017/10/14 6 | * @homepage: https://github.com/kenshin/react-md-ui-ui 7 | * @license : MIT https://github.com/kenshin/react-md/blob/master/LICENSE 8 | * @author : Kenshin Wang 9 | * 10 | * @copyright 2017 11 | */ 12 | 13 | console.log( "==== simpread component: Button ====" ) 14 | 15 | let current = {}, $mask, style, styles = new Map(); 16 | 17 | const raisedstyle = { 18 | color : "rgba(255, 255, 255, .7)", 19 | backgroundColor : "rgba(0, 137, 123, 1)", 20 | hoverColor : "rgba( 255, 255, 255, .4)", 21 | }, 22 | flatstyle = { 23 | color : "rgba(0, 137, 123, .8)", 24 | backgroundColor : "transparent", 25 | hoverColor : "rgba( 153, 153, 153, .4)" 26 | }, 27 | secondary = { 28 | flat: { 29 | opacity: 0.6, 30 | }, 31 | raised: { 32 | backgroundColor: "rgba( 153, 153, 153, .2)", 33 | } 34 | }, 35 | disable = { 36 | flat: { 37 | cursor: "no-drop", 38 | color: "rgba(0, 0, 0, 0.298039)", 39 | }, 40 | raised: { 41 | cursor: "no-drop", 42 | color: "rgba(0, 0, 0, 0.298039)", 43 | backgroundColor: "rgb(229, 229, 229)", 44 | boxShadow: "none", 45 | } 46 | } 47 | 48 | const cssinjs = () => { 49 | const styles = { 50 | 51 | root: {}, 52 | normal_root : { 53 | display: 'block', 54 | 55 | minWidth: '88px', 56 | height: '36px', 57 | 58 | margin: '6px', 59 | padding: 0, 60 | 61 | fontFamily: 'sans-serif', 62 | textDecoration: 'none', 63 | 64 | cursor: 'pointer', 65 | 66 | border: 'none', 67 | borderRadius: '2px', 68 | }, 69 | 70 | mask: { 71 | display: '-webkit-flex', 72 | justifyContent: 'center', 73 | alignItems: 'center', 74 | 75 | width: '100%', 76 | height: '100%', 77 | 78 | margin: 0, 79 | padding: '0 8px', 80 | 81 | border: 'none', 82 | borderRadius: '2px', 83 | boxSizing: 'border-box', 84 | transition: 'all .5s ease-in-out', 85 | 86 | backgroundColor: 'transparent', 87 | }, 88 | 89 | raised: { 90 | boxShadow: '0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2)', 91 | }, 92 | 93 | flat: { 94 | fontWeight: 400, 95 | }, 96 | 97 | span : { 98 | display: 'flex', 99 | alignItems: 'center', 100 | 101 | userSelect: 'none', 102 | }, 103 | 104 | text: { 105 | padding: '0 8px 0', 106 | 107 | textDecoration: 'none', 108 | textAlign: 'center', 109 | letterSpacing: '.5px', 110 | 111 | fontSize: '15px', 112 | lineHeight: '1', 113 | }, 114 | 115 | icon: { 116 | order: -1, 117 | display: 'block', 118 | 119 | width: '24px', 120 | height: '24px', 121 | 122 | border: 'none', 123 | backgroundPosition: 'center', 124 | backgroundRepeat: 'no-repeat', 125 | }, 126 | 127 | circle: { 128 | borderRadius: '50%', 129 | }, 130 | 131 | } 132 | 133 | return styles; 134 | } 135 | 136 | /** 137 | * Custom tag component: Button 138 | * 139 | * Reference: 140 | * - https://material.io/guidelines/components/buttons.html 141 | * - https://material.io/guidelines/components/buttons-floating-action-button.html 142 | * - http://www.material-ui.com/#/components/flat-button 143 | * - http://www.material-ui.com/#/components/raised-button 144 | * - http://www.material-ui.com/#/components/icon-button 145 | * 146 | * @class 147 | */ 148 | export default class Button extends React.Component { 149 | 150 | static defaultProps = { 151 | href : "javascript:;", 152 | target : "_self", 153 | text : "", 154 | disable : false, 155 | icon : "", 156 | order : "before", 157 | type : "flat", 158 | mode : "primary", 159 | shape : "rect", 160 | style : undefined, 161 | color : "", 162 | width : undefined, 163 | backgroundColor : "", 164 | hoverColor : "", 165 | tooltip : {}, 166 | waves : undefined, 167 | } 168 | 169 | static propTypes = { 170 | href : React.PropTypes.string, 171 | target : React.PropTypes.string, 172 | text : React.PropTypes.string, 173 | disable : React.PropTypes.bool, 174 | icon : React.PropTypes.string, 175 | order : React.PropTypes.oneOf([ "before", "after" ]), 176 | type : React.PropTypes.oneOf([ "flat", "raised" ]), 177 | mode : React.PropTypes.oneOf([ "primary", "secondary" ]), 178 | shape : React.PropTypes.oneOf([ "rect", "circle" ]), 179 | style : React.PropTypes.object, 180 | width : React.PropTypes.string, 181 | color : React.PropTypes.string, 182 | backgroundColor : React.PropTypes.string, 183 | hoverColor : React.PropTypes.string, 184 | tooltip : React.PropTypes.object, 185 | waves : React.PropTypes.string, 186 | onClick : React.PropTypes.func, 187 | } 188 | 189 | state = { 190 | id : Math.round(+new Date()), 191 | color: ((bool)=>bool ? flatstyle.color : raisedstyle.color)( this.props.type != "raised" ), 192 | backgroundColor: ((bool)=>bool ? flatstyle.backgroundColor : raisedstyle.backgroundColor)( this.props.type != "raised" ), 193 | hoverColor: ((bool)=>bool ? flatstyle.hoverColor : raisedstyle.hoverColor)( this.props.type != "raised" ), 194 | } 195 | 196 | onMouseOver() { 197 | [ style, $mask ] = [ styles.get( this.state.id ), $( this.refs.mask ) ]; 198 | $mask.css( "background-color", this.state.hoverColor ); 199 | } 200 | 201 | onMouseOut() { 202 | [ style, $mask ] = [ styles.get( this.state.id ), $( this.refs.mask ) ]; 203 | $mask.css({ ...style.mask }); 204 | } 205 | 206 | onClick( event ) { 207 | this.props.onClick && this.props.onClick( event ); 208 | } 209 | 210 | componentWillMount() { 211 | this.props.color != "" && this.setState({ color: this.props.color }); 212 | this.props.backgroundColor != "" && this.setState({ backgroundColor: this.props.backgroundColor }); 213 | this.props.hoverColor != "" && this.setState({ hoverColor: this.props.hoverColor }); 214 | } 215 | 216 | render() { 217 | styles.set( this.state.id, { ...cssinjs() } ); 218 | style = styles.get( this.state.id ); 219 | 220 | current = this.props.type == "raised" ? { ...style.raised } : { ...style.flat }; 221 | current.color = this.state.color; 222 | current.backgroundColor = this.state.backgroundColor; 223 | 224 | if ( this.props.text == "" && this.props.icon != "" ) { 225 | delete style.normal_root.minWidth; 226 | delete style.normal_root.borderRadius; 227 | style.normal_root.width = style.normal_root.height; 228 | } 229 | 230 | this.props.shape == "circle" && 231 | ( current = { ...current, ...style.circle } ); 232 | 233 | this.props.shape == "circle" && this.props.width && 234 | ( current.height = this.props.width ); 235 | 236 | this.props.mode == "secondary" && 237 | Object.keys( secondary[ this.props.type ] ).forEach( key => style.mask[ key ] = secondary[ this.props.type ][ key ] ); 238 | 239 | this.props.disable && 240 | Object.keys( disable[ this.props.type ] ).forEach( key => current[ key ] = disable[ this.props.type ][ key ] ); 241 | 242 | style.root = { ...style.normal_root, ...current }; 243 | 244 | this.props.style && 245 | ( style.root = { ...style.root, ...this.props.style } ); 246 | 247 | this.props.text == "" && ( style.text.display = "none" ); 248 | this.props.icon != "" ? ( style.icon.backgroundImage = `url(${this.props.icon})` ) : ( style.icon.display = "none" ); 249 | this.props.order == "after" && ( style.icon.order = 1 ); 250 | this.props.width && ( style.root.width = this.props.width ); 251 | 252 | const events = this.props.disable ? {} : { 253 | onMouseOver : ()=>this.onMouseOver(), 254 | onMouseOut : ()=>this.onMouseOut(), 255 | onClick : (e)=>this.onClick(e), 256 | }, 257 | tooltip = this.props.tooltip; 258 | 259 | return ( 260 | 265 | 266 | 267 | 268 | { this.props.text } 269 | 270 | 271 | 272 | ) 273 | } 274 | } -------------------------------------------------------------------------------- /src/vender/notify/notify.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* 4 | * Options: 5 | * - title ( string, optional, if value is "" not show.) 6 | * 7 | * - content ( string, required) 8 | * 9 | * - type ( int, NORMAL/SUCCESS/WARING/ERROR) 10 | * ( optional, default is NORMAL ) 11 | * 12 | * - mode ( string, toast/modal/snackbar) 13 | * ( optional, default is toast ) 14 | * 15 | * - delay ( boolean, optional ) 16 | * ( default is 1000 * 5 ) 17 | * 18 | * - icon ( string, optional ) 19 | * 20 | * - action ( string, optional ) 21 | * - callback( func, optional ) 22 | * ( when action != "" must set callback ) 23 | * 24 | * Param: 25 | * - string: 26 | * - 1:content 27 | * - 2:type content or title content 28 | * 29 | * - object 30 | * - { type: xxx, title: xxx, content: xxx, mode: xxx, icon: xxx, delay: 500, action: xxx, callback:()=>{xxxx} } 31 | * 32 | * Example: 33 | * new Notify().Render( "一个参数的 toast" ); 34 | * new Notify().Render( 0, "两个参数的 toast" ); 35 | * new Notify().Render( 1, "两个参数的 toast" ); 36 | * new Notify().Render( 2, "两个参数的 toast" ); 37 | * new Notify().Render( 3, "两个参数的 toast" ); 38 | * new Notify().Render( "snackbar", "两个参数的 snackbar" ); 39 | * new Notify().Render( "三个参数的 callback", "undo", ()=>{console.log("bbbbbb")} ); 40 | * new Notify().Render( "snackbar", "四个参数的 snackbar callback", "undo", ()=>{console.log("rrrrrr")} ); 41 | * new Notify().Render( "SimpTab 版本提示", `已更新到最新版本,详细请看 CHANGELOG` ); 42 | * new Notify().Render({ content: "带 icon 的 toast", icon: "/weight_icon.png" } ); 43 | * new Notify().Render({ content: "带 delay 的 toast", delay: 10000 } ); 44 | * new Notify().Render({ content: "带 icon 的 snackbar", icon: "/fontsize_icon.png" }); 45 | * new Notify().Render({ content: "带 callback 的 toast", icon: "/icon.png", mode: "snackbar", action: "提交", callback: ()=>{console.log("dddddddd")}} ); 46 | * new Notify().Render( "错误的 callback", "undo", '()=>{console.log("eeeeeeee")}' ); 47 | * 48 | */ 49 | var Notify = ( function () { 50 | var VERSION = "2.0.0", 51 | name = "notify", 52 | root = "notify-gp", 53 | roottmpl= "<" + root + ">", 54 | num = 0, 55 | NORMAL = 0, 56 | SUCCESS = 1, 57 | WARNING = 2, 58 | ERROR = 3, 59 | MODE = { 60 | toast : "toast", 61 | modal : "modal", 62 | snackbar : "snackbar", 63 | }, 64 | options = { 65 | version : VERSION, 66 | title : "", 67 | content : "", 68 | type : NORMAL, 69 | mode : MODE.toast, 70 | delay : 1000 * 5, 71 | icon : "", 72 | action : "", 73 | callback: undefined, 74 | }, 75 | timer = {}, 76 | $root, 77 | TMPL = '\ 78 | \ 79 | \ 80 | \ 81 | \ 82 | \ 83 | \ 84 | ', 85 | prefix = function( value ) { 86 | return name + "-" + value; 87 | }, 88 | registyElement = function( name, elements ) { 89 | elements.forEach( function( item ) { 90 | document.createElement( prefix( item )); 91 | }); 92 | }, 93 | closeHandle = function( event ) { 94 | $root.off( "click", "." + event.data + " notify-a", closeHandle ); 95 | hidden( $(this).parent() ); 96 | }, 97 | delayHandler = function( item ) { 98 | clearTimeout( timer[item] ); 99 | delete timer[item]; 100 | hidden( this ); 101 | }, 102 | callbackHander = function( event ) { 103 | event.data[1](); 104 | $root.off( "click", "." + event.data[0] + " notify-action", callbackHander ); 105 | hidden( $(this).parent() ); 106 | }, 107 | hidden = function( target ) { 108 | target.addClass( "notify-hide" ).slideUp( 500, function() { 109 | target.remove(); 110 | if ($root.children().length === 0 ) $root.css( "z-index", 0 ); 111 | }); 112 | }, 113 | render = function() { 114 | var $target = $( TMPL ), 115 | $title = $target.find(prefix( "title" )), 116 | $content = $target.find(prefix( "content" )), 117 | $close = $target.find(prefix( "a" )), 118 | $icon = $target.find(prefix( "i" )), 119 | $action = $target.find(prefix( "action" )), 120 | item = "notify-item-" + num++; 121 | 122 | this.title ? $title.text( this.title ) : $title.hide(); 123 | this.content ? $content.html( this.content ) : $content.hide(); 124 | 125 | if ( this.mode === MODE.modal ) { 126 | $target.addClass( "notify-modal" ); 127 | $content.addClass( "notify-modal-content" ); 128 | $root.on( "click", "." + item + " notify-a", item, closeHandle ); 129 | } else { 130 | $close.hide(); 131 | this.mode == MODE.snackbar && $target.addClass( "notify-snackbar" ); 132 | } 133 | 134 | this.mode !== MODE.modal && this.icon !== "" && 135 | $icon.css({ "background-image": "url(" + this.icon + ")", "display": "block" }); 136 | 137 | switch( this.type ) { 138 | case 1: 139 | $content.addClass( "notify-success" ); 140 | break; 141 | case 2: 142 | $content.addClass( "notify-warning" ); 143 | break; 144 | case 3: 145 | $content.addClass( "notify-error" ); 146 | break; 147 | } 148 | 149 | if ( this.action !== "" && this.callback && typeof this.callback == "function" ) { 150 | $content.css( "width", "100%" ); 151 | $action.text( this.action ).css( "display", "block" ); 152 | $root.on( "click", "." + item + " notify-action", [ item, this.callback ], callbackHander ); 153 | } 154 | 155 | this.mode !== MODE.modal && ( this.action == "" || !this.callback || typeof this.callback != "function" ) && 156 | ( timer[item] = setTimeout( delayHandler.bind( $target, item ), this.delay ) ); 157 | 158 | $target.addClass( item ); 159 | $root.append( $target ).css( "z-index", 2147483647 ); 160 | this.mode == MODE.snackbar && $target.css( "margin-left", "-" + $target.width()/2 + "px" ); 161 | setTimeout( function() { $target.addClass( "notify-show" ); }, 200 ); 162 | }; 163 | 164 | function Notify() { 165 | registyElement( name, [ "gp", "div", "a", "span", "title", "content", "i" ] ); 166 | if ( $( "html" ).find ( root ).length == 0 ) { 167 | $( "html" ).append( roottmpl ); 168 | $root = $( root ); 169 | } 170 | } 171 | 172 | Notify.prototype.title = options.title; 173 | Notify.prototype.content = options.content; 174 | Notify.prototype.type = options.type; 175 | Notify.prototype.mode = options.mode; 176 | Notify.prototype.delay = options.delay; 177 | Notify.prototype.icon = options.icon; 178 | Notify.prototype.action = options.action; 179 | Notify.prototype.callback= options.callback; 180 | 181 | Notify.prototype.Render = function () { 182 | 183 | var self = this; 184 | 185 | if ( arguments.length === 1 && typeof arguments[0] === "object" ) { 186 | options = arguments[0]; 187 | 188 | Object.keys( options ).forEach( function( item ) { 189 | self[item] = options[item]; 190 | }); 191 | 192 | render.bind( self )(); 193 | } 194 | else if ( typeof arguments[0] !== "object" && arguments.length > 0 && arguments.length < 5 ) { 195 | switch ( arguments.length ) { 196 | case 1: 197 | this.content = arguments[0]; 198 | break; 199 | case 2: 200 | if ( arguments[0] == MODE.snackbar ) { 201 | this.mode = arguments[0]; 202 | } 203 | else if ( typeof arguments[0] == "number" ) { 204 | this.type = arguments[0]; 205 | } else { 206 | this.mode = MODE.modal, 207 | this.title = arguments[0]; 208 | } 209 | this.content = arguments[1]; 210 | break; 211 | case 3: 212 | this.content = arguments[0]; 213 | this.action = arguments[1]; 214 | this.callback = arguments[2]; 215 | break; 216 | case 4: 217 | if ( arguments[0] == MODE.snackbar ) { 218 | this.mode = arguments[0]; 219 | this.content = arguments[1]; 220 | this.action = arguments[2]; 221 | this.callback = arguments[3]; 222 | } 223 | break; 224 | } 225 | render.bind( self )(); 226 | } 227 | else { 228 | console.error( "Arguments error", arguments ); 229 | } 230 | }; 231 | 232 | return Notify; 233 | 234 | })(); 235 | 236 | module.exports = Notify; -------------------------------------------------------------------------------- /src/vender/mduikit/tooltip.jsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Material Design: Tooltip 3 | * 4 | * @version : 0.0.1 5 | * @update : 2017/04/25 6 | * @homepage: https://github.com/kenshin/react-md-ui 7 | * @license : MIT https://github.com/kenshin/react-md/blob/master/LICENSE 8 | * @author : Kenshin Wang 9 | 10 | * @reference: 11 | * - https://material.io/guidelines/components/tooltips.html 12 | * - http://materializecss.com/dialogs.html 13 | * 14 | * @copyright 2017 15 | */ 16 | 17 | console.log( "==== simpread component: ToolTip ====" ) 18 | 19 | let started = false, timeout, $target, $back, style, styles = new Map(); 20 | const cssinjs = () => { 21 | const styles = { 22 | 23 | root : { 24 | display: 'flex', 25 | alignItems: 'center', 26 | position: 'absolute', 27 | 28 | top: 0, 29 | left: 0, 30 | padding: '5px 16px', 31 | 32 | minHeight: '32px', 33 | maxHeight: '150px', 34 | maxWidth: '400px', 35 | 36 | fontSize: '14px', 37 | textAlign: 'center', 38 | 39 | zIndex: 2000, 40 | 41 | color: '#fff', 42 | backgroundColor: 'transparent', 43 | 44 | borderRadius: '2px', 45 | 46 | pointerEvents: 'none', 47 | 48 | opacity: 0, 49 | overflow: 'hidden', 50 | visibility: 'hidden', 51 | }, 52 | 53 | back: { 54 | position: 'absolute', 55 | 56 | width: '14px', 57 | height: '7px', 58 | 59 | backgroundColor: 'rgba(97, 97, 98, .9)', 60 | 61 | borderRadius: '0 0 50% 50%', 62 | 63 | zIndex: -1, 64 | 65 | transformOrigin: '50% 0%', 66 | 67 | opacity: 0, 68 | visibility: 'hidden', 69 | }, 70 | } 71 | 72 | return styles; 73 | } 74 | 75 | /** 76 | * Custom component: Tooltip, component e.g. 77 | 78 | 79 | 80 | 关闭 81 |
82 |
83 |
84 | 85 | 86 | 更多 87 |
88 |
89 |
90 |
91 | * 92 | * @class 93 | */ 94 | class ToolTip extends React.Component { 95 | 96 | static defaultProps = { 97 | root : "", 98 | text : "", 99 | position : "bottom", 100 | delay : 350, 101 | $item : undefined, 102 | } 103 | 104 | static propTypes = { 105 | root : React.PropTypes.string, 106 | text : React.PropTypes.string, 107 | position : React.PropTypes.oneOf([ "bottom", "top", "left", "right" ]), 108 | delay : React.PropTypes.number, 109 | $item : React.PropTypes.any, 110 | } 111 | 112 | state = { 113 | id : Math.round(+new Date()), 114 | } 115 | 116 | onMouseEnter() { 117 | const showTooltip = ()=> { 118 | started = true; 119 | [ $target, $back ] = [ $( this.refs.target ), $( this.refs.back ) ]; 120 | 121 | $target.velocity( "stop" ); 122 | $back.velocity( "stop" ); 123 | $target.css({ visibility: "visible", left: "0px", top: "0px" }); 124 | 125 | const originWidth = this.props.$item.outerWidth(), 126 | originHeight = this.props.$item.outerHeight(), 127 | tooltipHeight = $target.outerHeight(), 128 | tooltipWidth = $target.outerWidth(), 129 | backWidth = $back[0].offsetWidth, 130 | backHeight = $back[0].offsetHeight; 131 | 132 | let tooltipVert = "0px", tooltipHori = "0px", 133 | scaleXFactor = 8, scaleYFactor = 8, scaleFactor = 0, 134 | targetTop, targetLeft, top, left; 135 | 136 | /*if ( this.props.$item.css( "position" ) == "static" ) { 137 | top = this.props.$item.position().top; 138 | left = this.props.$item.position().left; 139 | } else { 140 | top = this.props.$item.offset().top; 141 | left = this.props.$item.offset().left; 142 | }*/ 143 | 144 | top = this.props.$item[0].getBoundingClientRect().top; 145 | left = this.props.$item[0].getBoundingClientRect().left; 146 | 147 | if ( this.props.position == "bottom" ) { 148 | tooltipVert = "+14px"; 149 | targetTop = top + originHeight; 150 | targetLeft = left + ( originWidth - tooltipWidth ) / 2; 151 | } else if ( this.props.position == "top" ) { 152 | tooltipVert = '-14px'; 153 | targetTop = top - tooltipHeight; 154 | targetLeft = left + ( originWidth - tooltipWidth ) / 2; 155 | } else if ( this.props.position == "left" ) { 156 | tooltipHori = '-14px'; 157 | targetTop = top + ( originHeight - tooltipHeight ) / 2; 158 | targetLeft = left - tooltipWidth; 159 | } else { 160 | tooltipHori = '+14px'; 161 | targetTop = top + ( originHeight - tooltipHeight ) / 2; 162 | targetLeft = left + originWidth + Number.parseInt( tooltipHori ) - Number.parseInt( $target.css( "padding-left" )); 163 | } 164 | 165 | $back.css({ 166 | top: 0, 167 | left: 0, 168 | marginLeft: ( tooltipWidth - backWidth ) / 2 169 | }); 170 | 171 | $target.css({ 172 | top: targetTop + ( $( this.props.root ).css( "position" ) != "fixed" ? $( "body" ).scrollTop() : 0 ), 173 | left: targetLeft + ( $( this.props.root ).css( "position" ) != "fixed" ? $( "body" ).scrollLeft(): 0 ), 174 | }); 175 | 176 | scaleXFactor = Math.SQRT2 * tooltipWidth / parseInt( backWidth ); 177 | scaleYFactor = Math.SQRT2 * tooltipHeight / parseInt( backHeight ); 178 | scaleFactor = Math.max( scaleXFactor, scaleYFactor ); 179 | 180 | $target.velocity({ translateY: tooltipVert, translateX: tooltipHori }, { duration: 350, queue: false }) 181 | .velocity({ opacity: 1 }, { duration: 300, delay: 50, queue: false }); 182 | $back.css({ visibility: "visible" }) 183 | .velocity({ opacity: 1 }, { duration: 55, delay: 0, queue: false }) 184 | .velocity({ scaleX: scaleFactor, scaleY: scaleFactor }, { duration: 300, delay: 0, queue: false, easing: "easeInOutQuad" }); 185 | }; 186 | timeout = setTimeout( showTooltip, this.props.delay ); 187 | } 188 | 189 | onMouseLeave() { 190 | started = false; 191 | const delay = 225; 192 | clearTimeout( timeout ); 193 | setTimeout( () => { 194 | [ $target, $back ] = [ $( this.refs.target ), $( this.refs.back ) ]; 195 | $target.velocity({ 196 | opacity: 0, translateY: 0, translateX: 0}, { duration: delay, queue: false }); 197 | $back.velocity({opacity: 0, scaleX: 1, scaleY: 1}, { 198 | duration: delay, 199 | queue: false, 200 | complete: () => { 201 | $back.css({ visibility: "hidden" }); 202 | $target.css({ visibility: "hidden" }); 203 | started = false; 204 | } 205 | }); 206 | }, delay ); 207 | } 208 | 209 | componentDidMount() { 210 | this.props.$item.on( "mouseenter", this.onMouseEnter.bind( this ) ); 211 | this.props.$item.on( "mouseleave", this.onMouseLeave.bind( this ) ); 212 | } 213 | 214 | componentWillUnmount() { 215 | this.props.$item.off( "mouseenter", this.onMouseEnter ); 216 | this.props.$item.off( "mouseleave", this.onMouseLeave ); 217 | } 218 | 219 | render() { 220 | styles.set( this.state.id, cssinjs() ); 221 | style = styles.get( this.state.id ); 222 | 223 | return ( 224 | 225 | { this.props.text } 226 |
227 |
228 | ) 229 | } 230 | 231 | } 232 | 233 | /** 234 | * Render 235 | * 236 | * @param {string} element, e.g. class: .xxx; id: #xxxx; tag: xxx 237 | */ 238 | function Render( root ) { 239 | setTimeout( () => { 240 | const $root = $( root ); 241 | $root.find( "[data-tooltip]" ).map( ( idx, item )=>{ 242 | const $item = $(item), 243 | position = $item.attr( "data-tooltip-position" ), 244 | delay = $item.attr( "data-tooltip-delay" ), 245 | text = $item.attr( "data-tooltip" ); 246 | text && text != "" && 247 | ReactDOM.render( , getTooltipRoot( $root ) ); 248 | }); 249 | }, 1000 ); 250 | } 251 | 252 | /** 253 | * Exit 254 | * 255 | * @param {string} element, e.g. class: .xxx; id: #xxxx; tag: xxx 256 | */ 257 | function Exit( root ) { 258 | $( root ).find( "tooltip-tips" ).map( ( idx, item )=>{ 259 | ReactDOM.unmountComponentAtNode( $(item)[0] ); 260 | }); 261 | } 262 | 263 | /** 264 | * Create Tooltip root html 265 | * 266 | * @param {jquery} jquery object 267 | * @return {element} html element 268 | */ 269 | function getTooltipRoot( $root ) { 270 | $root.find( "tooltip-gp" ).length == 0 && $root.append( "" ); 271 | $root.find( "tooltip-gp" ).append( "" ); 272 | return $( "tooltip-tips" ).last()[0]; 273 | } 274 | 275 | export { Render, Exit }; -------------------------------------------------------------------------------- /src/assets/css/search.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Search page 3 | */ 4 | 5 | :root { 6 | --max-width: 800px; 7 | --pure-max-width: 832px; 8 | --search-primary-color: rgba( 255, 255, 255, 1 ); 9 | --offset: 187; 10 | --duration: 1.4s; 11 | } 12 | 13 | .searchpage { 14 | display: flex; 15 | flex-direction: column; 16 | 17 | width: 100%; 18 | } 19 | 20 | .searchpage .top { 21 | position: relative; 22 | 23 | display: flex; 24 | flex-direction: row; 25 | justify-content: center; 26 | align-items: center; 27 | 28 | width: 100%; 29 | min-height: 150px; 30 | 31 | background-color: var(--primary-color); 32 | box-shadow: var( --shadow ); 33 | } 34 | 35 | .searchpage .top .logo { 36 | width: 100%; 37 | text-align: right; 38 | } 39 | 40 | .searchpage .top .logo img { 41 | padding-right: 20px; 42 | } 43 | 44 | .searchpage .top .placeholder { 45 | width: 100%; 46 | } 47 | 48 | .searchpage .searchbar { 49 | min-width: var(--pure-max-width); 50 | } 51 | 52 | .searchpage .searchbar .search { 53 | position: relative; 54 | } 55 | 56 | .searchpage .searchbar .bar { 57 | display: block; 58 | position: absolute; 59 | 60 | right: 0; 61 | bottom: 5px; 62 | 63 | width: 35px; 64 | height: 35px; 65 | } 66 | 67 | .searchpage .searchbar .arrow { 68 | display: block; 69 | position: absolute; 70 | 71 | right: 35px; 72 | bottom: 5px; 73 | 74 | width: 35px; 75 | height: 35px; 76 | } 77 | 78 | .searchpage .searchbar input { 79 | line-height: 17px; 80 | font-size: 17px!important; 81 | color: var(--search-primary-color) !important; 82 | } 83 | 84 | .searchpage .searchbar input::-webkit-input-placeholder { 85 | color: var(--search-primary-color); 86 | } 87 | 88 | .searchpage .searchbar text-field text-field-state { 89 | border-top: none color(var(--search-primary-color) alpha(-20%)) !important; 90 | border-left: none color(var(--search-primary-color) alpha(-20%)) !important; 91 | border-right: none color(var(--search-primary-color) alpha(-20%)) !important; 92 | border-bottom: 2px solid color(var(--search-primary-color) alpha(-20%)) !important; 93 | } 94 | 95 | .searchpage .cost { 96 | display: flex; 97 | justify-content: center; 98 | 99 | width: 100%; 100 | } 101 | 102 | .searchpage .cost span { 103 | padding: 10px 0; 104 | width: var(--pure-max-width); 105 | font-size: .9rem; 106 | font-weight: 400; 107 | } 108 | 109 | .searchpage .searchresults { 110 | display: flex; 111 | flex-direction: column; 112 | align-items: center; 113 | 114 | width: 100%; 115 | } 116 | 117 | .searchpage .searchresults .resultcard { 118 | display: flex; 119 | flex-direction: column; 120 | align-items: center; 121 | 122 | margin: 0 20%; 123 | margin-top: 30px; 124 | padding: 16px; 125 | 126 | width: var(--max-width); 127 | 128 | color: color( var(--text-color) alpha(-13%)); 129 | background-color: var(--search-primary-color); 130 | 131 | border-radius: 2px; 132 | box-shadow: var( --shadow ); 133 | } 134 | 135 | .searchpage .searchresults .resultcard:first-child { 136 | margin-top: 0; 137 | } 138 | 139 | .searchpage .searchresults .resultcard:hover { 140 | transition: all 450ms 0ms; 141 | box-shadow: 1px 1px 8px rgba(0, 0, 0, .16); 142 | } 143 | 144 | .searchpage .searchresults .resultcard .title { 145 | @apply --ellipsis; 146 | 147 | width: 100%; 148 | font-size: 1.4rem; 149 | } 150 | 151 | .searchpage .searchresults .resultcard .title a { 152 | color: var(--text-color); 153 | text-decoration: none; 154 | } 155 | 156 | .searchpage .searchresults .resultcard .desc { 157 | @apply --ellipsis; 158 | -webkit-line-clamp: 3; 159 | 160 | padding: 16px 0; 161 | width: 100%; 162 | height: auto; 163 | max-height: 73px; 164 | 165 | color: #333333; 166 | font-size: 1rem; 167 | } 168 | 169 | .searchpage .searchresults .resultcard .details { 170 | display: flex; 171 | flex-direction: row; 172 | 173 | width: 100%; 174 | 175 | color: color( var(--text-color) alpha(-46%)); 176 | font-size: .8rem; 177 | } 178 | 179 | .searchpage .searchresults .resultcard .details a { 180 | color: var(--primary-color); 181 | 182 | text-transform: capitalize; 183 | text-decoration: none; 184 | } 185 | 186 | .searchpage .searchresults .empty, 187 | .searchpage .searchresults .loading { 188 | display: flex; 189 | flex-direction: column; 190 | justify-content: center; 191 | align-items: center; 192 | 193 | width: 100%; 194 | height: 100%; 195 | min-height: 200px; 196 | 197 | font-weight: 400; 198 | font-size: 1.2rem; 199 | } 200 | 201 | .searchpage .searchresults .empty .bg { 202 | display: block; 203 | 204 | margin-bottom: 20px; 205 | 206 | width: 440px; 207 | height: 351px; 208 | 209 | background-position: center; 210 | background-repeat: no-repeat; 211 | background-image: url( '../icons/404.png' ); 212 | } 213 | 214 | .searchpage .paging { 215 | display: flex; 216 | justify-content: center; 217 | 218 | margin-top: 30px; 219 | 220 | width: 100%; 221 | } 222 | 223 | .searchpage .paging a { 224 | width: var(--pure-max-width); 225 | } 226 | 227 | .searchpage .pagingbg { 228 | display: flex; 229 | justify-content: center; 230 | 231 | margin-top: 30px; 232 | width: 100%; 233 | } 234 | 235 | .searchpage .pagingbg .paginghr { 236 | display: flex; 237 | flex-direction: row; 238 | align-items: center; 239 | 240 | width: var(--pure-max-width); 241 | } 242 | 243 | .searchpage .pagingbg .paginghr .page { 244 | width: 450px; 245 | color: color( var(--text-color) alpha(-46%)); 246 | 247 | text-align: center; 248 | font-weight: 500; 249 | } 250 | 251 | .searchpage .pagingbg .paginghr .divider { 252 | width: 100%; 253 | height: 1px; 254 | border-bottom: 1px solid color( var(--text-color) alpha(-88%)); 255 | } 256 | 257 | /** 258 | * Loading bar 259 | */ 260 | 261 | .searchpage .searchresults .loading .spinner { 262 | animation: rotator var(--duration) linear infinite; 263 | } 264 | 265 | @keyframes rotator { 266 | 0% { transform: rotate(0deg); } 267 | 100% { transform: rotate(270deg); } 268 | } 269 | 270 | .searchpage .searchresults .loading .path { 271 | stroke-dasharray: var(--offset); 272 | stroke-dashoffset: 0; 273 | transform-origin: center; 274 | animation: 275 | dash var(--duration) ease-in-out infinite, 276 | colors calc(var(--duration)*4) ease-in-out infinite; 277 | } 278 | 279 | @keyframes colors { 280 | 0% { stroke: #4285F4; } 281 | 25% { stroke: #DE3E35; } 282 | 50% { stroke: #F7C223; } 283 | 75% { stroke: #1B9A59; } 284 | 100% { stroke: #4285F4; } 285 | } 286 | 287 | @keyframes dash { 288 | 0% { stroke-dashoffset: var(--offset); } 289 | 50% { 290 | stroke-dashoffset: calc( var(--offset)/4 ); 291 | transform:rotate(135deg); 292 | } 293 | 100% { 294 | stroke-dashoffset: var(--offset); 295 | transform:rotate(450deg); 296 | } 297 | } 298 | 299 | /** 300 | * Footer 301 | */ 302 | 303 | .footer { 304 | display: flex; 305 | flex-direction: column; 306 | justify-content: center; 307 | align-items: center; 308 | 309 | margin-top: 30px; 310 | min-height: 200px; 311 | 312 | color: var(--search-primary-color); 313 | background-color: #37474F; 314 | 315 | font-size: .8rem; 316 | } 317 | 318 | .footer a { 319 | padding-bottom: 5px; 320 | 321 | font-weight: 500; 322 | 323 | color: var(--search-primary-color); 324 | text-decoration: none; 325 | 326 | transition: border-color .5s; 327 | border-bottom: 2px solid transparent; 328 | } 329 | 330 | .footer .groups { 331 | display: flex; 332 | flex-direction: row; 333 | justify-content: space-between; 334 | 335 | padding-bottom: 10px; 336 | 337 | width: var(--max-width); 338 | } 339 | 340 | .footer .groups .links { 341 | display: flex; 342 | flex-direction: column; 343 | align-items: flex-start; 344 | } 345 | 346 | .footer .groups .links a:hover { 347 | color: color( var(--search-primary-color) alpha(-10%)); 348 | text-decoration: none; 349 | } 350 | 351 | .footer .groups .links:nth-of-type(1) { 352 | justify-content: center; 353 | } 354 | 355 | .footer .groups .links ul { 356 | display: flex; 357 | flex-direction: row; 358 | 359 | margin: 0; 360 | padding: 0; 361 | list-style: none; 362 | 363 | zoom: .9; 364 | } 365 | 366 | .footer .groups .links ul li .icon { 367 | display: block; 368 | 369 | width: 30px; 370 | height: 30px; 371 | 372 | padding: 5px; 373 | 374 | opacity: .4; 375 | 376 | background-repeat: no-repeat; 377 | background-position: center center; 378 | } 379 | 380 | .footer .groups .links ul li .weibo { 381 | background-image: url( '../icons/weibo.png' ); 382 | } 383 | 384 | .footer .groups .links ul li .douban { 385 | background-image: url( '../icons/douban.png' ); 386 | } 387 | 388 | .footer .groups .links ul li .twitter { 389 | background-image: url( '../icons/twitter.png' ); 390 | } 391 | 392 | .footer .groups .links ul li .facebook { 393 | background-image: url( '../icons/facebook.png' ); 394 | } 395 | 396 | .footer .groups .links ul li .gplus { 397 | background-image: url( '../icons/gplus.png' ); 398 | } 399 | 400 | .footer .groups .links ul li .telegram { 401 | background-image: url( '../icons/telegram.png' ); 402 | } 403 | 404 | .footer .groups .links .logo { 405 | display: flex; 406 | justify-content: center; 407 | 408 | width: 100%; 409 | 410 | opacity: .8; 411 | } 412 | 413 | .footer .copywrite { 414 | padding: 10px 0; 415 | 416 | width: 100%; 417 | text-align: center; 418 | } 419 | 420 | .footer .copywrite a:hover { 421 | color: color( var(--search-primary-color) alpha(-10%)); 422 | border-bottom: 2px solid var(--search-primary-color); 423 | } -------------------------------------------------------------------------------- /src/vender/mduikit/tabs.jsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Material Design: Tabs 3 | * 4 | * @version : 0.0.1 5 | * @update : 2017/04/07 6 | * @homepage: https://github.com/kenshin/react-md-ui 7 | * @license : MIT https://github.com/kenshin/react-md/blob/master/LICENSE 8 | * @author : Kenshin Wang 9 | * 10 | * @copyright 2017 11 | */ 12 | 13 | console.log( "==== simpread component: Tabs ====" ) 14 | 15 | let styles = new Map(); 16 | 17 | const color = 'rgba(255, 255, 255, .7)', 18 | secondary_color = "rgba(204, 204, 204, 1)", 19 | active_color = 'rgba(255, 255, 255, 1)', 20 | header_corlor = 'transparent'; 21 | 22 | const cssinjs = () => { 23 | const styles = { 24 | 25 | root: { 26 | display: 'block', 27 | width: '100%', 28 | }, 29 | 30 | header: { 31 | display: 'flex', 32 | justifyContent: 'space-around', 33 | alignItems: 'flex-end', 34 | 35 | position: 'relative', 36 | 37 | width: '100%', 38 | 39 | backgroundColor: header_corlor, 40 | }, 41 | 42 | label: { 43 | display: 'flex', 44 | alignItems: 'center', 45 | justifyContent: 'center', 46 | 47 | position: 'relative', 48 | 49 | padding: '0 24px', 50 | 51 | width: '100%', 52 | height: '48px', 53 | 54 | color, 55 | backgroundColor: 'transparent', 56 | 57 | fontSize: '1.4rem', 58 | textTransform: 'uppercase', 59 | 60 | }, 61 | 62 | label_active: { 63 | color: active_color, 64 | fontWeight: 500, 65 | }, 66 | 67 | link: { 68 | color: 'inherit', 69 | backgroundColor: 'transparent', 70 | 71 | overflow: 'hidden', 72 | whiteSpace: 'nowrap', 73 | textOverflow: 'ellipsis', 74 | textDecoration: 'none', 75 | }, 76 | 77 | link_disable: { 78 | color: secondary_color, 79 | cursor: 'not-allowed', 80 | }, 81 | 82 | link_icon: { 83 | display: 'flex', 84 | flexDirection: 'column', 85 | alignItems: 'center', 86 | }, 87 | 88 | icon: { 89 | display: 'block', 90 | 91 | width: '24px', 92 | height: '24px', 93 | 94 | border: 'none', 95 | backgroundPosition: 'center', 96 | backgroundRepeat: 'no-repeat', 97 | }, 98 | 99 | border: { 100 | display: 'block', 101 | position: 'absolute', 102 | 103 | left: 0, 104 | bottom: 0, 105 | 106 | width: '100%', 107 | height: '4px', 108 | 109 | backgroundColor: '#EEFF41', 110 | 111 | transform: 'scaleX(0)', 112 | transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', 113 | }, 114 | 115 | border_active: { 116 | transform: 'scaleX(1)', 117 | }, 118 | 119 | shadow: { 120 | display: 'block', 121 | position: 'absolute', 122 | 123 | left: 0, 124 | bottom: 0, 125 | 126 | width: '100%', 127 | height: '4px', 128 | 129 | boxShadow: '0 1px 2px rgba(0, 0, 0, .12), 0 2px 2px rgba(0, 0, 0, .26)', 130 | }, 131 | 132 | groups: { 133 | display: 'block', 134 | width: '100%', 135 | }, 136 | 137 | group: { 138 | display: 'none', 139 | }, 140 | 141 | group_active: { 142 | display: 'block', 143 | opacity: 1, 144 | } 145 | 146 | }; 147 | 148 | return styles; 149 | } 150 | 151 | /** 152 | * TabLabel react stateless component 153 | * 154 | * @param {object} props, include: 155 | * - idx : [PropTypes.number] index 156 | * - name : [PropTypes.string] name 157 | * - value : [PropTypes.string] value 158 | * - icon : [PropTypes.string] icon path 159 | * - active : [PropTypes.bool] active 160 | * - route : [PropTypes.string] href value 161 | * - disable : [PropTypes.string] disable 162 | * - style : [PropTypes.object] tab-label style, include: label, border, link, label_active, border_active 163 | * - waves : [PropTypes.string] material waves effect 164 | * - tooltip : [PropTypes.string] tooltip 165 | * - onClick : [PropTypes.func] onClick event 166 | */ 167 | const TabLabel = ( props ) => { 168 | const route = !props.route || props.route == "" ? "#" : props.route, 169 | disable = props.disable ? true : false, 170 | tooltip = props.tooltip.text ? props.tooltip.text : props[ props.tooltip.target ], 171 | style = props.style, 172 | target = !route.startsWith( "#" ) ? "_blank" : undefined; 173 | props.active && ( style.label = { ...style.label, ...style.label_active } ); 174 | props.active && ( style.border = { ...style.border, ...style.border_active } ); 175 | props.disable && ( style.link = { ...style.link, ...style.link_disable } ); 176 | if ( props.icon && props.icon != "" ) { 177 | style.icon.display = "block"; 178 | style.icon.backgroundImage = `url(${props.icon})`; 179 | style.link = { ...style.link, ...style.link_icon }; 180 | } else { 181 | style.icon.display = "none"; 182 | } 183 | return ( 184 | props.onClick() )}> 185 | 190 | 191 | { props.name } 192 | 193 | 194 | 195 | ); 196 | } 197 | 198 | /** 199 | * Custom tag component: Tabs, component e.g. 200 | * 201 | 202 | 203 | 204 | 共通 205 | 206 | 207 | 208 | 聚焦模式 209 | 210 | 211 | 212 | 213 | 214 | 215 | aaa 216 | 217 | 218 | bbb 219 | 220 | 221 | 222 | * 223 | * Reference: 224 | * - https://material.io/guidelines/components/tabs.html 225 | * - http://www.material-ui.com/#/components/tabs 226 | * 227 | * @class 228 | */ 229 | export default class Tabs extends React.Component { 230 | 231 | static defaultProps = { 232 | items : [], 233 | color : "", 234 | activeColor: "", 235 | bgColor : "", 236 | headerStyle: undefined, 237 | groupsStyle: undefined, 238 | borderStyle: undefined, 239 | waves : "", 240 | tooltip : "", 241 | }; 242 | 243 | static propTypes = { 244 | items : React.PropTypes.array, 245 | color : React.PropTypes.string, 246 | activeColor : React.PropTypes.string, 247 | bgColor : React.PropTypes.string, 248 | headerStyle : React.PropTypes.object, 249 | groupsStyle : React.PropTypes.object, 250 | borderStyle : React.PropTypes.object, 251 | waves : React.PropTypes.string, 252 | tooltip : React.PropTypes.string, 253 | onChange : React.PropTypes.func, 254 | }; 255 | 256 | state = { 257 | id : Math.round(+new Date()), 258 | } 259 | 260 | componentWillUnmount() { 261 | $( "tabs" ).remove(); 262 | } 263 | 264 | tabLabelOnClick() { 265 | let $target = $( event.target ); 266 | 267 | if($target.is("tab-label")) { 268 | const $a = $target.find( "a" ); 269 | $a[0] && $a[0].click(); 270 | return; 271 | } 272 | 273 | if(!$target.is('a')) { $target = $target.parent(); } 274 | 275 | const href = $target.attr('href'); 276 | if(!href.startsWith( "#" )) { return; } 277 | 278 | const style = styles.get( this.state.id ), 279 | idx = $target.attr( "id" ), 280 | value = $target.attr( "value" ), 281 | name = $target.text(), 282 | $prev = $( "tab-label[active=true]" ); 283 | 284 | $( "tab-label[active=true]" ) 285 | .attr( "active", false ).css({ ...style.label }) 286 | .find( "tab-border" ).css({ ...style.border }); 287 | 288 | $target.parent().attr( "active", true ) 289 | .css({ ...style.label, ...style.label_active }) 290 | .find( "tab-border" ).css({ ...style.border, ...style.border_active }); 291 | 292 | $( "tab-group[active=true]" ) 293 | .attr( "active", false ) 294 | .velocity({ opacity: 0 }, { complete: target => { 295 | $(target).css({ ...style.group }); 296 | $($( "tab-group" )[idx]).attr( "active", true ).css({ ...style.group, ...style.group_active }) 297 | }}); 298 | 299 | this.props.onChange && this.props.onChange( $prev, $target, event ); 300 | } 301 | 302 | render() { 303 | const style = { ...cssinjs() }; 304 | styles.set( this.state.id, style ); 305 | 306 | const { items, color, activeColor, bgColor, headerStyle, groupsStyle, borderStyle, children, ...others } = this.props; 307 | 308 | color && ( style.label.color = color ); 309 | bgColor && ( style.header.backgroundColor = bgColor ); 310 | activeColor && ( style.label_active.color = activeColor ); 311 | 312 | headerStyle && ( style.header = { ...style.header, ...headerStyle } ); 313 | groupsStyle && ( style.groups = { ...style.groups, ...groupsStyle } ); 314 | borderStyle && ( style.border = { ...style.border, ...borderStyle } ); 315 | 316 | const tabLabel = items && items.map( ( item, idx ) => { 317 | const label_style = { 318 | label : style.label, 319 | border : style.border, 320 | link : style.link, 321 | link_icon : style.link_icon, 322 | icon : style.icon, 323 | link_disable : style.link_disable, 324 | label_active : style.label_active, 325 | border_active: style.border_active, 326 | }; 327 | return this.tabLabelOnClick() } />; 331 | }), 332 | tabHeader = tabLabel && { tabLabel }; 333 | 334 | const activeIdx = items.findIndex( item=>item.active), 335 | tabGroup = children && children.map( ( item, idx ) => { 336 | const group_style = activeIdx == idx ? { ...style.group, ...style.group_active } : { ...style.group }; 337 | return { item } 338 | }), 339 | tabGroups = tabGroup && { tabGroup }; 340 | 341 | return ( 342 | 343 | { tabHeader } 344 | { tabGroups } 345 | 346 | ) 347 | } 348 | } -------------------------------------------------------------------------------- /src/vender/mduikit/textfield.jsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * React Material Design: TextField 3 | * 4 | * @version : 0.0.2 5 | * @update : 2017/10/14 6 | * @homepage: https://github.com/kenshin/react-md-ui 7 | * @license : MIT https://github.com/kenshin/react-md/blob/master/LICENSE 8 | * @author : Kenshin Wang 9 | * 10 | * @copyright 2017 11 | */ 12 | 13 | console.log( "==== simpread component: TextField ====" ) 14 | 15 | let $target, $float, $state, $border, $error, 16 | element, 17 | style, styles = new Map(); 18 | 19 | const [ MIN_ROWS, steps ] = [ 3, 24 ], 20 | cssinjs = ()=>{ 21 | 22 | const color = 'rgba(51, 51, 51, .87)', 23 | secondary_color = 'rgba(204, 204, 204, 1)', 24 | 25 | focus_color = 'rgba(0, 137, 123, .8)', 26 | border_color = 'rgba(224, 224, 224, 1)', 27 | error_color = 'rgba(244, 67, 54, 1)', 28 | 29 | margin = '8px 0 0 0', 30 | display = 'block', 31 | medium = '14px', 32 | large = '16px', 33 | lineHeight = 1.5, 34 | fontWeight = 'bold', 35 | width = '100%', 36 | styles = { 37 | hidden : 'none', 38 | root: { 39 | display, 40 | position: 'relative', 41 | margin: 0, 42 | padding: 0, 43 | 44 | width, 45 | lineHeight: 1, 46 | }, 47 | 48 | input: { 49 | color, 50 | backgroundColor: 'transparent', 51 | 52 | width, 53 | height: '20px', 54 | 55 | margin, 56 | padding: 0, 57 | 58 | fontFamily: 'sans-serif', 59 | fontSize: medium, 60 | 61 | border: 'none', 62 | outline: 'none', 63 | 64 | boxShadow: 'none', 65 | boxSizing: 'content-box', 66 | transition: 'all 0.3s', 67 | }, 68 | 69 | textarea : { 70 | position: 'relative', 71 | 72 | color, 73 | backgroundColor: 'transparent', 74 | 75 | width, 76 | height: '60px', 77 | 78 | margin, 79 | padding: 0, 80 | 81 | fontFamily: 'sans-serif', 82 | fontSize: medium, 83 | lineHeight, 84 | 85 | cursor: 'inherit', 86 | 87 | border: 'none', 88 | outline: 'none', 89 | resize: 'none', 90 | 91 | boxSizing: 'border-box', 92 | WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)', 93 | WebkitAppearance: 'textfield', 94 | }, 95 | 96 | border : { 97 | display, 98 | 99 | width, 100 | margin, 101 | 102 | borderTop: `none ${border_color}`, 103 | borderLeft: `none ${border_color}`, 104 | borderRight: `none ${border_color}`, 105 | borderBottom: `1px solid ${border_color}`, 106 | boxSizing: 'content-box', 107 | }, 108 | 109 | float : {}, 110 | 111 | float_normal : { 112 | display, 113 | position: 'absolute', 114 | 115 | margin, 116 | 117 | color: secondary_color, 118 | 119 | fontSize: medium, 120 | fontWeight: 'initial', 121 | 122 | userSelect: 'none', 123 | pointerEvents: 'none', 124 | 125 | transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', 126 | transform: 'scale(1) translate( 0px, 0px )', 127 | transformOrigin: 'left top 0px', 128 | }, 129 | 130 | float_focus : { 131 | color: focus_color, 132 | 133 | margin: `-${margin}`, 134 | 135 | fontSize: medium, 136 | fontWeight, 137 | 138 | transform: 'scale(0.75) translate( 0px, -8px )', 139 | }, 140 | 141 | float_error : { 142 | color: error_color, 143 | }, 144 | 145 | state : {}, 146 | 147 | state_normal : { 148 | display, 149 | position: 'absolute', 150 | 151 | width, 152 | margin: '-1px 0 0 0', 153 | 154 | borderTop: `none ${focus_color}`, 155 | borderLeft: `none ${focus_color}`, 156 | borderRight: `none ${focus_color}`, 157 | borderBottom: `2px solid ${focus_color}`, 158 | boxSizing: 'content-box', 159 | 160 | transform: 'scaleX(0)', 161 | transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', 162 | }, 163 | 164 | state_focus : { 165 | transform: 'scaleX(1)', 166 | }, 167 | 168 | state_error : { 169 | transform: 'scaleX(1)', 170 | borderTop: `none ${error_color}`, 171 | borderLeft: `none ${error_color}`, 172 | borderRight: `none ${error_color}`, 173 | borderBottom: `2px solid ${error_color}`, 174 | }, 175 | 176 | error : { 177 | display, 178 | position: 'relative', 179 | 180 | margin, 181 | maxWidth: '428px', 182 | 183 | fontSize: medium, 184 | fontWeight, 185 | lineHeight, 186 | textAlign: 'initial', 187 | wordWrap: 'break-word', 188 | 189 | userSelect: 'none', 190 | 191 | color: error_color, 192 | transform: 'scale(0.75) translate( -73px, 0 )', 193 | }, 194 | 195 | }; 196 | 197 | return styles; 198 | } 199 | 200 | /** 201 | * Custom /