├── test.js ├── app ├── js │ ├── book.js │ ├── category.js │ ├── index.js │ ├── jquery.base64.js │ ├── scripts │ │ ├── jquery.base64.js │ │ ├── touch.js │ │ └── jquery.jsonp.js │ └── jquery.jsonp.js ├── views │ ├── male.html │ ├── rank.html │ ├── female.html │ ├── search.html │ ├── include │ │ ├── book-header.html │ │ ├── index-bottom.html │ │ ├── header.html │ │ ├── index-free.html │ │ ├── index-self.html │ │ ├── index-top.html │ │ ├── index-hot.html │ │ ├── index-female.html │ │ ├── index-male.html │ │ └── index-recommend.html │ ├── category.html │ ├── book.html │ ├── index.html │ └── catalog.html ├── css │ ├── test.styl │ ├── test.css │ ├── reset.css │ ├── all.styl │ ├── copycode.txt │ └── all.css ├── img │ ├── back.png │ ├── female.png │ ├── male.png │ ├── rank.png │ └── category.png ├── data │ ├── data1.json │ ├── data2.json │ ├── data3.json │ └── data4.json └── 新建文本文档.txt ├── .gitignore ├── Models ├── Beauty.js └── Movie.js ├── data ├── data1.json ├── data2.json ├── data3.json └── data4.json ├── mock ├── reader │ └── data │ │ ├── data1.json │ │ ├── data2.json │ │ ├── data3.json │ │ └── data4.json ├── book │ └── 18218.json ├── bookbacket.json └── rank.json ├── Schemas ├── Beauty.js └── Movie.js ├── README.md ├── gulpfile.js ├── package.json ├── css ├── reset.css └── copycode.txt ├── service └── webAppService.js ├── util.js ├── server.js ├── js ├── jquery.base64.js └── jquery.jsonp.js └── lib └── zepto.min.js /test.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/js/book.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/male.html: -------------------------------------------------------------------------------- 1 | male.html -------------------------------------------------------------------------------- /app/views/rank.html: -------------------------------------------------------------------------------- 1 | rank.html -------------------------------------------------------------------------------- /app/views/female.html: -------------------------------------------------------------------------------- 1 | female.html -------------------------------------------------------------------------------- /app/views/search.html: -------------------------------------------------------------------------------- 1 | search.html -------------------------------------------------------------------------------- /app/css/test.styl: -------------------------------------------------------------------------------- 1 | .div 2 | display flex 3 | flex-flow row nowrap -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | app/static/* 2 | 3 | app/index.html 4 | 5 | /node_modules/* 6 | -------------------------------------------------------------------------------- /app/css/test.css: -------------------------------------------------------------------------------- 1 | .div { 2 | display: flex; 3 | flex-flow: row nowrap; 4 | } 5 | -------------------------------------------------------------------------------- /app/img/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lastIndexOf/bookstore/HEAD/app/img/back.png -------------------------------------------------------------------------------- /app/img/female.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lastIndexOf/bookstore/HEAD/app/img/female.png -------------------------------------------------------------------------------- /app/img/male.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lastIndexOf/bookstore/HEAD/app/img/male.png -------------------------------------------------------------------------------- /app/img/rank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lastIndexOf/bookstore/HEAD/app/img/rank.png -------------------------------------------------------------------------------- /app/img/category.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lastIndexOf/bookstore/HEAD/app/img/category.png -------------------------------------------------------------------------------- /Models/Beauty.js: -------------------------------------------------------------------------------- 1 | const BeautySchema = require('../Schemas/Beauty') 2 | , mongoose = require('mongoose') 3 | 4 | const Beauty = mongoose.model('beauties', BeautySchema) 5 | 6 | module.exports = Beauty 7 | -------------------------------------------------------------------------------- /app/views/include/book-header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 书籍详情 7 |
8 |
-------------------------------------------------------------------------------- /app/views/include/index-bottom.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 未完待续 5 | 6 |
7 |
8 |
-------------------------------------------------------------------------------- /Models/Movie.js: -------------------------------------------------------------------------------- 1 | const MovieSchema = require('../Schemas/Movie') 2 | , mongoose = require('mongoose') 3 | 4 | let Movie = mongoose.model('Movie', MovieSchema) 5 | , Book = mongoose.model('Book', MovieSchema) 6 | 7 | module.exports = { Movie, Book } 8 | -------------------------------------------------------------------------------- /app/js/category.js: -------------------------------------------------------------------------------- 1 | $.get('/ajax/category', (data) => { 2 | if (!data.ERROR) 3 | new Vue({ 4 | data: function() { 5 | return { 6 | male: data.data, 7 | isMale: false 8 | } 9 | } 10 | }).$mount('#category') 11 | }) 12 | -------------------------------------------------------------------------------- /data/data1.json: -------------------------------------------------------------------------------- 1 | {"msg": "\u6210\u529f", "result": 0, "jsonp": "http://html.read.duokan.com/mfsv2/secure/s010/60009/file?nonce=87e8e80bd9a84314badbd9230ff521b4&token=89GiFGpK01J7WSSnxHnjoefpgNPv-zrNCurl0z1EkRx4OZm4-aB36_TllcymXfewETa58Q9VLD9jJcC4MS7oa0uRTgC6JG9Poed648pU41U&sig=FxmsSqJuj3BM0pQ07XKq13UGJLY"} -------------------------------------------------------------------------------- /data/data2.json: -------------------------------------------------------------------------------- 1 | {"msg": "\u6210\u529f", "result": 0, "jsonp": "http://html.read.duokan.com/mfsv2/secure/s010/60009/file?nonce=74c8522cfd8546fd8e2ab346366d24d0&token=89GiFGpK01J7WSSnxHnjoU435w7sJdY_EntshdzDbZLgSoyHzWOcfbMWIqJ4TfqoETa58Q9VLD9jJcC4MS7oa0uRTgC6JG9Poed648pU41U&sig=TtG7TCFv4lKLctffbvFC-iXwIoE"} -------------------------------------------------------------------------------- /data/data3.json: -------------------------------------------------------------------------------- 1 | {"msg": "\u6210\u529f", "result": 0, "jsonp": "http://html.read.duokan.com/mfsv2/secure/s010/60009/file?nonce=1c0e98ddfdc443d48b8d4e227bf1c445&token=89GiFGpK01J7WSSnxHnjoS3l3LWUG3wT-GV19WJsgeR_-qC6G01gWwWbK91FIP3yETa58Q9VLD9jJcC4MS7oa0uRTgC6JG9Poed648pU41U&sig=YLnZSBB1As8jtzOXoFwqXVmtodA"} -------------------------------------------------------------------------------- /data/data4.json: -------------------------------------------------------------------------------- 1 | {"msg": "\u6210\u529f", "result": 0, "jsonp": "http://html.read.duokan.com/mfsv2/secure/s010/60009/file?nonce=bc14aee4f2fe4a4c8a5c7d934f239264&token=89GiFGpK01J7WSSnxHnjodEBjNZoilVyYrYcWjlgecZ8mtQUmtRvDBg185IHWPeqETa58Q9VLD9jJcC4MS7oa0uRTgC6JG9Poed648pU41U&sig=uEajgc6opUF36h7rDo3R6hB31OI"} -------------------------------------------------------------------------------- /app/data/data1.json: -------------------------------------------------------------------------------- 1 | {"msg": "\u6210\u529f", "result": 0, "jsonp": "http://html.read.duokan.com/mfsv2/secure/s010/60009/file?nonce=87e8e80bd9a84314badbd9230ff521b4&token=89GiFGpK01J7WSSnxHnjoefpgNPv-zrNCurl0z1EkRx4OZm4-aB36_TllcymXfewETa58Q9VLD9jJcC4MS7oa0uRTgC6JG9Poed648pU41U&sig=FxmsSqJuj3BM0pQ07XKq13UGJLY"} -------------------------------------------------------------------------------- /app/data/data2.json: -------------------------------------------------------------------------------- 1 | {"msg": "\u6210\u529f", "result": 0, "jsonp": "http://html.read.duokan.com/mfsv2/secure/s010/60009/file?nonce=74c8522cfd8546fd8e2ab346366d24d0&token=89GiFGpK01J7WSSnxHnjoU435w7sJdY_EntshdzDbZLgSoyHzWOcfbMWIqJ4TfqoETa58Q9VLD9jJcC4MS7oa0uRTgC6JG9Poed648pU41U&sig=TtG7TCFv4lKLctffbvFC-iXwIoE"} -------------------------------------------------------------------------------- /app/data/data3.json: -------------------------------------------------------------------------------- 1 | {"msg": "\u6210\u529f", "result": 0, "jsonp": "http://html.read.duokan.com/mfsv2/secure/s010/60009/file?nonce=1c0e98ddfdc443d48b8d4e227bf1c445&token=89GiFGpK01J7WSSnxHnjoS3l3LWUG3wT-GV19WJsgeR_-qC6G01gWwWbK91FIP3yETa58Q9VLD9jJcC4MS7oa0uRTgC6JG9Poed648pU41U&sig=YLnZSBB1As8jtzOXoFwqXVmtodA"} -------------------------------------------------------------------------------- /app/data/data4.json: -------------------------------------------------------------------------------- 1 | {"msg": "\u6210\u529f", "result": 0, "jsonp": "http://html.read.duokan.com/mfsv2/secure/s010/60009/file?nonce=bc14aee4f2fe4a4c8a5c7d934f239264&token=89GiFGpK01J7WSSnxHnjodEBjNZoilVyYrYcWjlgecZ8mtQUmtRvDBg185IHWPeqETa58Q9VLD9jJcC4MS7oa0uRTgC6JG9Poed648pU41U&sig=uEajgc6opUF36h7rDo3R6hB31OI"} -------------------------------------------------------------------------------- /mock/reader/data/data1.json: -------------------------------------------------------------------------------- 1 | {"msg": "\u6210\u529f", "result": 0, "jsonp": "http://html.read.duokan.com/mfsv2/secure/s010/60009/file?nonce=87e8e80bd9a84314badbd9230ff521b4&token=89GiFGpK01J7WSSnxHnjoefpgNPv-zrNCurl0z1EkRx4OZm4-aB36_TllcymXfewETa58Q9VLD9jJcC4MS7oa0uRTgC6JG9Poed648pU41U&sig=FxmsSqJuj3BM0pQ07XKq13UGJLY"} -------------------------------------------------------------------------------- /mock/reader/data/data2.json: -------------------------------------------------------------------------------- 1 | {"msg": "\u6210\u529f", "result": 0, "jsonp": "http://html.read.duokan.com/mfsv2/secure/s010/60009/file?nonce=74c8522cfd8546fd8e2ab346366d24d0&token=89GiFGpK01J7WSSnxHnjoU435w7sJdY_EntshdzDbZLgSoyHzWOcfbMWIqJ4TfqoETa58Q9VLD9jJcC4MS7oa0uRTgC6JG9Poed648pU41U&sig=TtG7TCFv4lKLctffbvFC-iXwIoE"} -------------------------------------------------------------------------------- /mock/reader/data/data3.json: -------------------------------------------------------------------------------- 1 | {"msg": "\u6210\u529f", "result": 0, "jsonp": "http://html.read.duokan.com/mfsv2/secure/s010/60009/file?nonce=1c0e98ddfdc443d48b8d4e227bf1c445&token=89GiFGpK01J7WSSnxHnjoS3l3LWUG3wT-GV19WJsgeR_-qC6G01gWwWbK91FIP3yETa58Q9VLD9jJcC4MS7oa0uRTgC6JG9Poed648pU41U&sig=YLnZSBB1As8jtzOXoFwqXVmtodA"} -------------------------------------------------------------------------------- /mock/reader/data/data4.json: -------------------------------------------------------------------------------- 1 | {"msg": "\u6210\u529f", "result": 0, "jsonp": "http://html.read.duokan.com/mfsv2/secure/s010/60009/file?nonce=bc14aee4f2fe4a4c8a5c7d934f239264&token=89GiFGpK01J7WSSnxHnjodEBjNZoilVyYrYcWjlgecZ8mtQUmtRvDBg185IHWPeqETa58Q9VLD9jJcC4MS7oa0uRTgC6JG9Poed648pU41U&sig=uEajgc6opUF36h7rDo3R6hB31OI"} -------------------------------------------------------------------------------- /Schemas/Beauty.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | let BeautySchema = new mongoose.Schema({ 4 | src: Buffer, 5 | meta: { 6 | createAt: { 7 | type: Date, 8 | default: new Date() 9 | }, 10 | updateAt: { 11 | type: Date, 12 | default: new Date() 13 | } 14 | } 15 | }) 16 | 17 | module.exports = BeautySchema -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 简单书城 2 | 3 | - [这是另外的一个线上书城](http://www.ibiquwu.com) 4 | 5 | 利用vue+nodejs+koa+mongodb搭建的简易移动书城 6 | 7 | 使用 8 | 9 | `git clone https://github.com/lastIndexOf/bookstore` 10 | 11 | `cd bookstore` 12 | 13 | `npm i` 14 | 15 | `node server` 16 | 17 | `打开浏览器 enjoy` 18 | 19 | 其中, 排行榜单中的数据来自数据库,直接从数据库中读取,而其他榜单的数据皆来自mock数据,在点进详情页时会自动根据书名去爬全书网,有些mock数据可能爬取不到数据,等待后续优化... 20 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | , nodemon = require('gulp-nodemon') 3 | , browserSync = require('browser-sync') 4 | , reload = browserSync.reload 5 | 6 | gulp.task('default', () => { 7 | browserSync.init({ 8 | proxy: 'localhost:3004' 9 | }) 10 | gulp.watch(['index.html', 'js/*.*', 'css/*.*'], reload) 11 | gulp.watch(['app/**/*.*'], reload) 12 | 13 | nodemon({ 14 | script: 'server.js', 15 | env: { 16 | 'NODE_ENV': 'development' 17 | }, 18 | ignore: [ './js', './app/js' ] 19 | }) 20 | }) -------------------------------------------------------------------------------- /app/新建文本文档.txt: -------------------------------------------------------------------------------- 1 | var appData = require('../data.json') 2 | , seller = appData.seller 3 | , goods = appData.goods 4 | , ratings = appData.ratings 5 | , apiRoutes = express.Router() 6 | 7 | apiRoutes.get('/seller', (req, res) => { 8 | res.json({ 9 | errno: 0, 10 | data: seller 11 | }) 12 | }) 13 | apiRoutes.get('/goods', (req, res) => { 14 | res.json({ 15 | errno: 0, 16 | data: goods 17 | }) 18 | }) 19 | apiRoutes.get('/ratings', (req, res) => { 20 | res.json({ 21 | errno: 0, 22 | data: ratings 23 | }) 24 | }) -------------------------------------------------------------------------------- /app/views/include/header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 书城 5 | 6 |
7 | 8 |
9 | 10 | 11 |
-------------------------------------------------------------------------------- /app/views/include/index-free.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

限时免费

5 |
6 |
7 | 17 |
18 | 23 |
24 |
-------------------------------------------------------------------------------- /app/views/include/index-self.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 输入书名/作者/关键字 5 | 6 |
7 | 8 |
9 |
10 |
11 | 25 |
26 |
-------------------------------------------------------------------------------- /app/js/index.js: -------------------------------------------------------------------------------- 1 | $.get('/ajax/index', function(data) { 2 | new Vue({ 3 | data: { 4 | top: data.items[0].data.data, 5 | hot: data.items[1].data.data, 6 | recommend: data.items[2].data.data, 7 | female: data.items[3].data.data, 8 | male: data.items[4].data.data, 9 | free: data.items[5].data.data, 10 | highlight: 1, 11 | tabA: 0, 12 | isMale: true 13 | }, 14 | computed: { 15 | tabSwipe: function() { 16 | if (this.highlight === 1) 17 | return `translateX(-${ this.tabA }px)` 18 | else { 19 | const width = this.$refs.tabA.offsetWidth 20 | , distance = (width + 34) / 2 21 | 22 | return `translateX(${ distance }px)` 23 | } 24 | } 25 | }, 26 | mounted: function() { 27 | const width = this.$refs.tabA.offsetWidth 28 | , distance = (width - 34) / 2 29 | 30 | this.tabA = distance 31 | } 32 | }).$mount('#index') 33 | }, 'json') -------------------------------------------------------------------------------- /app/views/include/index-top.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 输入书名/作者/关键字 6 | 7 |
8 |
9 | 10 | 11 | 12 |
13 |
14 | 40 |
-------------------------------------------------------------------------------- /app/views/include/index-hot.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 本周最火 5 |
6 | 25 |
26 |
-------------------------------------------------------------------------------- /Schemas/Movie.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | let MovieSchema = new mongoose.Schema({ 4 | title: String, 5 | authors: String, 6 | cover: String, 7 | summary: String, 8 | allArcs: { 9 | type: Number, 10 | default: 0 11 | }, 12 | arcs: [{ 13 | title: String, 14 | content: String 15 | }], 16 | meta: { 17 | createAt: { 18 | type: Date, 19 | default: new Date() 20 | }, 21 | updateAt: { 22 | type: Date, 23 | default: new Date() 24 | } 25 | } 26 | }) 27 | 28 | MovieSchema.pre('save', function(next) { 29 | if (this.isNew) 30 | this.meta.updateAt = this.meta.createAt = new Date() 31 | else 32 | this.meta.updateAt = new Date() 33 | 34 | next() 35 | }) 36 | 37 | MovieSchema.statics = { 38 | findById: function(id, cb) { 39 | return this.find({ _id: id }) 40 | .exec(cb) 41 | }, 42 | fetch: function(cb) { 43 | return this.find({}) 44 | .exec(cb) 45 | } 46 | } 47 | 48 | module.exports = MovieSchema -------------------------------------------------------------------------------- /app/views/include/index-female.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 女生最爱 5 |
6 | 21 |
22 | 换一换 23 | 女生频道>> 24 |
25 |
26 |
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookstore", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "zfk", 10 | "license": "ISC", 11 | "dependencies": { 12 | "auto-reload": "0.0.2", 13 | "cheerio": "^0.22.0", 14 | "co-views": "^2.1.0", 15 | "ejs": "^2.5.5", 16 | "iconv-lite": "^0.4.15", 17 | "koa": "^1.2.4", 18 | "koa-bodyparser": "^2.3.0", 19 | "koa-route": "^2.4.2", 20 | "koa-static": "^2.1.0", 21 | "koa-static-server": "^1.0.0", 22 | "koa-views": "^5.2.0", 23 | "mongoose": "^4.8.2", 24 | "superagent": "^3.4.4", 25 | "superagent-charset": "^1.1.1", 26 | "vue": "^2.1.10", 27 | "vue-router": "^2.2.1" 28 | }, 29 | "devDependencies": { 30 | "browser-sync": "^2.18.7", 31 | "gulp": "^3.9.1", 32 | "gulp-nodemon": "^2.2.1", 33 | "webpack": "^2.2.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/views/include/index-male.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 男生频道 5 |
6 | 21 |
22 | 换一换 23 | 男生频道>> 24 |
25 |
26 |
-------------------------------------------------------------------------------- /css/reset.css: -------------------------------------------------------------------------------- 1 | /* 重置&默认(reset&base)(tags) */ 2 | *[hidefocus],input,textarea,a{outline:none;} 3 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{padding:0;margin:0;} 4 | fieldset,img,html,body,iframe{border:0;} 5 | table{border-collapse:collapse;border-spacing:0;} 6 | li{list-style:none;} 7 | h1,h2,h3,h4,h5,h6{font-size:100%;} 8 | caption,th{font-weight:normal;font-style:normal;text-align:left;} 9 | em,strong{font-weight:bold;font-style:normal;} 10 | body,textarea,select,input,pre{font-family:arial,microsoft yahei,helvetica,sans-serif;font-size:14px;color:#555;} 11 | body{background:#f8f8f8;line-height:1.5em;-webkit-text-size-adjust:none;} 12 | a,button{cursor:pointer;} 13 | textarea{resize:none;overflow:auto;} 14 | pre{white-space:pre-wrap;} 15 | a{color:#333;text-decoration:none;} 16 | input{ 17 | -webkit-tap-highlight-color: rgba(255, 255, 255, 0); 18 | -webkit-user-modify: read-write-plaintext-only; 19 | } 20 | button { 21 | -webkit-tap-highlight-color: rgba(255,255,255,0) 22 | } 23 | /* 布局(grids)(.g-) */ 24 | /* 页面 */ 25 | 26 | -------------------------------------------------------------------------------- /app/views/category.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= title %> 9 | 10 | 11 | 21 | 22 | 23 |
24 | <% include include/book-header.html %> 25 |
26 |
27 |
28 |

快来看小说

29 |
30 |
31 | <% include include/index-male.html %> 32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/css/reset.css: -------------------------------------------------------------------------------- 1 | *[hidefocus],input,textarea,a{outline:none;} 2 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{padding:0;margin:0;} 3 | fieldset,img,html,body,iframe{border:0;} 4 | table{border-collapse:collapse;border-spacing:0;} 5 | li{list-style:none;} 6 | h1,h2,h3,h4,h5,h6{font-size:100%;} 7 | caption,th{font-weight:normal;font-style:normal;text-align:left;} 8 | em,strong{font-weight:bold;font-style:normal;} 9 | body,textarea,select,input,pre{font-family: "miui", "Helvetica Neue",Helvetica,STHeiTi,sans-serif;font-size:14px;color:#555;} 10 | body{background:#f8f8f8;line-height:1.5em;-webkit-text-size-adjust:none;} 11 | a,button{cursor:pointer;} 12 | textarea{resize:none;overflow:auto;} 13 | pre{white-space:pre-wrap;} 14 | a{color:#333;text-decoration:none;} 15 | input{ 16 | -webkit-tap-highlight-color: rgba(255, 255, 255, 0); 17 | -webkit-user-modify: read-write-plaintext-only; 18 | } 19 | html{width:100%;height: 100% ;overflow-x:hidden;font-size: 10px;} 20 | body{background:rgba(255, 255, 255, 0);position:relative;text-align:left;width:100%;height:100%;-webkit-tap-highlight-color: rgba(0,0,0,0.05);-webkit-touch-callout:none;-webkit-user-select:none;} 21 | 22 | body{ 23 | -webkit-overflow-scrolling: touch; 24 | } -------------------------------------------------------------------------------- /app/views/include/index-recommend.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 重磅推荐 5 | 6 |
7 | 8 | 9 |
10 |
11 | 35 |
36 |
-------------------------------------------------------------------------------- /app/views/book.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= book.title %> 9 | 10 | 11 | 13 | 14 | 15 |
16 | <% include include/book-header.html %> 17 |
18 |
19 |
20 | 21 |
22 |
23 |

<%= book.title %>

24 |

<%= book.authors %>

25 |

113个评价

26 |

字数: xxxx字 连载中

27 |
28 |
29 | 开始阅读 30 |
31 | <%- book.summary %> 32 |
33 |
最新:
34 |
35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= title %> 9 | 10 | 11 | 42 | 43 | 44 |
45 | <% include include/header.html %> 46 |
47 |
48 | <% include include/index-top.html %> 49 | <% include include/index-hot.html %> 50 | <% include include/index-recommend.html %> 51 | <% include include/index-male.html %> 52 | <% include include/index-female.html %> 53 | <% include include/index-free.html %> 54 | <% include include/index-bottom.html %> 55 |
56 |
57 |
58 | <% include include/index-self.html %> 59 |
60 |
61 |
62 |
63 |
64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /app/views/catalog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= title %> 9 | 10 | 11 | 32 | 33 | 34 |
35 |
36 |
37 | 38 | 39 | 40 | <%= data.title %> 41 |
42 |
43 | 58 |
59 | 60 | 61 | 62 | 63 | 69 | -------------------------------------------------------------------------------- /service/webAppService.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | exports.get_chapter_data = function() { 4 | var content = fs.readFileSync('./mock/reader/chapter.json', 'utf-8'); 5 | return content; 6 | } 7 | 8 | exports.get_chapter_content_data = function(id) { 9 | if (!id) { 10 | id = "1"; 11 | } 12 | var content = fs.readFileSync('./mock/reader/data/data' + id + '.json', 'utf-8'); 13 | return content; 14 | } 15 | 16 | exports.get_index_data = function() { 17 | var content = fs.readFileSync('./mock/home.json', 'utf-8'); 18 | return content; 19 | } 20 | 21 | exports.get_book_data = function(id) { 22 | if (!id) { 23 | id = "18218"; 24 | } 25 | if(fs.existsSync('./mock/book/' + id + '.json')){ 26 | return fs.readFileSync('./mock/book/' + id + '.json', 'utf-8'); 27 | }else{ 28 | return fs.readFileSync('./mock/book/18218.json', 'utf-8'); 29 | } 30 | } 31 | 32 | exports.get_rank_data = function(channelId) { 33 | var content = fs.readFileSync('./mock/rank.json', 'utf-8'); 34 | return content; 35 | } 36 | 37 | exports.get_category_data = function(channelId) { 38 | var content = fs.readFileSync('./mock/category.json', 'utf-8'); 39 | return content; 40 | } 41 | 42 | exports.get_male_data = function(channelId) { 43 | var content = fs.readFileSync('./mock/channel/male.json', 'utf-8'); 44 | return content; 45 | } 46 | 47 | exports.get_female_data = function(channelId) { 48 | var content = fs.readFileSync('./mock/channel/male.json', 'utf-8'); 49 | return content; 50 | } 51 | 52 | 53 | exports.get_search_data = function(start, end, keyword) { 54 | return function(cb) { 55 | var http = require('http'); 56 | var qs = require('querystring'); 57 | var data = { 58 | s: keyword, 59 | start: start, 60 | end: end 61 | } 62 | var content = qs.stringify(data); 63 | var http_request = { 64 | hostname: 'dushu.xiaomi.com', 65 | port: 80, 66 | path: '/store/v0/lib/query/onebox?' + content, 67 | method: 'GET' 68 | }; 69 | 70 | req_obj = http.request(http_request, function(_res) { 71 | var callback_content = ''; 72 | var _this = this; 73 | var content=''; 74 | _res.setEncoding('utf8'); 75 | 76 | _res.on('data', function(chunk) { 77 | content += chunk; 78 | }); 79 | 80 | _res.on('end', function(e) { 81 | cb(null,content); 82 | }); 83 | 84 | }); 85 | 86 | req_obj.on('error', function(e) { 87 | 88 | }); 89 | 90 | req_obj.end(); 91 | } 92 | } -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio') 2 | , Books = require('./Models/Movie') 3 | , Book = Books.Book 4 | , baseUrl = 'http://www.quanshu.net/book' 5 | 6 | exports.html2data = function*(html, request) { 7 | const $ = cheerio.load(html) 8 | , src = $('#navList .seeWell.cf li:first-child > a').attr('href') 9 | 10 | if (src) { 11 | const book = yield getBook(src, request) 12 | , jQuery = cheerio.load(book) 13 | let _book = {} 14 | 15 | _book.authors = jQuery('.author .bookDetail .bookso').find('dd a').text() 16 | _book.cover = jQuery('#container .mainnav .main.b-detail').find('.l.mr11 > img').attr('src') 17 | _book.src = jQuery('#container .mainnav .main.b-detail .detail .b-info .b-oper > a:first-child').attr('href').split(baseUrl)[1] 18 | _book.summary = jQuery('#container .mainnav .main.b-detail').find('.detail .b-info #waa').text() 19 | _book._is = 1 20 | 21 | return _book 22 | } 23 | else if ($('.author .bookDetail .bookso').find('dd a').text()) { 24 | let _book = {} 25 | 26 | _book.authors = $('.author .bookDetail .bookso').find('dd a').text() 27 | _book.cover = $('#container .mainnav .main.b-detail').find('.l.mr11 > img').attr('src') 28 | _book.summary = $('#container .mainnav .main.b-detail').find('.detail .b-info #waa').text() 29 | _book.src = $('#container .mainnav .main.b-detail .detail .b-info .b-oper > a:first-child').attr('href').split(baseUrl)[1] 30 | _book._is = 1 31 | 32 | return _book 33 | } 34 | else 35 | return { _is: 0, cover: 'http://www.quanshu.net/modules/article/images/nocover.jpg', authors: '没有找到这本书%>_<%', summary: '没有爬到数据...' } 36 | } 37 | 38 | exports.getCurPage = function*(request, src, page) { 39 | let curPage = {} 40 | const URL = baseUrl + src 41 | , pages = yield getBook(URL, request) 42 | , $ = cheerio.load(pages) 43 | , pageslist = $('#chapter .chapterSo .chapterNum ul .clearfix.dirconone li') 44 | , pageA = $(pageslist[`${ page }`]).find('a') 45 | , contentPage = yield getBook(URL + '/' + pageA.attr('href'), request) 46 | 47 | curPage.title = pageA.text() 48 | curPage.content = cheerio.load(contentPage)('#content').text() 49 | 50 | return curPage 51 | } 52 | 53 | exports.getPageslist = function*(src, request, title) { 54 | const html = yield getBook(baseUrl + src, request) 55 | , $ = cheerio.load(html) 56 | 57 | let arcs = [] 58 | $('#chapter .chapterSo .chapterNum ul .clearfix.dirconone li') 59 | .each((index, item) => { 60 | arcs.push({ _id: '', title: $(item).find('a').text(), page: index }) 61 | }) 62 | 63 | return { arcs, title, src } 64 | } 65 | 66 | function getBook(url, request) { 67 | return new Promise((resolve, reject) => { 68 | let key = false 69 | , get = false 70 | setTimeout(() => { 71 | if (!key) { 72 | get = true 73 | console.log('请求超时...') 74 | resolve('timeout') 75 | } 76 | },20000) 77 | console.log(`正在爬取${ url }...`) 78 | request.get(url) 79 | .charset('gbk') 80 | .end((err, res) => { 81 | if (get) 82 | return 83 | if (err) 84 | reject(err) 85 | key = true 86 | console.log('爬取成功,正在分析...') 87 | // console.log(res) 88 | resolve(res.text) 89 | }).on('error', (err) => { 90 | reject(err) 91 | }) 92 | }) 93 | } 94 | 95 | // function addBook(html) { 96 | // return new Promise((resolve, reject) => { 97 | // const $ = cheerio.load(html) 98 | 99 | // book.name = $('#container .mainnav .main.b-detail').find('.detail .b-info > h1').text() 100 | // book.autor = $('.author .bookDetail .bookso').find('dd a').text() 101 | // book.avaiter = $('#container .mainnav .main.b-detail').find('.l.mr11 > img').attr('src') 102 | // book.intrudoce = $('#container .mainnav .main.b-detail').find('.detail .b-info #waa').text() 103 | // book.arcs =[] 104 | 105 | // book = new Movie(book) 106 | 107 | // book.save((err) => { 108 | // if (err) 109 | // reject(err) 110 | 111 | // resolve() 112 | // }) 113 | // }) 114 | // } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const koa = require('koa') 2 | , router = require('koa-route') 3 | , static = require('koa-static') 4 | , bodyParser = require('koa-bodyparser') 5 | , mongoose = require('mongoose') 6 | , superagent = require('superagent') 7 | , install = require('superagent-charset') 8 | , views = require('co-views') 9 | , iconv = require('iconv-lite') 10 | , querystring = require('querystring') 11 | , Books = require('./Models/Movie') 12 | , util = require('./util') 13 | , service = require('./service/webAppService') 14 | , appData = require('./data.json') 15 | , seller = appData.seller 16 | , goods = appData.goods 17 | , ratings = appData.ratings 18 | , app = koa() 19 | , request = install(superagent) 20 | 21 | const dbUrl = 'mongodb://localhost:27017/avMovies' 22 | , Book = Books.Movie 23 | , render = views('app/views', { 24 | map: { html: 'ejs' } 25 | }) 26 | // 连接数据库 27 | mongoose.connect(dbUrl) 28 | 29 | 30 | app.use(static('app', { index: 'none' })) 31 | app.use(bodyParser()) 32 | 33 | app.use(router.get('/', function*() { 34 | this.body = yield render('index', { 35 | title: '首页' 36 | }) 37 | })) 38 | app.use(router.get('/book', function*() { 39 | const query = this.request.query 40 | , _id = query.id 41 | , title = query.title 42 | 43 | if (_id && _id !== 'undefined') { 44 | const book = yield Book.findOne({_id: _id}, ['title', 'cover', 'summary', 'authors']) 45 | this.body = yield render('book', { book }) 46 | } else { 47 | const _title = escape(iconv.encode(title, 'gb2312').toString('binary')) 48 | let result = yield request.get('http://www.quanshu.net/modules/article/search.php') 49 | .query(`searchkey=${ _title }`) 50 | .query('searchtype=articlename&searchbuttom.x=0&searchbuttom.y=0') 51 | .charset('gbk') 52 | 53 | result = yield util.html2data(result.text, request) 54 | result.title = title 55 | 56 | if (result._is) 57 | this.body = yield render('book', { book: result }) 58 | else { 59 | this.body = yield render('book', { book: result }) 60 | } 61 | } 62 | 63 | })) 64 | 65 | app.use(router.get('/category', function*() { 66 | this.body = yield render('category', { title: '爬自全书网...' }) 67 | })) 68 | 69 | app.use(router.get('/api/seller', function* () { 70 | this.body = { 71 | errno: 0, 72 | data: seller 73 | } 74 | })) 75 | app.use(router.get('/api/goods', function* () { 76 | this.body = { 77 | errno: 0, 78 | data: goods 79 | } 80 | })) 81 | app.use(router.get('/api/ratings', function* () { 82 | this.body = { 83 | errno: 0, 84 | data: ratings 85 | } 86 | })) 87 | app.use(router.get('/catalog', function*() { 88 | const query = this.request.query 89 | , _id = query.id 90 | , _title = query.title 91 | , _src = query.src 92 | , page = query.page - 0 93 | 94 | let data = [] 95 | 96 | if (_id) 97 | data = yield Book.findOne({_id: _id}, ['title', 'arcs']) 98 | else 99 | data = yield util.getPageslist(_src, request, _title) 100 | 101 | this.body = yield render('catalog', { title: _title, page, data: data }) 102 | })) 103 | 104 | // app.use(router.get('/male', function*() { 105 | // this.body = yield render('male', {}) 106 | // })) 107 | // app.use(router.get('/female', function*() { 108 | // this.body = yield render('female', {}) 109 | // })) 110 | // app.use(router.get('/search', function*() { 111 | // this.body = yield render('search', {}) 112 | // })) 113 | // app.use(router.get('/rank', function*() { 114 | // this.body = yield render('rank', {}) 115 | // })) 116 | 117 | app.use(router.get('/ajax/index', function*(){ 118 | this.body = service.get_index_data() 119 | })) 120 | 121 | app.use(router.get('/ajax/book', function*(){ 122 | var params = querystring.parse(this.req._parsedUrl.query) 123 | var id = params.id 124 | if(!id){ 125 | id = "" 126 | } 127 | this.body = service.get_book_data(id) 128 | })) 129 | 130 | app.use(router.get('/ajax/page', function*() { 131 | const query = this.request.query 132 | , _id = query.id 133 | , page = query.page - 0 134 | 135 | if (_id) { 136 | const pageDate = yield Book.findOne({_id: _id}, ['arcs']) 137 | this.body = { ERROR: 1, data: pageDate.arcs[`${ page }`] } 138 | } else { 139 | const _title = query.title 140 | , _src = query.src 141 | , pageDate = yield util.getCurPage(request, _src, page) 142 | 143 | this.body = { ERROR: 1, data: pageDate } 144 | } 145 | })) 146 | 147 | app.use(router.get('/ajax/category', function*(){ 148 | const books = yield Book.find({}, ['title', 'authors', 'cover', 'summary']).limit(9) 149 | 150 | if (books) 151 | this.body = { ERROR: 0, data: books } 152 | else 153 | this.body = { ERROR: 1 } 154 | })) 155 | 156 | // app.use(router.get('/ajax/chapter_data', function*(){ 157 | // var params = querystring.parse(this.req._parsedUrl.query) 158 | // var id = params.id 159 | // if(!id){ 160 | // id = "" 161 | // } 162 | // this.body = service.get_chapter_content_data(id) 163 | // })) 164 | 165 | // app.use(router.get('/ajax/rank', function*(){ 166 | // this.body = service.get_rank_data() 167 | // })) 168 | 169 | // app.use(router.get('/ajax/male', function*(){ 170 | // this.body = service.get_male_data() 171 | // })) 172 | 173 | // app.use(router.get('/ajax/female', function*(){ 174 | // this.body = service.get_female_data() 175 | // })) 176 | 177 | app.listen(3004) 178 | console.log('listing on 3004') 179 | -------------------------------------------------------------------------------- /mock/book/18218.json: -------------------------------------------------------------------------------- 1 | { 2 | "item": { 3 | "ad": 0, 4 | "word_count": 70977, 5 | "owner": 0, 6 | "allow_discount": 0, 7 | "ad_time": 0, 8 | "ad_duration": 0, 9 | "click": 2438627, 10 | "score_count": 113, 11 | "title": "老九门", 12 | "on_sale": true, 13 | "comment_count": 0, 14 | "has_ad": 0, 15 | "id": 1724032, 16 | "content": "【陈伟霆、张艺兴、赵丽颖主演,同名电视剧正在热播】民国时期,长沙车站开来一辆来历不明的火车,彻查发现来自一座深山中的百年矿山,因为常年开采导致地皮塌陷,矿山中弥漫着一股奇怪的浓雾,长沙老九门盗墓家族张家和红家经过了一番查探,发现了深埋矿山之下,有着另外一个秘密,经过一番探险,他们深入矿山之中,来到矿山的底部,看到了让人无法置信的景象。千年之前坠落的三颗陨石,长白山下埋藏的秘密,他们到底从矿山底部带出了什么东西,为何世界慢慢变的异样诡异?", 17 | "source": 2, 18 | "latest_id": 41, 19 | "score": 7.8, 20 | "allow_free_read": 1, 21 | "toc": [], 22 | "rights_id": 10080, 23 | "channel": [1], 24 | "updated": 1469173947, 25 | "finish": false, 26 | "tags": ["寻墓探险", "悬疑探险", "灵异"], 27 | "started": 1469168519, 28 | "price": 5, 29 | "chapter_count": 42, 30 | "authors": "南派三叔", 31 | "categories": [{ 32 | "category_id": 8000000, 33 | "label": "灵异" 34 | }, { 35 | "category_id": 8000300, 36 | "label": "悬疑探险" 37 | }], 38 | "author_books_total": 2, 39 | "rights": "阅文集团旗下起点中文网", 40 | "level": 0, 41 | "cover": "http://cover.read.duokan.com/mfsv2/download/fdsc3/p01TQfpyV6px/06EirDzmjj8zuK.jpg", 42 | "summary": "【陈伟霆、张艺兴、赵丽颖主演,同名电视正在热播】民国时期,长沙车站开来一辆来历不明的火车,彻查发现", 43 | "source_id": 323725, 44 | "fiction_id": 323725, 45 | "outer_id": "5791bb8850e3a4587b490db2", 46 | "latest": "第二十七章 缠斗" 47 | }, 48 | "related": [{ 49 | "ad": 0, 50 | "word_count": 599903, 51 | "owner": 0, 52 | "allow_discount": 0, 53 | "ad_time": 0, 54 | "ad_duration": 0, 55 | "score_count": 1, 56 | "title": "食咒", 57 | "on_sale": true, 58 | "comment_count": 0, 59 | "has_ad": 0, 60 | "click": 55868, 61 | "latest_id": 228, 62 | "score": 6.0, 63 | "allow_free_read": 1, 64 | "rights_id": 10080, 65 | "channel": [1], 66 | "updated": 1469613079, 67 | "finish": false, 68 | "tags": ["悬疑探险", "灵异"], 69 | "price": 5, 70 | "chapter_count": 229, 71 | "authors": "蟋与蝉", 72 | "categories": [{ 73 | "category_id": 8000000, 74 | "label": "灵异" 75 | }, { 76 | "category_id": 8000300, 77 | "label": "悬疑探险" 78 | }], 79 | "rights": "阅文集团旗下起点中文网", 80 | "level": 0, 81 | "cover": "http://cover.read.duokan.com/mfsv2/download/fdsc3/p01QivZjexs1/9FS6XpwmPq9ZxF.jpg", 82 | "summary": "饭店的东西是不可以乱吃的!因为你吃下去的很可能是......\r\n\r\n本文将带你进入厨子的黑暗世界。\r...", 83 | "fiction_id": 319741, 84 | "outer_id": "5718455f50e3a46f452a60be", 85 | "latest": "第四十七章:科技装备" 86 | }, { 87 | "ad": 0, 88 | "word_count": 243928, 89 | "owner": 0, 90 | "allow_discount": 0, 91 | "ad_time": 0, 92 | "ad_duration": 0, 93 | "score_count": 27, 94 | "title": "校园惊魂:我的初恋是厉鬼", 95 | "on_sale": true, 96 | "comment_count": 0, 97 | "has_ad": 0, 98 | "click": 277169, 99 | "latest_id": 99, 100 | "score": 8.66, 101 | "allow_free_read": 1, 102 | "rights_id": 10080, 103 | "channel": [1], 104 | "updated": 1465700482, 105 | "finish": false, 106 | "tags": ["灵异悬疑", "恐怖惊悚", "灵异"], 107 | "price": 5, 108 | "chapter_count": 100, 109 | "authors": "梦入星河", 110 | "categories": [{ 111 | "category_id": 50000, 112 | "label": "灵异悬疑" 113 | }, { 114 | "category_id": 50200, 115 | "label": "恐怖惊悚" 116 | }, { 117 | "category_id": 8000000, 118 | "label": "灵异" 119 | }, { 120 | "category_id": 8000200, 121 | "label": "恐怖惊悚" 122 | }], 123 | "rights": "阅文集团旗下创世中文网", 124 | "level": 0, 125 | "cover": "http://cover.read.duokan.com/mfsv2/download/fdsc3/p01i8Qo2TuU3/IcyOEngmwcpps5.jpg", 126 | "summary": "大红的霓裳,大红的冠顶,大红的绣花鞋,她一身上下,仿佛浸染过人世间最浓郁的鲜血,一头长发如墨,在烈烈...", 127 | "fiction_id": 271520, 128 | "outer_id": "5582352d50e3a473924c8758", 129 | "latest": "第109章 六阴解梦,梦魇散去" 130 | }, { 131 | "ad": 0, 132 | "word_count": 1506629, 133 | "owner": 0, 134 | "allow_discount": 0, 135 | "ad_time": 0, 136 | "ad_duration": 0, 137 | "score_count": 16, 138 | "title": "阴阳天师", 139 | "on_sale": true, 140 | "comment_count": 0, 141 | "has_ad": 0, 142 | "click": 203382, 143 | "latest_id": 720, 144 | "score": 7.5, 145 | "allow_free_read": 1, 146 | "rights_id": 10080, 147 | "channel": [1], 148 | "updated": 1468229857, 149 | "finish": false, 150 | "tags": ["灵异悬疑", "灵异奇谈", "灵异神怪", "灵异"], 151 | "price": 5, 152 | "chapter_count": 721, 153 | "authors": "落语", 154 | "categories": [{ 155 | "category_id": 50000, 156 | "label": "灵异悬疑" 157 | }, { 158 | "category_id": 50300, 159 | "label": "灵异神怪" 160 | }, { 161 | "category_id": 8000000, 162 | "label": "灵异" 163 | }, { 164 | "category_id": 8000400, 165 | "label": "灵异奇谈" 166 | }], 167 | "rights": "阅文集团旗下创世中文网", 168 | "level": 0, 169 | "cover": "http://cover.read.duokan.com/mfsv2/download/fdsc3/p01aEqw2vIGw/I3bD0QHJuwJjpg.jpg", 170 | "summary": "无论你信不信这个世界上有鬼,我却从小和它们为伍。七年前,我一直追随师父走南闯北,见识过形形色色的古怪...", 171 | "fiction_id": 302188, 172 | "outer_id": "5582351750e3a4738e4c8776", 173 | "latest": "第七百二十一章 各方震动" 174 | }], 175 | "author_books": [{ 176 | "ad": 0, 177 | "word_count": 1441873, 178 | "owner": 0, 179 | "allow_discount": false, 180 | "ad_time": 0, 181 | "ad_duration": 0, 182 | "score_count": 8854, 183 | "title": "盗墓笔记", 184 | "on_sale": true, 185 | "comment_count": 1927, 186 | "has_ad": 0, 187 | "click": 34069097, 188 | "latest_id": 481, 189 | "score": 9.0, 190 | "allow_free_read": 1, 191 | "rights_id": 10080, 192 | "channel": [1], 193 | "updated": 1450801819, 194 | "finish": true, 195 | "tags": ["升级练功", "盗墓探险", "盗墓", "热血", "灵异悬疑"], 196 | "price": 5, 197 | "chapter_count": 482, 198 | "authors": "南派三叔", 199 | "categories": [{ 200 | "category_id": 50000, 201 | "label": "灵异悬疑" 202 | }, { 203 | "category_id": 50400, 204 | "label": "盗墓探险" 205 | }, { 206 | "category_id": 8000000, 207 | "label": "灵异" 208 | }, { 209 | "category_id": 8000300, 210 | "label": "悬疑探险" 211 | }], 212 | "rights": "阅文集团旗下起点中文网", 213 | "level": 0, 214 | "cover": "http://cover.read.duokan.com/mfsv2/download/s010/p01yfPOuJRok/dQJSYfAnD3AB42.jpg", 215 | "summary": "这神秘的墓主人到底是谁,他们到底能不能找到真正的棺椁?", 216 | "fiction_id": 270635, 217 | "outer_id": "5581401c50e3a4079e15a4f9", 218 | "latest": "《后记》下" 219 | }], 220 | "result": 0 221 | } -------------------------------------------------------------------------------- /app/js/jquery.base64.js: -------------------------------------------------------------------------------- 1 | /*jslint adsafe: false, bitwise: true, browser: true, cap: false, css: false, 2 | debug: false, devel: true, eqeqeq: true, es5: false, evil: false, 3 | forin: false, fragment: false, immed: true, laxbreak: false, newcap: true, 4 | nomen: false, on: false, onevar: true, passfail: false, plusplus: true, 5 | regexp: false, rhino: true, safe: false, strict: false, sub: false, 6 | undef: true, white: false, widget: false, windows: false */ 7 | /*global jQuery: false, window: false */ 8 | "use strict"; 9 | 10 | /* 11 | * Original code (c) 2010 Nick Galbreath 12 | * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript 13 | * 14 | * jQuery port (c) 2010 Carlo Zottmann 15 | * http://github.com/carlo/jquery-base64 16 | * 17 | * Permission is hereby granted, free of charge, to any person 18 | * obtaining a copy of this software and associated documentation 19 | * files (the "Software"), to deal in the Software without 20 | * restriction, including without limitation the rights to use, 21 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | * copies of the Software, and to permit persons to whom the 23 | * Software is furnished to do so, subject to the following 24 | * conditions: 25 | * 26 | * The above copyright notice and this permission notice shall be 27 | * included in all copies or substantial portions of the Software. 28 | * 29 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 30 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 31 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 32 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 33 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 34 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 35 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 36 | * OTHER DEALINGS IN THE SOFTWARE. 37 | */ 38 | 39 | /* base64 encode/decode compatible with window.btoa/atob 40 | * 41 | * window.atob/btoa is a Firefox extension to convert binary data (the "b") 42 | * to base64 (ascii, the "a"). 43 | * 44 | * It is also found in Safari and Chrome. It is not available in IE. 45 | * 46 | * if (!window.btoa) window.btoa = $.base64.encode 47 | * if (!window.atob) window.atob = $.base64.decode 48 | * 49 | * The original spec's for atob/btoa are a bit lacking 50 | * https://developer.mozilla.org/en/DOM/window.atob 51 | * https://developer.mozilla.org/en/DOM/window.btoa 52 | * 53 | * window.btoa and $.base64.encode takes a string where charCodeAt is [0,255] 54 | * If any character is not [0,255], then an exception is thrown. 55 | * 56 | * window.atob and $.base64.decode take a base64-encoded string 57 | * If the input length is not a multiple of 4, or contains invalid characters 58 | * then an exception is thrown. 59 | */ 60 | 61 | jQuery.base64 = ( function( $ ) { 62 | 63 | var _PADCHAR = "=", 64 | _ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 65 | _VERSION = "1.0"; 66 | 67 | 68 | function _getbyte64( s, i ) { 69 | // This is oddly fast, except on Chrome/V8. 70 | // Minimal or no improvement in performance by using a 71 | // object with properties mapping chars to value (eg. 'A': 0) 72 | 73 | var idx = _ALPHA.indexOf( s.charAt( i ) ); 74 | 75 | if ( idx === -1 ) { 76 | throw "Cannot decode base64"; 77 | } 78 | 79 | return idx; 80 | } 81 | 82 | 83 | function _decode( s ) { 84 | var pads = 0, 85 | i, 86 | b10, 87 | imax = s.length, 88 | x = []; 89 | 90 | s = String( s ); 91 | 92 | if ( imax === 0 ) { 93 | return s; 94 | } 95 | 96 | if ( imax % 4 !== 0 ) { 97 | throw "Cannot decode base64"; 98 | } 99 | 100 | if ( s.charAt( imax - 1 ) === _PADCHAR ) { 101 | pads = 1; 102 | 103 | if ( s.charAt( imax - 2 ) === _PADCHAR ) { 104 | pads = 2; 105 | } 106 | 107 | // either way, we want to ignore this last block 108 | imax -= 4; 109 | } 110 | 111 | for ( i = 0; i < imax; i += 4 ) { 112 | b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ) | _getbyte64( s, i + 3 ); 113 | x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff, b10 & 0xff ) ); 114 | } 115 | 116 | switch ( pads ) { 117 | case 1: 118 | b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ); 119 | x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff ) ); 120 | break; 121 | 122 | case 2: 123 | b10 = ( _getbyte64( s, i ) << 18) | ( _getbyte64( s, i + 1 ) << 12 ); 124 | x.push( String.fromCharCode( b10 >> 16 ) ); 125 | break; 126 | } 127 | 128 | return x.join( "" ); 129 | } 130 | 131 | 132 | function _getbyte( s, i ) { 133 | var x = s.charCodeAt( i ); 134 | 135 | if ( x > 255 ) { 136 | throw "INVALID_CHARACTER_ERR: DOM Exception 5"; 137 | } 138 | 139 | return x; 140 | } 141 | 142 | 143 | function _encode( s ) { 144 | if ( arguments.length !== 1 ) { 145 | throw "SyntaxError: exactly one argument required"; 146 | } 147 | 148 | s = String( s ); 149 | 150 | var i, 151 | b10, 152 | x = [], 153 | imax = s.length - s.length % 3; 154 | 155 | if ( s.length === 0 ) { 156 | return s; 157 | } 158 | 159 | for ( i = 0; i < imax; i += 3 ) { 160 | b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ) | _getbyte( s, i + 2 ); 161 | x.push( _ALPHA.charAt( b10 >> 18 ) ); 162 | x.push( _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) ); 163 | x.push( _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) ); 164 | x.push( _ALPHA.charAt( b10 & 0x3f ) ); 165 | } 166 | 167 | switch ( s.length - imax ) { 168 | case 1: 169 | b10 = _getbyte( s, i ) << 16; 170 | x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _PADCHAR + _PADCHAR ); 171 | break; 172 | 173 | case 2: 174 | b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ); 175 | x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) + _PADCHAR ); 176 | break; 177 | } 178 | 179 | return x.join( "" ); 180 | } 181 | 182 | 183 | return { 184 | decode: _decode, 185 | encode: _encode, 186 | VERSION: _VERSION 187 | }; 188 | 189 | }( jQuery ) ); 190 | -------------------------------------------------------------------------------- /js/jquery.base64.js: -------------------------------------------------------------------------------- 1 | /*jslint adsafe: false, bitwise: true, browser: true, cap: false, css: false, 2 | debug: false, devel: true, eqeqeq: true, es5: false, evil: false, 3 | forin: false, fragment: false, immed: true, laxbreak: false, newcap: true, 4 | nomen: false, on: false, onevar: true, passfail: false, plusplus: true, 5 | regexp: false, rhino: true, safe: false, strict: false, sub: false, 6 | undef: true, white: false, widget: false, windows: false */ 7 | /*global jQuery: false, window: false */ 8 | "use strict"; 9 | 10 | /* 11 | * Original code (c) 2010 Nick Galbreath 12 | * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript 13 | * 14 | * jQuery port (c) 2010 Carlo Zottmann 15 | * http://github.com/carlo/jquery-base64 16 | * 17 | * Permission is hereby granted, free of charge, to any person 18 | * obtaining a copy of this software and associated documentation 19 | * files (the "Software"), to deal in the Software without 20 | * restriction, including without limitation the rights to use, 21 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | * copies of the Software, and to permit persons to whom the 23 | * Software is furnished to do so, subject to the following 24 | * conditions: 25 | * 26 | * The above copyright notice and this permission notice shall be 27 | * included in all copies or substantial portions of the Software. 28 | * 29 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 30 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 31 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 32 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 33 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 34 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 35 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 36 | * OTHER DEALINGS IN THE SOFTWARE. 37 | */ 38 | 39 | /* base64 encode/decode compatible with window.btoa/atob 40 | * 41 | * window.atob/btoa is a Firefox extension to convert binary data (the "b") 42 | * to base64 (ascii, the "a"). 43 | * 44 | * It is also found in Safari and Chrome. It is not available in IE. 45 | * 46 | * if (!window.btoa) window.btoa = $.base64.encode 47 | * if (!window.atob) window.atob = $.base64.decode 48 | * 49 | * The original spec's for atob/btoa are a bit lacking 50 | * https://developer.mozilla.org/en/DOM/window.atob 51 | * https://developer.mozilla.org/en/DOM/window.btoa 52 | * 53 | * window.btoa and $.base64.encode takes a string where charCodeAt is [0,255] 54 | * If any character is not [0,255], then an exception is thrown. 55 | * 56 | * window.atob and $.base64.decode take a base64-encoded string 57 | * If the input length is not a multiple of 4, or contains invalid characters 58 | * then an exception is thrown. 59 | */ 60 | 61 | jQuery.base64 = ( function( $ ) { 62 | 63 | var _PADCHAR = "=", 64 | _ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 65 | _VERSION = "1.0"; 66 | 67 | 68 | function _getbyte64( s, i ) { 69 | // This is oddly fast, except on Chrome/V8. 70 | // Minimal or no improvement in performance by using a 71 | // object with properties mapping chars to value (eg. 'A': 0) 72 | 73 | var idx = _ALPHA.indexOf( s.charAt( i ) ); 74 | 75 | if ( idx === -1 ) { 76 | throw "Cannot decode base64"; 77 | } 78 | 79 | return idx; 80 | } 81 | 82 | 83 | function _decode( s ) { 84 | var pads = 0, 85 | i, 86 | b10, 87 | imax = s.length, 88 | x = []; 89 | 90 | s = String( s ); 91 | 92 | if ( imax === 0 ) { 93 | return s; 94 | } 95 | 96 | if ( imax % 4 !== 0 ) { 97 | throw "Cannot decode base64"; 98 | } 99 | 100 | if ( s.charAt( imax - 1 ) === _PADCHAR ) { 101 | pads = 1; 102 | 103 | if ( s.charAt( imax - 2 ) === _PADCHAR ) { 104 | pads = 2; 105 | } 106 | 107 | // either way, we want to ignore this last block 108 | imax -= 4; 109 | } 110 | 111 | for ( i = 0; i < imax; i += 4 ) { 112 | b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ) | _getbyte64( s, i + 3 ); 113 | x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff, b10 & 0xff ) ); 114 | } 115 | 116 | switch ( pads ) { 117 | case 1: 118 | b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ); 119 | x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff ) ); 120 | break; 121 | 122 | case 2: 123 | b10 = ( _getbyte64( s, i ) << 18) | ( _getbyte64( s, i + 1 ) << 12 ); 124 | x.push( String.fromCharCode( b10 >> 16 ) ); 125 | break; 126 | } 127 | 128 | return x.join( "" ); 129 | } 130 | 131 | 132 | function _getbyte( s, i ) { 133 | var x = s.charCodeAt( i ); 134 | 135 | if ( x > 255 ) { 136 | throw "INVALID_CHARACTER_ERR: DOM Exception 5"; 137 | } 138 | 139 | return x; 140 | } 141 | 142 | 143 | function _encode( s ) { 144 | if ( arguments.length !== 1 ) { 145 | throw "SyntaxError: exactly one argument required"; 146 | } 147 | 148 | s = String( s ); 149 | 150 | var i, 151 | b10, 152 | x = [], 153 | imax = s.length - s.length % 3; 154 | 155 | if ( s.length === 0 ) { 156 | return s; 157 | } 158 | 159 | for ( i = 0; i < imax; i += 3 ) { 160 | b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ) | _getbyte( s, i + 2 ); 161 | x.push( _ALPHA.charAt( b10 >> 18 ) ); 162 | x.push( _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) ); 163 | x.push( _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) ); 164 | x.push( _ALPHA.charAt( b10 & 0x3f ) ); 165 | } 166 | 167 | switch ( s.length - imax ) { 168 | case 1: 169 | b10 = _getbyte( s, i ) << 16; 170 | x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _PADCHAR + _PADCHAR ); 171 | break; 172 | 173 | case 2: 174 | b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ); 175 | x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) + _PADCHAR ); 176 | break; 177 | } 178 | 179 | return x.join( "" ); 180 | } 181 | 182 | 183 | return { 184 | decode: _decode, 185 | encode: _encode, 186 | VERSION: _VERSION 187 | }; 188 | 189 | }( jQuery ) ); 190 | -------------------------------------------------------------------------------- /app/js/scripts/jquery.base64.js: -------------------------------------------------------------------------------- 1 | /*jslint adsafe: false, bitwise: true, browser: true, cap: false, css: false, 2 | debug: false, devel: true, eqeqeq: true, es5: false, evil: false, 3 | forin: false, fragment: false, immed: true, laxbreak: false, newcap: true, 4 | nomen: false, on: false, onevar: true, passfail: false, plusplus: true, 5 | regexp: false, rhino: true, safe: false, strict: false, sub: false, 6 | undef: true, white: false, widget: false, windows: false */ 7 | /*global jQuery: false, window: false */ 8 | "use strict"; 9 | 10 | /* 11 | * Original code (c) 2010 Nick Galbreath 12 | * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript 13 | * 14 | * jQuery port (c) 2010 Carlo Zottmann 15 | * http://github.com/carlo/jquery-base64 16 | * 17 | * Permission is hereby granted, free of charge, to any person 18 | * obtaining a copy of this software and associated documentation 19 | * files (the "Software"), to deal in the Software without 20 | * restriction, including without limitation the rights to use, 21 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | * copies of the Software, and to permit persons to whom the 23 | * Software is furnished to do so, subject to the following 24 | * conditions: 25 | * 26 | * The above copyright notice and this permission notice shall be 27 | * included in all copies or substantial portions of the Software. 28 | * 29 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 30 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 31 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 32 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 33 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 34 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 35 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 36 | * OTHER DEALINGS IN THE SOFTWARE. 37 | */ 38 | 39 | /* base64 encode/decode compatible with window.btoa/atob 40 | * 41 | * window.atob/btoa is a Firefox extension to convert binary data (the "b") 42 | * to base64 (ascii, the "a"). 43 | * 44 | * It is also found in Safari and Chrome. It is not available in IE. 45 | * 46 | * if (!window.btoa) window.btoa = $.base64.encode 47 | * if (!window.atob) window.atob = $.base64.decode 48 | * 49 | * The original spec's for atob/btoa are a bit lacking 50 | * https://developer.mozilla.org/en/DOM/window.atob 51 | * https://developer.mozilla.org/en/DOM/window.btoa 52 | * 53 | * window.btoa and $.base64.encode takes a string where charCodeAt is [0,255] 54 | * If any character is not [0,255], then an exception is thrown. 55 | * 56 | * window.atob and $.base64.decode take a base64-encoded string 57 | * If the input length is not a multiple of 4, or contains invalid characters 58 | * then an exception is thrown. 59 | */ 60 | 61 | jQuery.base64 = ( function( $ ) { 62 | 63 | var _PADCHAR = "=", 64 | _ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 65 | _VERSION = "1.0"; 66 | 67 | 68 | function _getbyte64( s, i ) { 69 | // This is oddly fast, except on Chrome/V8. 70 | // Minimal or no improvement in performance by using a 71 | // object with properties mapping chars to value (eg. 'A': 0) 72 | 73 | var idx = _ALPHA.indexOf( s.charAt( i ) ); 74 | 75 | if ( idx === -1 ) { 76 | throw "Cannot decode base64"; 77 | } 78 | 79 | return idx; 80 | } 81 | 82 | 83 | function _decode( s ) { 84 | var pads = 0, 85 | i, 86 | b10, 87 | imax = s.length, 88 | x = []; 89 | 90 | s = String( s ); 91 | 92 | if ( imax === 0 ) { 93 | return s; 94 | } 95 | 96 | if ( imax % 4 !== 0 ) { 97 | throw "Cannot decode base64"; 98 | } 99 | 100 | if ( s.charAt( imax - 1 ) === _PADCHAR ) { 101 | pads = 1; 102 | 103 | if ( s.charAt( imax - 2 ) === _PADCHAR ) { 104 | pads = 2; 105 | } 106 | 107 | // either way, we want to ignore this last block 108 | imax -= 4; 109 | } 110 | 111 | for ( i = 0; i < imax; i += 4 ) { 112 | b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ) | _getbyte64( s, i + 3 ); 113 | x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff, b10 & 0xff ) ); 114 | } 115 | 116 | switch ( pads ) { 117 | case 1: 118 | b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ); 119 | x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff ) ); 120 | break; 121 | 122 | case 2: 123 | b10 = ( _getbyte64( s, i ) << 18) | ( _getbyte64( s, i + 1 ) << 12 ); 124 | x.push( String.fromCharCode( b10 >> 16 ) ); 125 | break; 126 | } 127 | 128 | return x.join( "" ); 129 | } 130 | 131 | 132 | function _getbyte( s, i ) { 133 | var x = s.charCodeAt( i ); 134 | 135 | if ( x > 255 ) { 136 | throw "INVALID_CHARACTER_ERR: DOM Exception 5"; 137 | } 138 | 139 | return x; 140 | } 141 | 142 | 143 | function _encode( s ) { 144 | if ( arguments.length !== 1 ) { 145 | throw "SyntaxError: exactly one argument required"; 146 | } 147 | 148 | s = String( s ); 149 | 150 | var i, 151 | b10, 152 | x = [], 153 | imax = s.length - s.length % 3; 154 | 155 | if ( s.length === 0 ) { 156 | return s; 157 | } 158 | 159 | for ( i = 0; i < imax; i += 3 ) { 160 | b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ) | _getbyte( s, i + 2 ); 161 | x.push( _ALPHA.charAt( b10 >> 18 ) ); 162 | x.push( _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) ); 163 | x.push( _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) ); 164 | x.push( _ALPHA.charAt( b10 & 0x3f ) ); 165 | } 166 | 167 | switch ( s.length - imax ) { 168 | case 1: 169 | b10 = _getbyte( s, i ) << 16; 170 | x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _PADCHAR + _PADCHAR ); 171 | break; 172 | 173 | case 2: 174 | b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ); 175 | x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) + _PADCHAR ); 176 | break; 177 | } 178 | 179 | return x.join( "" ); 180 | } 181 | 182 | 183 | return { 184 | decode: _decode, 185 | encode: _encode, 186 | VERSION: _VERSION 187 | }; 188 | 189 | }( jQuery ) ); 190 | -------------------------------------------------------------------------------- /app/js/scripts/touch.js: -------------------------------------------------------------------------------- 1 | // Zepto.js 2 | // (c) 2010-2016 Thomas Fuchs 3 | // Zepto.js may be freely distributed under the MIT license. 4 | 5 | ;(function($){ 6 | var touch = {}, 7 | touchTimeout, tapTimeout, swipeTimeout, longTapTimeout, 8 | longTapDelay = 750, 9 | gesture 10 | 11 | function swipeDirection(x1, x2, y1, y2) { 12 | return Math.abs(x1 - x2) >= 13 | Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down') 14 | } 15 | 16 | function longTap() { 17 | longTapTimeout = null 18 | if (touch.last) { 19 | touch.el.trigger('longTap') 20 | touch = {} 21 | } 22 | } 23 | 24 | function cancelLongTap() { 25 | if (longTapTimeout) clearTimeout(longTapTimeout) 26 | longTapTimeout = null 27 | } 28 | 29 | function cancelAll() { 30 | if (touchTimeout) clearTimeout(touchTimeout) 31 | if (tapTimeout) clearTimeout(tapTimeout) 32 | if (swipeTimeout) clearTimeout(swipeTimeout) 33 | if (longTapTimeout) clearTimeout(longTapTimeout) 34 | touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null 35 | touch = {} 36 | } 37 | 38 | function isPrimaryTouch(event){ 39 | return (event.pointerType == 'touch' || 40 | event.pointerType == event.MSPOINTER_TYPE_TOUCH) 41 | && event.isPrimary 42 | } 43 | 44 | function isPointerEventType(e, type){ 45 | return (e.type == 'pointer'+type || 46 | e.type.toLowerCase() == 'mspointer'+type) 47 | } 48 | 49 | $(document).ready(function(){ 50 | var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType 51 | 52 | if ('MSGesture' in window) { 53 | gesture = new MSGesture() 54 | gesture.target = document.body 55 | } 56 | 57 | $(document) 58 | .bind('MSGestureEnd', function(e){ 59 | var swipeDirectionFromVelocity = 60 | e.velocityX > 1 ? 'Right' : e.velocityX < -1 ? 'Left' : e.velocityY > 1 ? 'Down' : e.velocityY < -1 ? 'Up' : null 61 | if (swipeDirectionFromVelocity) { 62 | touch.el.trigger('swipe') 63 | touch.el.trigger('swipe'+ swipeDirectionFromVelocity) 64 | } 65 | }) 66 | .on('touchstart MSPointerDown pointerdown', function(e){ 67 | if((_isPointerType = isPointerEventType(e, 'down')) && 68 | !isPrimaryTouch(e)) return 69 | firstTouch = _isPointerType ? e : e.touches[0] 70 | if (e.touches && e.touches.length === 1 && touch.x2) { 71 | // Clear out touch movement data if we have it sticking around 72 | // This can occur if touchcancel doesn't fire due to preventDefault, etc. 73 | touch.x2 = undefined 74 | touch.y2 = undefined 75 | } 76 | now = Date.now() 77 | delta = now - (touch.last || now) 78 | touch.el = $('tagName' in firstTouch.target ? 79 | firstTouch.target : firstTouch.target.parentNode) 80 | touchTimeout && clearTimeout(touchTimeout) 81 | touch.x1 = firstTouch.pageX 82 | touch.y1 = firstTouch.pageY 83 | if (delta > 0 && delta <= 250) touch.isDoubleTap = true 84 | touch.last = now 85 | longTapTimeout = setTimeout(longTap, longTapDelay) 86 | // adds the current touch contact for IE gesture recognition 87 | if (gesture && _isPointerType) gesture.addPointer(e.pointerId) 88 | }) 89 | .on('touchmove MSPointerMove pointermove', function(e){ 90 | if((_isPointerType = isPointerEventType(e, 'move')) && 91 | !isPrimaryTouch(e)) return 92 | firstTouch = _isPointerType ? e : e.touches[0] 93 | cancelLongTap() 94 | touch.x2 = firstTouch.pageX 95 | touch.y2 = firstTouch.pageY 96 | 97 | deltaX += Math.abs(touch.x1 - touch.x2) 98 | deltaY += Math.abs(touch.y1 - touch.y2) 99 | }) 100 | .on('touchend MSPointerUp pointerup', function(e){ 101 | if((_isPointerType = isPointerEventType(e, 'up')) && 102 | !isPrimaryTouch(e)) return 103 | cancelLongTap() 104 | 105 | // swipe 106 | if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) || 107 | (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30)) 108 | 109 | swipeTimeout = setTimeout(function() { 110 | if (touch.el){ 111 | touch.el.trigger('swipe') 112 | touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2))) 113 | } 114 | touch = {} 115 | }, 0) 116 | 117 | // normal tap 118 | else if ('last' in touch) 119 | // don't fire tap when delta position changed by more than 30 pixels, 120 | // for instance when moving to a point and back to origin 121 | if (deltaX < 30 && deltaY < 30) { 122 | // delay by one tick so we can cancel the 'tap' event if 'scroll' fires 123 | // ('tap' fires before 'scroll') 124 | tapTimeout = setTimeout(function() { 125 | 126 | // trigger universal 'tap' with the option to cancelTouch() 127 | // (cancelTouch cancels processing of single vs double taps for faster 'tap' response) 128 | var event = $.Event('tap') 129 | event.cancelTouch = cancelAll 130 | // [by paper] fix -> "TypeError: 'undefined' is not an object (evaluating 'touch.el.trigger'), when double tap 131 | if (touch.el) touch.el.trigger(event) 132 | 133 | // trigger double tap immediately 134 | if (touch.isDoubleTap) { 135 | if (touch.el) touch.el.trigger('doubleTap') 136 | touch = {} 137 | } 138 | 139 | // trigger single tap after 250ms of inactivity 140 | else { 141 | touchTimeout = setTimeout(function(){ 142 | touchTimeout = null 143 | if (touch.el) touch.el.trigger('singleTap') 144 | touch = {} 145 | }, 250) 146 | } 147 | }, 0) 148 | } else { 149 | touch = {} 150 | } 151 | deltaX = deltaY = 0 152 | 153 | }) 154 | // when the browser window loses focus, 155 | // for example when a modal dialog is shown, 156 | // cancel all ongoing events 157 | .on('touchcancel MSPointerCancel pointercancel', cancelAll) 158 | 159 | // scrolling the window indicates intention of the user 160 | // to scroll, not tap or swipe, so cancel all ongoing events 161 | $(window).on('scroll', cancelAll) 162 | }) 163 | 164 | ;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 165 | 'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function(eventName){ 166 | $.fn[eventName] = function(callback){ return this.on(eventName, callback) } 167 | }) 168 | })(Zepto) 169 | -------------------------------------------------------------------------------- /mock/bookbacket.json: -------------------------------------------------------------------------------- 1 | { 2 | items: [{ 3 | ad: 0, 4 | word_count: 999756, 5 | owner: 0, 6 | allow_discount: 0, 7 | ad_time: 3, 8 | click: 8561542, 9 | score_count: 600, 10 | title: "先婚厚爱", 11 | on_sale: true, 12 | comment_count: 61, 13 | ad_duration: 5, 14 | latest_id: 253, 15 | score: 8.41, 16 | allow_free_read: 1, 17 | rights_id: 10039, 18 | channel: [ 19 | 2 20 | ], 21 | updated: 1465813422, 22 | finish: true, 23 | tags: [ 24 | "现言", 25 | "都市言情" 26 | ], 27 | price: 5, 28 | chapter_count: 254, 29 | authors: "莫萦", 30 | categories: [{ 31 | category_id: 100000, 32 | label: "现代言情" 33 | }, { 34 | category_id: 100100, 35 | label: "都市言情" 36 | }, { 37 | category_id: 18000000, 38 | label: "现代言情" 39 | }, { 40 | category_id: 18000100, 41 | label: "都市生活" 42 | }], 43 | rights: "潇湘书院", 44 | level: 0, 45 | cover: "http://cover.read.duokan.com/mfsv2/download/s010/p017Bgr0t7WU/iYz4mtvYjzwZjO.jpg", 46 | summary: "江城第一把手被她误以为相亲对象,身为剩女的她第一句话:“去领证吗?”", 47 | fiction_id: 46509, 48 | outer_id: "53e2dde80ea497276b5a2f06", 49 | latest: "番外宝贝女儿" 50 | }, { 51 | ad: 0, 52 | word_count: 4792595, 53 | owner: 0, 54 | allow_discount: false, 55 | ad_time: 3, 56 | click: 106686189, 57 | score_count: 12305, 58 | title: "七界武神", 59 | on_sale: true, 60 | comment_count: 308, 61 | ad_duration: 5, 62 | latest_id: 1535, 63 | score: 8.36, 64 | allow_free_read: 1, 65 | rights_id: 10080, 66 | channel: [ 67 | 1 68 | ], 69 | updated: 1468301457, 70 | finish: false, 71 | tags: [ 72 | "七界武神", 73 | "武魂", 74 | "叶天", 75 | "异世大陆", 76 | "玄幻", 77 | "异界大陆", 78 | "玄幻奇幻" 79 | ], 80 | price: 5, 81 | chapter_count: 1536, 82 | authors: "叶之凡", 83 | categories: [{ 84 | category_id: 10000, 85 | label: "玄幻奇幻" 86 | }, { 87 | category_id: 10200, 88 | label: "异世大陆" 89 | }, { 90 | category_id: 1000000, 91 | label: "玄幻" 92 | }, { 93 | category_id: 1000200, 94 | label: "异界大陆" 95 | }], 96 | rights: "阅文集团旗下创世中文网", 97 | level: 0, 98 | cover: "http://cover.read.duokan.com/mfsv2/download/fdsc3/p01DMhyt0Zkm/sf6mdPxWm06zcc.jpg", 99 | summary: "华夏特种兵穿越武者世界,得到上古魔祖吞噬魔功,练成了吞噬体质,从而踏上巅峰,横扫天下!", 100 | fiction_id: 270794, 101 | outer_id: "558233a550e3a4738f4c81d4", 102 | latest: "第一千五百三十四章 圣主陨落" 103 | }, { 104 | ad: 0, 105 | word_count: 786451, 106 | owner: 0, 107 | allow_discount: 0, 108 | ad_time: 3, 109 | click: 2451425, 110 | score_count: 83, 111 | title: "宠妃难为", 112 | on_sale: true, 113 | comment_count: 0, 114 | ad_duration: 5, 115 | latest_id: 161, 116 | score: 8.62, 117 | allow_free_read: 1, 118 | rights_id: 20004, 119 | channel: [ 120 | 2 121 | ], 122 | updated: 1439007915, 123 | finish: true, 124 | tags: [ 125 | "原创", 126 | "言情", 127 | "爱情", 128 | "古典架空", 129 | "架空历史", 130 | "古代言情" 131 | ], 132 | price: 3, 133 | chapter_count: 162, 134 | authors: "碧云天", 135 | categories: [{ 136 | category_id: 110000, 137 | label: "古代言情" 138 | }, { 139 | category_id: 110100, 140 | label: "架空历史" 141 | }, { 142 | category_id: 17000000, 143 | label: "古代言情" 144 | }, { 145 | category_id: 17000200, 146 | label: "古典架空" 147 | }], 148 | rights: "晋江文学", 149 | level: 0, 150 | cover: "http://cover.read.duokan.com/mfsv2/download/s010/p018NN4CqhaC/BmIs9AONWa0zsO.jpg", 151 | summary: "十七岁,仟夕瑶进宫两年,无宠,想的不过是怎么逃离这后宫,结果那许多嫔肚子里没有消息,她却一下子蹦出个...", 152 | fiction_id: 308833, 153 | outer_id: "55c18acf50e3a4299f2b569c", 154 | latest: "第 162 章" 155 | }, { 156 | ad: 0, 157 | word_count: 6422470, 158 | owner: 0, 159 | allow_discount: false, 160 | ad_time: 3, 161 | click: 15451173, 162 | score_count: 5217, 163 | title: "完美世界", 164 | on_sale: true, 165 | comment_count: 240, 166 | ad_duration: 5, 167 | latest_id: 2031, 168 | score: 9.07, 169 | allow_free_read: 1, 170 | rights_id: 10080, 171 | channel: [ 172 | 1 173 | ], 174 | updated: 1468265441, 175 | finish: false, 176 | tags: [ 177 | "玄幻奇幻", 178 | "玄幻", 179 | "东方玄幻" 180 | ], 181 | price: 5, 182 | chapter_count: 2032, 183 | authors: "辰东", 184 | categories: [{ 185 | category_id: 10000, 186 | label: "玄幻奇幻" 187 | }, { 188 | category_id: 10100, 189 | label: "东方玄幻" 190 | }, { 191 | category_id: 1000000, 192 | label: "玄幻" 193 | }, { 194 | category_id: 1000100, 195 | label: "东方玄幻" 196 | }], 197 | rights: "阅文集团旗下起点中文网", 198 | level: 0, 199 | cover: "http://cover.read.duokan.com/mfsv2/download/s010/p01CBWdv06tQ/0wQb35pmpRhaIr.jpg", 200 | summary: "一粒尘可填海,一根草斩尽日月星辰,弹指间天翻地覆。 群雄并起,万族林立,诸圣争霸,乱天动地。问苍茫大...", 201 | fiction_id: 270644, 202 | outer_id: "558142d650e3a4079d15aab3", 203 | latest: "请假,暂停一天" 204 | }, { 205 | ad: 0, 206 | word_count: 1842693, 207 | owner: 0, 208 | allow_discount: 0, 209 | ad_time: 3, 210 | click: 549763, 211 | score_count: 138, 212 | title: "重生之国民男神", 213 | on_sale: true, 214 | comment_count: 0, 215 | ad_duration: 5, 216 | latest_id: 432, 217 | score: 9.6, 218 | allow_free_read: 1, 219 | rights_id: 10039, 220 | channel: [ 221 | 2 222 | ], 223 | updated: 1466750712, 224 | finish: false, 225 | tags: [ 226 | "重生之国民男神", 227 | "现代言情", 228 | "都市言情", 229 | "都市生活" 230 | ], 231 | price: 5, 232 | chapter_count: 433, 233 | authors: "水千澈", 234 | categories: [{ 235 | category_id: 100000, 236 | label: "现代言情" 237 | }, { 238 | category_id: 100100, 239 | label: "都市言情" 240 | }, { 241 | category_id: 18000000, 242 | label: "现代言情" 243 | }, { 244 | category_id: 18000100, 245 | label: "都市生活" 246 | }], 247 | rights: "潇湘书院", 248 | level: 0, 249 | cover: "http://cover.read.duokan.com/mfsv2/download/s010/p01OKCgsrZfq/3IzTun8ZP3Yrud.jpg", 250 | summary: "前生司凰被至亲控制陷害,贵为连冠影帝,却死无葬身之地。意外重生,再回起点,获得古怪传承。司凰摸着下巴...", 251 | fiction_id: 317733, 252 | outer_id: "569e9d8150e3a468e9b8ec23", 253 | latest: "不爱你还能爱谁" 254 | }, { 255 | ad: 0, 256 | word_count: 1441873, 257 | owner: 0, 258 | allow_discount: false, 259 | ad_time: 3, 260 | click: 31431177, 261 | score_count: 8745, 262 | title: "盗墓笔记", 263 | on_sale: true, 264 | comment_count: 1927, 265 | ad_duration: 5, 266 | latest_id: 481, 267 | score: 8.99, 268 | allow_free_read: 1, 269 | rights_id: 10080, 270 | channel: [ 271 | 1 272 | ], 273 | updated: 1450801819, 274 | finish: true, 275 | tags: [ 276 | "升级练功", 277 | "盗墓探险", 278 | "盗墓", 279 | "热血", 280 | "灵异悬疑" 281 | ], 282 | price: 5, 283 | chapter_count: 482, 284 | authors: "南派三叔", 285 | categories: [{ 286 | category_id: 50000, 287 | label: "灵异悬疑" 288 | }, { 289 | category_id: 50400, 290 | label: "盗墓探险" 291 | }, { 292 | category_id: 8000000, 293 | label: "灵异" 294 | }, { 295 | category_id: 8000300, 296 | label: "悬疑探险" 297 | }], 298 | rights: "阅文集团旗下起点中文网", 299 | level: 0, 300 | cover: "http://cover.read.duokan.com/mfsv2/download/s010/p01yfPOuJRok/dQJSYfAnD3AB42.jpg", 301 | summary: "这神秘的墓主人到底是谁,他们到底能不能找到真正的棺椁?", 302 | fiction_id: 270635, 303 | outer_id: "5581401c50e3a4079e15a4f9", 304 | latest: "《后记》下" 305 | }], 306 | result: 0 307 | } -------------------------------------------------------------------------------- /js/jquery.jsonp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery JSONP Core Plugin 2.4.0 (2012-08-21) 3 | * 4 | * https://github.com/jaubourg/jquery-jsonp 5 | * 6 | * Copyright (c) 2012 Julian Aubourg 7 | * 8 | * This document is licensed as free software under the terms of the 9 | * MIT License: http://www.opensource.org/licenses/mit-license.php 10 | */ 11 | ( function( $ ) { 12 | 13 | // ###################### UTILITIES ## 14 | 15 | // Noop 16 | function noop() { 17 | } 18 | 19 | // Generic callback 20 | function genericCallback( data ) { 21 | lastValue = [ data ]; 22 | } 23 | 24 | // Call if defined 25 | function callIfDefined( method , object , parameters ) { 26 | return method && method.apply( object.context || object , parameters ); 27 | } 28 | 29 | // Give joining character given url 30 | function qMarkOrAmp( url ) { 31 | return /\?/ .test( url ) ? "&" : "?"; 32 | } 33 | 34 | var // String constants (for better minification) 35 | STR_ASYNC = "async", 36 | STR_CHARSET = "charset", 37 | STR_EMPTY = "", 38 | STR_ERROR = "error", 39 | STR_INSERT_BEFORE = "insertBefore", 40 | STR_JQUERY_JSONP = "_jqjsp", 41 | STR_ON = "on", 42 | STR_ON_CLICK = STR_ON + "click", 43 | STR_ON_ERROR = STR_ON + STR_ERROR, 44 | STR_ON_LOAD = STR_ON + "load", 45 | STR_ON_READY_STATE_CHANGE = STR_ON + "readystatechange", 46 | STR_READY_STATE = "readyState", 47 | STR_REMOVE_CHILD = "removeChild", 48 | STR_SCRIPT_TAG = "