├── app.js ├── .gitignore ├── component ├── ganks │ ├── ganks.json │ ├── ganks.wxml │ ├── ganks.wxss │ └── ganks.js └── search │ ├── search-fab.json │ ├── search-fab.wxml │ ├── search-fab.wxss │ └── search-fab.js ├── screenshots ├── 0.png ├── 1.png └── 2.png ├── pages ├── details │ ├── details.wxml │ └── details.js ├── search │ ├── search.json │ ├── search.wxml │ ├── search.wxss │ └── search.js ├── ios │ ├── ios.wxml │ ├── ios.js │ └── ios.json ├── web │ ├── web.wxml │ ├── web.js │ └── web.json ├── video │ ├── video.wxml │ ├── video.js │ └── video.json ├── android │ ├── android.wxml │ ├── android.js │ └── android.json └── meizi │ ├── meizi.json │ ├── meizi.wxss │ ├── meizi.wxml │ └── meizi.js ├── app.wxss ├── utils ├── pagehelper.js └── dateformat.js ├── network └── api.js ├── app.json ├── README.md └── LICENSE /app.js: -------------------------------------------------------------------------------- 1 | //app.js 2 | App({}) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | project.config.json -------------------------------------------------------------------------------- /component/ganks/ganks.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true 3 | } -------------------------------------------------------------------------------- /component/search/search-fab.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true 3 | } -------------------------------------------------------------------------------- /screenshots/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamuelGjk/MpGank/HEAD/screenshots/0.png -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamuelGjk/MpGank/HEAD/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamuelGjk/MpGank/HEAD/screenshots/2.png -------------------------------------------------------------------------------- /pages/details/details.wxml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pages/search/search.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "搜索", 3 | "enablePullDownRefresh": true 4 | } -------------------------------------------------------------------------------- /pages/ios/ios.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /pages/web/web.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /pages/ios/ios.js: -------------------------------------------------------------------------------- 1 | // pages/ios/ios.js 2 | 3 | var pageHelper = require('../../utils/pagehelper.js') 4 | 5 | Page(pageHelper.pageInitializer) -------------------------------------------------------------------------------- /pages/video/video.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /pages/web/web.js: -------------------------------------------------------------------------------- 1 | // pages/web/web.js 2 | 3 | var pageHelper = require('../../utils/pagehelper.js') 4 | 5 | Page(pageHelper.pageInitializer) -------------------------------------------------------------------------------- /pages/video/video.js: -------------------------------------------------------------------------------- 1 | // pages/video/video.js 2 | 3 | var pageHelper = require('../../utils/pagehelper.js') 4 | 5 | Page(pageHelper.pageInitializer) -------------------------------------------------------------------------------- /pages/android/android.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /component/search/search-fab.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /pages/android/android.js: -------------------------------------------------------------------------------- 1 | // pages/android/android.js 2 | 3 | var pageHelper = require('../../utils/pagehelper.js') 4 | 5 | Page(pageHelper.pageInitializer) 6 | -------------------------------------------------------------------------------- /pages/meizi/meizi.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "search-fab": "../../component/search/search-fab" 4 | }, 5 | "enablePullDownRefresh": true 6 | } -------------------------------------------------------------------------------- /pages/ios/ios.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "ganks": "../../component/ganks/ganks", 4 | "search-fab": "../../component/search/search-fab" 5 | }, 6 | "enablePullDownRefresh": true 7 | } -------------------------------------------------------------------------------- /pages/web/web.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "ganks": "../../component/ganks/ganks", 4 | "search-fab": "../../component/search/search-fab" 5 | }, 6 | "enablePullDownRefresh": true 7 | } -------------------------------------------------------------------------------- /pages/video/video.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "ganks": "../../component/ganks/ganks", 4 | "search-fab": "../../component/search/search-fab" 5 | }, 6 | "enablePullDownRefresh": true 7 | } -------------------------------------------------------------------------------- /pages/android/android.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "ganks": "../../component/ganks/ganks", 4 | "search-fab": "../../component/search/search-fab" 5 | }, 6 | "enablePullDownRefresh": true 7 | } -------------------------------------------------------------------------------- /pages/details/details.js: -------------------------------------------------------------------------------- 1 | // pages/details/details.js 2 | Page({ 3 | data: { 4 | url: '' 5 | }, 6 | 7 | onLoad: function (options) { 8 | wx.setNavigationBarTitle({ 9 | title: options.title 10 | }) 11 | this.setData({ 12 | url: options.url 13 | }) 14 | } 15 | }) -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | 3 | .container { 4 | width: 100%; 5 | height: 100%; 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | justify-content: space-between; 10 | } 11 | 12 | .loading { 13 | color: #bdbdbd; 14 | font-size: 24rpx; 15 | padding: 10rpx 0; 16 | } 17 | -------------------------------------------------------------------------------- /component/search/search-fab.wxss: -------------------------------------------------------------------------------- 1 | /* component/search/search-fab.wxss */ 2 | .search-fab { 3 | float: right; 4 | position: fixed; 5 | bottom: 0; 6 | right: 0; 7 | margin: 16px; 8 | padding: 22rpx; 9 | background-color: #607D8B; 10 | border-radius: 500rpx; 11 | box-shadow: 0 4rpx 6rpx #666; 12 | } -------------------------------------------------------------------------------- /component/search/search-fab.js: -------------------------------------------------------------------------------- 1 | // component/search/search-fab.js 2 | Component({ 3 | 4 | properties: { 5 | 6 | }, 7 | 8 | data: { 9 | 10 | }, 11 | 12 | methods: { 13 | onClick: function (event) { 14 | wx.navigateTo({ 15 | url: '../../pages/search/search', 16 | }) 17 | } 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /component/ganks/ganks.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{gank.desc}} 6 | {{gank.who}} · {{gank.publishedAt}} 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /utils/pagehelper.js: -------------------------------------------------------------------------------- 1 | var pageInitializer = { 2 | onReady: function () { 3 | this.ganks = this.selectComponent("#ganks") 4 | this.ganks.fetchData(true) 5 | }, 6 | 7 | onPullDownRefresh: function () { 8 | this.ganks.fetchData(true) 9 | }, 10 | 11 | onReachBottom: function () { 12 | this.ganks.fetchData(false) 13 | }, 14 | 15 | onTabItemTap: function () { 16 | wx.pageScrollTo({ 17 | scrollTop: 0, 18 | duration: 300 19 | }) 20 | } 21 | } 22 | 23 | module.exports = { 24 | pageInitializer: pageInitializer 25 | } -------------------------------------------------------------------------------- /pages/search/search.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{result.desc}} 8 | {{result.who}} · {{result.publishedAt}} 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /pages/search/search.wxss: -------------------------------------------------------------------------------- 1 | /* pages/search/search.wxss */ 2 | 3 | .search-input { 4 | background-color: #EEE; 5 | border-radius: 6rpx; 6 | font-size: 24rpx; 7 | min-width: 90%; 8 | max-width: 90%; 9 | padding: 10rpx 16rpx; 10 | margin: 16rpx 0; 11 | } 12 | 13 | .result-item { 14 | display: flex; 15 | flex-direction: column; 16 | width: 93%; 17 | border-bottom: 1rpx solid #EEE; 18 | padding: 16rpx 0; 19 | } 20 | 21 | .title { 22 | color: #222; 23 | font-size: 28rpx; 24 | } 25 | 26 | .date { 27 | text-align: end; 28 | color: #757575; 29 | font-size: 24rpx; 30 | margin-top: 16rpx; 31 | } 32 | -------------------------------------------------------------------------------- /network/api.js: -------------------------------------------------------------------------------- 1 | const PAGE_SIZE = 20; 2 | 3 | function fetchData(gankType, page, callback) { 4 | wx.request({ 5 | url: 'http://gank.io/api/data/' + gankType + '/' + PAGE_SIZE + '/' + page, 6 | success: res => { 7 | callback.success(res) 8 | }, 9 | complete: () => { 10 | callback.complete() 11 | } 12 | }) 13 | } 14 | 15 | function search(keyword, page, callback) { 16 | wx.request({ 17 | url: 'http://gank.io/api/search/query/' + keyword + '/category/all/count/' + PAGE_SIZE + '/page/' + page, 18 | success: res => { 19 | callback.success(res) 20 | }, 21 | complete: () => { 22 | callback.complete() 23 | } 24 | }) 25 | } 26 | 27 | module.exports = { 28 | PAGE_SIZE: PAGE_SIZE, 29 | fetchData: fetchData, 30 | search: search 31 | } -------------------------------------------------------------------------------- /component/ganks/ganks.wxss: -------------------------------------------------------------------------------- 1 | /* component/ganks/ganks.wxss */ 2 | 3 | .gank-list { 4 | display: flex; 5 | flex-direction: column; 6 | padding: 10rpx 16rpx; 7 | width: 100%; 8 | height: 100%; 9 | align-items: center; 10 | justify-content: space-between; 11 | box-sizing: border-box; 12 | } 13 | 14 | .card { 15 | display: flex; 16 | flex-direction: column; 17 | background-color: #fff; 18 | width: 100%; 19 | border-radius: 6rpx; 20 | margin: 10rpx 0; 21 | padding: 10rpx 16rpx; 22 | box-sizing: border-box; 23 | box-shadow: 0 4rpx 6rpx 2rpx #ddd; 24 | } 25 | 26 | .title { 27 | color: #222; 28 | font-size: 28rpx; 29 | } 30 | 31 | .date { 32 | text-align: end; 33 | color: #757575; 34 | font-size: 24rpx; 35 | margin-top: 16rpx; 36 | } 37 | 38 | .loading { 39 | color: #bdbdbd; 40 | font-size: 24rpx; 41 | padding: 10rpx 0; 42 | } 43 | -------------------------------------------------------------------------------- /pages/meizi/meizi.wxss: -------------------------------------------------------------------------------- 1 | /* pages/meizhi/meizhi.wxss */ 2 | 3 | .meizi-container { 4 | width: 100%; 5 | height: 100%; 6 | padding: 10rpx 16rpx; 7 | box-sizing: border-box; 8 | } 9 | 10 | .meizi-list { 11 | width: 49%; 12 | height: 100%; 13 | } 14 | 15 | .left { 16 | float: left; 17 | } 18 | 19 | .right { 20 | float: right; 21 | } 22 | 23 | .meizi { 24 | width: 100%; 25 | border-radius: 6rpx; 26 | box-shadow: 0 4rpx 6rpx 2rpx #ddd; 27 | margin: 6rpx 0; 28 | } 29 | 30 | .single-image-container { 31 | background-color: rgba(0, 0, 0, 0.66); 32 | position: fixed; 33 | top: 0; 34 | left: 0; 35 | width: 100%; 36 | height: 100%; 37 | display: flex; 38 | justify-content: center; 39 | align-items: center; 40 | } 41 | 42 | .single-image { 43 | width: 90%; 44 | height: 100%; 45 | } 46 | 47 | .save { 48 | float: right; 49 | position: fixed; 50 | bottom: 0; 51 | right: 0; 52 | margin: 16px; 53 | background-color: #ffffff; 54 | border-radius: 500rpx; 55 | box-shadow: 0 4rpx 6rpx #000; 56 | } 57 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/meizi/meizi", 4 | "pages/android/android", 5 | "pages/ios/ios", 6 | "pages/details/details", 7 | "pages/web/web", 8 | "pages/video/video", 9 | "pages/search/search" 10 | ], 11 | "window": { 12 | "navigationBarBackgroundColor": "#607D8B", 13 | "navigationBarTitleText": "Gank.io", 14 | "navigationBarTextStyle": "white", 15 | "backgroundColor": "#607D8B", 16 | "backgroundTextStyle": "light" 17 | }, 18 | "tabBar": { 19 | "backgroundColor": "#607D8B", 20 | "color": "#BBBBBB", 21 | "selectedColor": "#FFFFFF", 22 | "position": "top", 23 | "list": [ 24 | { 25 | "pagePath": "pages/meizi/meizi", 26 | "text": "妹子" 27 | }, 28 | { 29 | "pagePath": "pages/android/android", 30 | "text": "Android" 31 | }, 32 | { 33 | "pagePath": "pages/ios/ios", 34 | "text": "iOS" 35 | }, 36 | { 37 | "pagePath": "pages/web/web", 38 | "text": "前端" 39 | }, 40 | { 41 | "pagePath": "pages/video/video", 42 | "text": "视频" 43 | } 44 | ] 45 | } 46 | } -------------------------------------------------------------------------------- /pages/meizi/meizi.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MpGank 2 | 第三方 Gank.io 微信小程序 3 | 4 | ## ??? 5 | 从学编程开始就不喜欢写网页,因为觉得这布局方式是真的迷啊,所以 HTML, CSS, JS 我是真的不怎么会.. 6 | 但是! 上周! 老大说! 移动端开发人员! 小程序也得会! 我..Fu.. 7 | 8 | ## 项目相关 9 | ### 界面长这样 10 |

11 | 12 | 13 | 14 |

15 | 16 | ### 有啥东西 17 | 1. 保存妹子图到系统相册 18 | 2. 瀑布流 19 | 3. 自定义组件 20 | 4. ... 21 | 22 | ### 注意 23 | 1. 需要打开 ```不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书``` 选项 24 | 2. 只能在调试模式下运行 25 | 3. `````` 只能在模拟器上才能打开网页 26 | 4. 有没有啥问题(比如兼容性啥的)我也不知道..还不是很熟悉.. 27 | 28 | ## 联系我 29 | E-MAIL: samuel.alva#outlook.com // 使用 @ 替换 # 30 | 新浪微博: [@打酱油的什么鬼](http://weibo.com/234394146) 31 | 32 | ## 开源协议 33 | Copyright 2018 SamuelGjk 34 | 35 | Licensed under the Apache License, Version 2.0 (the "License"); 36 | you may not use this file except in compliance with the License. 37 | You may obtain a copy of the License at 38 | 39 | http://www.apache.org/licenses/LICENSE-2.0 40 | 41 | Unless required by applicable law or agreed to in writing, software 42 | distributed under the License is distributed on an "AS IS" BASIS, 43 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 44 | See the License for the specific language governing permissions and 45 | limitations under the License. 46 | -------------------------------------------------------------------------------- /component/ganks/ganks.js: -------------------------------------------------------------------------------- 1 | // component/ganks/ganks.js 2 | 3 | var Api = require('../../network/api.js') 4 | var dateFormat = require('../../utils/dateformat.js') 5 | 6 | Component({ 7 | page: 1, 8 | 9 | /** 10 | * 组件的属性列表 11 | */ 12 | properties: { 13 | gankType: String 14 | }, 15 | 16 | /** 17 | * 组件的初始数据 18 | */ 19 | data: { 20 | ganks: [], 21 | loadingHidden: false 22 | }, 23 | 24 | /** 25 | * 组件的方法列表 26 | */ 27 | methods: { 28 | fetchData: function (clear) { 29 | this.page = clear ? 1 : ++this.page 30 | Api.fetchData(this.data.gankType, this.page, { 31 | success: res => { 32 | res.data.results = res.data.results.map(result => { 33 | result.publishedAt = dateFormat(new Date(result.publishedAt), "yyyy-mm-dd HH:MM") 34 | return result 35 | }) 36 | var items = clear ? res.data.results : this.data.ganks.concat(res.data.results) 37 | this.setData({ 38 | ganks: items, 39 | loadingHidden: items.length < Api.PAGE_SIZE 40 | }) 41 | }, 42 | complete: () => { 43 | if (clear) { 44 | setTimeout(() => { 45 | wx.stopPullDownRefresh() 46 | }, 1000) 47 | } 48 | } 49 | }) 50 | }, 51 | 52 | onItemClick: function (event) { 53 | var dataset = event.currentTarget.dataset 54 | wx.navigateTo({ 55 | url: '../details/details?title=' + dataset.title + '&url=' + event.currentTarget.dataset.url 56 | }) 57 | } 58 | } 59 | }) 60 | -------------------------------------------------------------------------------- /pages/search/search.js: -------------------------------------------------------------------------------- 1 | // pages/search/search.js 2 | 3 | var Api = require('../../network/api.js') 4 | var dateFormat = require('../../utils/dateformat.js') 5 | 6 | Page({ 7 | 8 | keyword: "", 9 | page: 1, 10 | 11 | data: { 12 | results: [], 13 | loadingHidden: true 14 | }, 15 | 16 | onPullDownRefresh: function () { 17 | this.doSearch(true) 18 | }, 19 | 20 | onReachBottom: function () { 21 | this.doSearch(false) 22 | }, 23 | 24 | onItemClick: function (event) { 25 | var dataset = event.currentTarget.dataset 26 | wx.navigateTo({ 27 | url: '../details/details?title=' + dataset.title + '&url=' + event.currentTarget.dataset.url 28 | }) 29 | }, 30 | 31 | onSearch: function (event) { 32 | this.keyword = event.detail.value 33 | if (this.keyword != "") { 34 | wx.showLoading({ 35 | title: "搜索中", 36 | mask: true 37 | }) 38 | } 39 | this.doSearch(true) 40 | }, 41 | 42 | doSearch: function (clear) { 43 | if (this.keyword == "") { 44 | if (this.data.results.length > 0) { 45 | this.setData({ 46 | results: [], 47 | loadingHidden: true 48 | }) 49 | } 50 | wx.showToast({ 51 | title: "关键词不能为空", 52 | icon: "none" 53 | }) 54 | wx.stopPullDownRefresh() 55 | return 56 | } 57 | 58 | this.page = clear ? 1 : ++this.page 59 | Api.search(this.keyword, this.page, { 60 | success: res => { 61 | res.data.results = res.data.results.map(result => { 62 | result.publishedAt = dateFormat(new Date(result.publishedAt), "yyyy-mm-dd HH:MM") 63 | return result 64 | }) 65 | var items = clear ? res.data.results : this.data.results.concat(res.data.results) 66 | if (items.length == 0) { 67 | wx.showToast({ 68 | title: "啥都没有", 69 | icon: "none" 70 | }) 71 | } 72 | this.setData({ 73 | results: items, 74 | loadingHidden: items.length < Api.PAGE_SIZE 75 | }) 76 | }, 77 | complete: () => { 78 | if (clear) { 79 | setTimeout(() => { 80 | wx.hideLoading() 81 | wx.stopPullDownRefresh() 82 | }, 1000) 83 | } 84 | } 85 | }) 86 | } 87 | }) -------------------------------------------------------------------------------- /pages/meizi/meizi.js: -------------------------------------------------------------------------------- 1 | // pages/meizhi/meizhi.js 2 | 3 | var Api = require('../../network/api.js') 4 | 5 | Page({ 6 | page: 1, 7 | 8 | data: { 9 | meizis: [], 10 | loadingHidden: false, 11 | singleImage: "", 12 | singleImageHidden: true, 13 | opacity: 0, 14 | opacityAnimation: {} 15 | }, 16 | 17 | onLoad: function (options) { 18 | this.animation = wx.createAnimation({ 19 | duration: 500, 20 | timingFunction: "ease" 21 | }).opacity(1) 22 | 23 | this.fetchData(true) 24 | }, 25 | 26 | onPullDownRefresh: function () { 27 | this.fetchData(true) 28 | }, 29 | 30 | onReachBottom: function () { 31 | this.fetchData(false) 32 | }, 33 | 34 | onItemClick: function (event) { 35 | this.setData({ 36 | singleImage: event.currentTarget.dataset.src, 37 | singleImageHidden: false 38 | }) 39 | }, 40 | 41 | onImageLoad: function (event) { 42 | this.setData({ 43 | opacityAnimation: this.animation.step().export() 44 | }) 45 | }, 46 | 47 | onTouchMove: function (event) { 48 | // Empty 49 | // 用于屏蔽底层页面滚动 50 | }, 51 | 52 | onSingleImageClick: function (event) { 53 | this.setData({ 54 | singleImage: "", 55 | opacity: 0, 56 | singleImageHidden: true 57 | }) 58 | }, 59 | 60 | saveImage: function (event) { 61 | wx.showLoading({ 62 | title: "正在下载图片", 63 | mask: true 64 | }) 65 | wx.downloadFile({ 66 | url: this.data.singleImage, 67 | success: res => { 68 | wx.saveImageToPhotosAlbum({ 69 | filePath: res.tempFilePath, 70 | success: () => { 71 | wx.showToast({ 72 | title: "图片已保存到相册", 73 | icon: "none" 74 | }) 75 | }, 76 | fail: () => { 77 | wx.showToast({ 78 | title: "图片保存失败", 79 | icon: "none" 80 | }) 81 | } 82 | }) 83 | }, 84 | complete: () => { 85 | wx.hideLoading() 86 | } 87 | }) 88 | }, 89 | 90 | onTabItemTap: function () { 91 | wx.pageScrollTo({ 92 | scrollTop: 0, 93 | duration: 300 94 | }) 95 | }, 96 | 97 | fetchData: function (clear) { 98 | this.page = clear ? 1 : ++this.page 99 | Api.fetchData("福利", this.page, { 100 | success: res => { 101 | var items = clear ? res.data.results : this.data.meizis.concat(res.data.results) 102 | this.setData({ 103 | meizis: items, 104 | loadingHidden: items.length < Api.PAGE_SIZE 105 | }) 106 | }, 107 | complete: () => { 108 | if (clear) { 109 | setTimeout(() => { 110 | wx.stopPullDownRefresh() 111 | }, 1000) 112 | } 113 | } 114 | }) 115 | } 116 | }) -------------------------------------------------------------------------------- /utils/dateformat.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Date Format 1.2.3 3 | * (c) 2007-2009 Steven Levithan 4 | * MIT license 5 | * 6 | * Includes enhancements by Scott Trenda 7 | * and Kris Kowal 8 | * 9 | * Accepts a date, a mask, or a date and a mask. 10 | * Returns a formatted version of the given date. 11 | * The date defaults to the current date/time. 12 | * The mask defaults to dateFormat.masks.default. 13 | */ 14 | 15 | (function(global) { 16 | 'use strict'; 17 | 18 | var dateFormat = (function() { 19 | var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZWN]|"[^"]*"|'[^']*'/g; 20 | var timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g; 21 | var timezoneClip = /[^-+\dA-Z]/g; 22 | 23 | // Regexes and supporting functions are cached through closure 24 | return function (date, mask, utc, gmt) { 25 | 26 | // You can't provide utc if you skip other args (use the 'UTC:' mask prefix) 27 | if (arguments.length === 1 && kindOf(date) === 'string' && !/\d/.test(date)) { 28 | mask = date; 29 | date = undefined; 30 | } 31 | 32 | date = date || new Date; 33 | 34 | if(!(date instanceof Date)) { 35 | date = new Date(date); 36 | } 37 | 38 | if (isNaN(date)) { 39 | throw TypeError('Invalid date'); 40 | } 41 | 42 | mask = String(dateFormat.masks[mask] || mask || dateFormat.masks['default']); 43 | 44 | // Allow setting the utc/gmt argument via the mask 45 | var maskSlice = mask.slice(0, 4); 46 | if (maskSlice === 'UTC:' || maskSlice === 'GMT:') { 47 | mask = mask.slice(4); 48 | utc = true; 49 | if (maskSlice === 'GMT:') { 50 | gmt = true; 51 | } 52 | } 53 | 54 | var _ = utc ? 'getUTC' : 'get'; 55 | var d = date[_ + 'Date'](); 56 | var D = date[_ + 'Day'](); 57 | var m = date[_ + 'Month'](); 58 | var y = date[_ + 'FullYear'](); 59 | var H = date[_ + 'Hours'](); 60 | var M = date[_ + 'Minutes'](); 61 | var s = date[_ + 'Seconds'](); 62 | var L = date[_ + 'Milliseconds'](); 63 | var o = utc ? 0 : date.getTimezoneOffset(); 64 | var W = getWeek(date); 65 | var N = getDayOfWeek(date); 66 | var flags = { 67 | d: d, 68 | dd: pad(d), 69 | ddd: dateFormat.i18n.dayNames[D], 70 | dddd: dateFormat.i18n.dayNames[D + 7], 71 | m: m + 1, 72 | mm: pad(m + 1), 73 | mmm: dateFormat.i18n.monthNames[m], 74 | mmmm: dateFormat.i18n.monthNames[m + 12], 75 | yy: String(y).slice(2), 76 | yyyy: y, 77 | h: H % 12 || 12, 78 | hh: pad(H % 12 || 12), 79 | H: H, 80 | HH: pad(H), 81 | M: M, 82 | MM: pad(M), 83 | s: s, 84 | ss: pad(s), 85 | l: pad(L, 3), 86 | L: pad(Math.round(L / 10)), 87 | t: H < 12 ? dateFormat.i18n.timeNames[0] : dateFormat.i18n.timeNames[1], 88 | tt: H < 12 ? dateFormat.i18n.timeNames[2] : dateFormat.i18n.timeNames[3], 89 | T: H < 12 ? dateFormat.i18n.timeNames[4] : dateFormat.i18n.timeNames[5], 90 | TT: H < 12 ? dateFormat.i18n.timeNames[6] : dateFormat.i18n.timeNames[7], 91 | Z: gmt ? 'GMT' : utc ? 'UTC' : (String(date).match(timezone) || ['']).pop().replace(timezoneClip, ''), 92 | o: (o > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), 93 | S: ['th', 'st', 'nd', 'rd'][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10], 94 | W: W, 95 | N: N 96 | }; 97 | 98 | return mask.replace(token, function (match) { 99 | if (match in flags) { 100 | return flags[match]; 101 | } 102 | return match.slice(1, match.length - 1); 103 | }); 104 | }; 105 | })(); 106 | 107 | dateFormat.masks = { 108 | 'default': 'ddd mmm dd yyyy HH:MM:ss', 109 | 'shortDate': 'm/d/yy', 110 | 'mediumDate': 'mmm d, yyyy', 111 | 'longDate': 'mmmm d, yyyy', 112 | 'fullDate': 'dddd, mmmm d, yyyy', 113 | 'shortTime': 'h:MM TT', 114 | 'mediumTime': 'h:MM:ss TT', 115 | 'longTime': 'h:MM:ss TT Z', 116 | 'isoDate': 'yyyy-mm-dd', 117 | 'isoTime': 'HH:MM:ss', 118 | 'isoDateTime': 'yyyy-mm-dd\'T\'HH:MM:sso', 119 | 'isoUtcDateTime': 'UTC:yyyy-mm-dd\'T\'HH:MM:ss\'Z\'', 120 | 'expiresHeaderFormat': 'ddd, dd mmm yyyy HH:MM:ss Z' 121 | }; 122 | 123 | // Internationalization strings 124 | dateFormat.i18n = { 125 | dayNames: [ 126 | 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 127 | 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' 128 | ], 129 | monthNames: [ 130 | 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 131 | 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' 132 | ], 133 | timeNames: [ 134 | 'a', 'p', 'am', 'pm', 'A', 'P', 'AM', 'PM' 135 | ] 136 | }; 137 | 138 | function pad(val, len) { 139 | val = String(val); 140 | len = len || 2; 141 | while (val.length < len) { 142 | val = '0' + val; 143 | } 144 | return val; 145 | } 146 | 147 | /** 148 | * Get the ISO 8601 week number 149 | * Based on comments from 150 | * http://techblog.procurios.nl/k/n618/news/view/33796/14863/Calculate-ISO-8601-week-and-year-in-javascript.html 151 | * 152 | * @param {Object} `date` 153 | * @return {Number} 154 | */ 155 | function getWeek(date) { 156 | // Remove time components of date 157 | var targetThursday = new Date(date.getFullYear(), date.getMonth(), date.getDate()); 158 | 159 | // Change date to Thursday same week 160 | targetThursday.setDate(targetThursday.getDate() - ((targetThursday.getDay() + 6) % 7) + 3); 161 | 162 | // Take January 4th as it is always in week 1 (see ISO 8601) 163 | var firstThursday = new Date(targetThursday.getFullYear(), 0, 4); 164 | 165 | // Change date to Thursday same week 166 | firstThursday.setDate(firstThursday.getDate() - ((firstThursday.getDay() + 6) % 7) + 3); 167 | 168 | // Check if daylight-saving-time-switch occurred and correct for it 169 | var ds = targetThursday.getTimezoneOffset() - firstThursday.getTimezoneOffset(); 170 | targetThursday.setHours(targetThursday.getHours() - ds); 171 | 172 | // Number of weeks between target Thursday and first Thursday 173 | var weekDiff = (targetThursday - firstThursday) / (86400000*7); 174 | return 1 + Math.floor(weekDiff); 175 | } 176 | 177 | /** 178 | * Get ISO-8601 numeric representation of the day of the week 179 | * 1 (for Monday) through 7 (for Sunday) 180 | * 181 | * @param {Object} `date` 182 | * @return {Number} 183 | */ 184 | function getDayOfWeek(date) { 185 | var dow = date.getDay(); 186 | if(dow === 0) { 187 | dow = 7; 188 | } 189 | return dow; 190 | } 191 | 192 | /** 193 | * kind-of shortcut 194 | * @param {*} val 195 | * @return {String} 196 | */ 197 | function kindOf(val) { 198 | if (val === null) { 199 | return 'null'; 200 | } 201 | 202 | if (val === undefined) { 203 | return 'undefined'; 204 | } 205 | 206 | if (typeof val !== 'object') { 207 | return typeof val; 208 | } 209 | 210 | if (Array.isArray(val)) { 211 | return 'array'; 212 | } 213 | 214 | return {}.toString.call(val) 215 | .slice(8, -1).toLowerCase(); 216 | }; 217 | 218 | 219 | 220 | if (typeof define === 'function' && define.amd) { 221 | define(function () { 222 | return dateFormat; 223 | }); 224 | } else if (typeof exports === 'object') { 225 | module.exports = dateFormat; 226 | } else { 227 | global.dateFormat = dateFormat; 228 | } 229 | })(this); 230 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------