├── .eslintrc.yaml
├── .github
└── FUNDING.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── assets
├── footer.css
└── thumb.svg
├── issues.png
├── lib
├── index.js
├── qrcode.js
└── template.hbs
├── package.json
├── page-footer-symmetrical.png
└── page-footer.png
/.eslintrc.yaml:
--------------------------------------------------------------------------------
1 |
2 | ---
3 | root : true
4 | extends : aleen42
5 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 | patreon: aleen42
3 | custom: ["http://paypal.me/aleen42", "https://www.buymeacoffee.com/aleen42"]
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
35 | # IDEA
36 | .idea
37 |
38 | # locked dependencies
39 | package-lock.json
40 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5"
4 | - "5.1"
5 | - "4"
6 | - "4.2"
7 | - "4.1"
8 | - "4.0"
9 | - "0.12"
10 | - "0.11"
11 | - "0.10"
12 | - "iojs"
13 | script: make test
14 | os:
15 | - linux
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Aleen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## page-footer
2 |
3 |  
4 |
5 | [](https://www.npmjs.com/package/gitbook-plugin-page-footer) [](https://travis-ci.org/aleen42/gitbook-footer) [](https://david-dm.org/aleen42/gitbook-footer#info=devDependencies) [](https://www.npmjs.com/package/gitbook-plugin-page-footer)
6 |
7 | A GitBook plugin to show issues and some other information of the repository behind each page.
8 |
9 | ### Normal Style
10 |
11 | 
12 |
13 | ### Symmetrical Style
14 |
15 | 
16 |
17 | ### Issues Part
18 |
19 | In this part, you can specify a repository and show your latest 8 opened issues or pull requests in a book:
20 |
21 | *Notice that: because there is a rate-limiting for calling GitHub API, so it's suggested to generate a token following the [site](https://github.com/blog/1509-personal-api-tokens). In addition, you can only update this part after rebuilding your books!*
22 |
23 | 
24 |
25 | ### Installation
26 |
27 | Add the following plugins to your `book.json` and run `gitbook install`
28 |
29 | ```json
30 | {
31 | "plugins": ["page-footer"]
32 | }
33 | ```
34 |
35 | ### Usage
36 |
37 | Just find the plugin on gitbook and install it on your gitbook project.
38 |
39 | The default configuration is:
40 |
41 | ```json
42 | {
43 | "plugins": [
44 | "page-footer"
45 | ],
46 | "pluginsConfig": {
47 | "page-footer": {
48 | "description": "modified at",
49 | "signature": "Aleen",
50 | "wisdom": "More than a coder, more than a designer",
51 | "format": "yyyy-MM-dd hh:mm:ss",
52 | "copyright": "Copyright © aleen42",
53 | "timeColor": "#666",
54 | "copyrightColor": "#666",
55 | "utcOffset": "8",
56 | "isShowQRCode": true,
57 | "isShowIssues": true,
58 | "baseUri": "https://wiki.aleen42.com/",
59 | "repo": "aleen42/PersonalWiki",
60 | "issueNum": "8",
61 | "token": "",
62 | "style": "normal"
63 | }
64 | }
65 | }
66 | ```
67 |
68 | ### Release History
69 |
70 | * ==================== **1.0.0 Initial release** ====================
71 | * 1.0.1 fix bugs
72 | * 1.0.2 fix bugs
73 | * 1.0.3 fix bugs
74 | * 1.0.4 fix bugs
75 | * 1.0.5 fix bugs
76 | * 1.0.6 update readme
77 | * 1.0.7 update package.json
78 | * 1.0.8 update readme
79 | * 1.0.9 update readme
80 | * 1.1.0 version restrict
81 | * 1.1.1 configuration
82 | * 1.1.2 configuration
83 | * 1.1.3 fix bugs
84 | * 1.1.4 fix bugs
85 | * 1.1.5 fix bugs
86 | * 1.1.6 fix bugs
87 | * 1.1.7 fix bugs
88 | * 1.1.8 fix bugs
89 | * 1.1.9 debug mode
90 | * 1.2.0 debug mode
91 | * 1.2.1 debug mode
92 | * 1.2.2 debug mode
93 | * 1.2.3 debug mode
94 | * 1.2.4 debug mode
95 | * ================ **1.3.3 released version** ================
96 | * 1.3.4 add title
97 | * ================ **1.4.3 released version** ================
98 | * ================ **1.4.6 released version** ================
99 | * 1.4.8 update readme
100 | * 1.4.9 update readme
101 | * ==================== **2.0.0 Featuring configuration** ====================
102 | * 2.0.1 update readme
103 | * 2.0.2 update dependency version
104 | * 2.0.3 update readme
105 | * 2.0.4 update readme
106 | * 2.0.5 update readme
107 | * 2.0.6 update readme
108 | * 2.0.7 update readme
109 | * 2.0.8 update readme
110 | * 2.0.9 update readme
111 | * 2.1.0 update readme
112 | * 2.1.1 update style
113 | * 2.1.2 unused version
114 | * 2.1.3 featuring Timezone
115 | * 2.1.4 fix bugs
116 | * 2.1.5 fix bugs
117 | * 2.1.6 fix bugs
118 | * 2.1.7 update readme
119 | * 2.8.9 unused version
120 | * ==================== **3.0.0 Featuring Qrcode** ====================
121 | * 3.0.1 update style
122 | * 3.0.6 fix bugs
123 | * 3.0.7 justify style for mobile
124 | * 3.0.8 justify style for mobile
125 | * 3.0.9 support optional styles
126 | * 3.1.0 featuring style of symmetrical
127 | * 3.1.1 fix bugs
128 | * 3.1.2 fix bugs
129 | * 3.1.3 fix bugs
130 | * 3.1.4 fix bugs
131 | * ==================== **4.0.0 Featuring Optional Styles** ====================
132 | * 4.0.1 modify description of options
133 | * 4.0.2 change qrcode's quality and size
134 | * 4.0.3 change stylesheet
135 | * 4.0.4 change stylesheet
136 | * 4.0.5 change stylesheet
137 | * 4.0.6 change stylesheet
138 | * 4.0.7 hotfix
139 | * 4.0.8 hotfix
140 | * 4.0.9 update readme
141 | * 4.1.9 update readme
142 | * 4.2.9 fix bugs of Gitbook engine 3.0.3
143 | * 4.3.0 update readme
144 | * ==================== **5.0.0 Featuring Issues Style** ====================
145 | * 5.0.1 update readme
146 | * 5.0.2 update default config
147 | * 5.0.4 fix bugs of issues [#4](https://github.com/aleen42/gitbook-footer/issues/4)
148 | * 5.0.5 update readme
149 | * 5.0.6 fix bugs
150 | * 5.0.7 fix bugs of issues [#5](https://github.com/aleen42/gitbook-footer/issues/5)
151 | * 5.0.8 update readme
152 | * 5.0.9 fix bugs
153 | * 5.1.0 fix bugs
154 | * 5.1.1 update styles
155 | * 5.1.2 update styles
156 | * 5.1.3 update styles
157 | * 5.1.4 update readme
158 | * 5.1.5 update readme
159 | * 5.1.6 optional issue number
160 | * 5.1.7 restrict reading book
161 | * 5.1.9 update default value
162 | * 5.2.0 fix bugs
163 | * 5.2.1 fix bugs
164 | * 5.2.2 merge pull requests of [#6](https://github.com/aleen42/gitbook-footer/issues/6)
165 | * 5.2.3 update test cases
166 | * 5.2.4 update readme
167 | * 5.2.5 update readme
168 | * 5.2.6 update code
169 | * 5.2.7 update readme
170 | * 5.3.0 calculate a proper color for the text in labels
171 | * 5.3.1 update readme
172 | * 5.3.2 fix timezone problem of UTC
173 | * 5.3.3 support GITHUB_TOKEN under travis-ci building
174 | * 5.3.4 fix the number of issues
175 | * 5.3.5 fix the problem of [#9](https://github.com/aleen42/gitbook-footer/issues/9)
176 | * 5.3.6 fix style and extend super option
177 | * 5.3.8 enhancement of styles
178 | * 5.3.9 fix style of normal type
179 | * 5.4.0 remove style of QRCode within normal type
180 | * 5.4.1 compatible style for gitbook themes
181 | * 5.4.3 deprecated token access way for GitHub
182 | * 5.4.7 use gitbook-color to support light or dark theme
183 | * 5.4.8 refactor
184 | * 5.4.9 fix the dependency problem [#13](https://github.com/aleen42/gitbook-footer/issues/13)
185 | * 5.5.0 eliminate different sizes of labels when changing color mode
186 |
187 | ### :fuelpump: How to contribute
188 |
189 | Have an idea? Found a bug? See [how to contribute](https://wiki.aleen42.com/contribution.html).
190 |
191 | ### :scroll: License
192 |
193 | [MIT](https://wiki.aleen42.com/MIT.html) © aleen42
194 |
195 | *Note: if you like this project, feel free to buy me a swimming chance:*
196 |
197 | [](http://paypal.me/aleen42) [](https://www.patreon.com/aleen42) [](https://www.buymeacoffee.com/aleen42)
198 |
--------------------------------------------------------------------------------
/assets/footer.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Common Parts
3 | */
4 |
5 | .footer {
6 | margin-top: 50px;
7 | padding: 10px 0 80px;
8 | font-size: 12px;
9 | position: relative
10 | }
11 |
12 | .paragraph {
13 | margin: 0 !important;
14 | line-height: normal !important;
15 | }
16 |
17 | .super {
18 | font-size: 0.8em !important;
19 | margin-left: 5px !important;
20 | }
21 |
22 | .box__issues {
23 | margin-top: 50px !important;
24 | text-align: center !important;
25 | }
26 |
27 | .issue-line {
28 | width: 50%;
29 | height: 0;
30 | display: inline-block;
31 | border-bottom: 1px solid var(--color-book-border, rgba(255, 255, 255, 0.2));
32 | text-align: center;
33 | box-sizing: border-box;
34 | margin: 0 auto;
35 | }
36 |
37 | .issue-header {
38 | font-size: 1.4em;
39 | height: 1.4em;
40 | position: relative;
41 | top: -0.7em;
42 | display: inline-block;
43 | padding: 0 10px;
44 | margin: 0 auto;
45 | background-color: var(--color-bg-primary, #fff);
46 | }
47 |
48 | .issue-label {
49 | float: right;
50 | padding: 0 3px !important;
51 | border-radius: 5px;
52 | margin-left: 10px;
53 | }
54 |
55 | .issue-label {
56 | --lightness-threshold: 0.453;
57 | --border-threshold: 0.96;
58 | --border-alpha: max(0,min(calc(var(--perceived-lightness)*100 - var(--border-threshold)*100),1));
59 | background: rgb(var(--label-r),var(--label-g),var(--label-b));
60 | border: 1px solid hsla(var(--label-h),calc(var(--label-s)*1%),calc((var(--label-l) - 25)*1%),var(--border-alpha));
61 | color: hsl(0,0%,calc(var(--lightness-switch)*100%));
62 | --perceived-lightness: calc(var(--label-r) * 0.2126 / 255 + var(--label-g) * 0.7152 / 255 + var(--label-b) * 0.0722 / 255);
63 | --lightness-switch: max(0, min(calc(var(--perceived-lightness) * -1000 - var(--lightness-threshold) * -1000), 1));
64 | }
65 |
66 | [data-color-mode=dark] .issue-label {
67 | --lightness-threshold: 0.6;
68 | --background-alpha: 0.18;
69 | --border-alpha: 0.3;
70 | --lighten-by: calc((var(--lightness-threshold) * 100 - var(--perceived-lightness) * 100) * var(--lightness-switch));
71 | background: rgba(var(--label-r), var(--label-g), var(--label-b), var(--background-alpha));
72 | border: 1px solid hsla(var(--label-h), calc(var(--label-s) * 1%), calc((var(--label-l) + var(--lighten-by)) * 1%), var(--border-alpha));
73 | color: hsl(var(--label-h), calc(var(--label-s) * 1%), calc((var(--label-l) + var(--lighten-by)) * 1%));
74 | }
75 |
76 | .issue-edge {
77 | border-top: 1px solid var(--color-book-border, rgba(255, 255, 255, 0.2));
78 | }
79 |
80 | .issues {
81 | margin: 0 !important;
82 | text-align: left;
83 | }
84 |
85 | @media screen and (min-width: 720px) {
86 | .issue-edge {
87 | width: 60%;
88 | margin: 0 0 auto 100px !important;
89 | }
90 |
91 | .issues {
92 | padding: 10px 60px !important;
93 | }
94 | }
95 |
96 | @media screen and (max-width: 720px) {
97 | .issue-edge {
98 | width: 30%;
99 | margin: 0 0 auto 20px !important;
100 | }
101 |
102 | .issues {
103 | padding: 10px !important;
104 | }
105 | }
106 |
107 | /**
108 | *
109 | *
110 | *
111 | * Normal Style
112 | *
113 | *
114 | *
115 | *
116 | */
117 |
118 | .footer__container--normal {
119 | display: flex !important;
120 | }
121 |
122 | .footer__description--normal {
123 | margin: 0 10px !important;
124 | vertical-align: top !important;
125 | flex: 1;
126 | position: relative;
127 | }
128 |
129 | .footer__author--normal {
130 | font-size: 2em !important;
131 | border-bottom: 1px solid var(--color-book-border, rgba(255, 255, 255, 0.2));
132 | padding-bottom: 10px;
133 | }
134 |
135 | .footer__quote--normal {
136 | word-break: break-all;
137 | padding: 10px 0;
138 | }
139 |
140 | .footer__modifyTime--normal {
141 | float: right;
142 | }
143 |
144 | .footer__main--normal {
145 | float: right;
146 | font-size: 12px !important;
147 | }
148 |
149 | .footer__main__paragraph--normal {
150 | margin: 3px 0 !important;
151 | }
152 |
153 | /**
154 | *
155 | *
156 | *
157 | * Symmetrical Style
158 | *
159 | *
160 | *
161 | *
162 | */
163 |
164 | .footer__container--symmetrical {
165 | text-align: center !important;
166 | }
167 |
168 | .footer__description--symmetrical {
169 | padding: 0 10px !important;
170 | vertical-align: top !important;
171 | flex: 1;
172 | }
173 |
174 | .footer__author--symmetrical {
175 | font-size: 2em !important;
176 | padding-left: 0.8em !important;
177 | border-bottom: 1px solid var(--color-book-border, rgba(255, 255, 255, 0.2));
178 | padding-bottom: 10px;
179 | }
180 |
181 | .footer__modifyTime--symmetrical {
182 | float: right;
183 | }
184 |
185 | @media screen and (min-width: 720px) {
186 | /* styles applied when the page/app is viewed on a screen with a width greater than or equal to 720px */
187 | .footer__quote--symmetrical {
188 | margin-top: 10px !important;
189 | }
190 | .footer__main--symmetrical {
191 | position: absolute;
192 | right: 0;
193 | font-size: 12px !important;
194 | text-align: right;
195 | bottom: 0;
196 | }
197 | }
198 |
199 | @media screen and (max-width: 720px) {
200 | /* styles applied when the page/app is viewed on a screen with a width greater than or equal to 720px */
201 | .footer__quote--symmetrical {
202 | display: none;
203 | }
204 | .footer__main--symmetrical {
205 | font-size: 12px !important;
206 | margin-top: 10px !important;
207 | text-align: right;
208 | }
209 | }
210 |
211 | .footer__main__paragraph--symmetrical {
212 | margin: 3px 0 !important;
213 | }
214 |
215 | .footer svg > path {
216 | fill: var(--color-text-primary, #24292f);
217 | }
218 |
--------------------------------------------------------------------------------
/assets/thumb.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
27 |
--------------------------------------------------------------------------------
/issues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleen42/gitbook-footer/e8035d7fe0e28da99e4f62aa517f665f7f64dcb1/issues.png
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | /***********************************************************************
2 | * _
3 | * _____ _ ____ _ |_|
4 | * | _ |/ \ ____ ____ __ ___ / ___\/ \ __ _ ____ _
5 | * | |_| || | / __ \/ __ \\ '_ \ _ / / | |___\ \ | |/ __ \| |
6 | * | _ || |__. ___/. ___/| | | ||_|\ \___ | _ | |_| |. ___/| |
7 | * |_/ \_|\___/\____|\____||_| |_| \____/|_| |_|_____|\____||_|
8 | *
9 | * ================================================================
10 | * More than a coder, More than a designer
11 | * ================================================================
12 | *
13 | *
14 | * - Document: index.js
15 | * - Author: aleen42
16 | * - Description: the main entrance for page-footer
17 | * - Create Time: Apr 16th, 2016
18 | * - Update Time: Jul 26th, 2021
19 | *
20 | *
21 | **********************************************************************/
22 |
23 | const fs = require('fs');
24 | const path = require('path');
25 | const Handlebars = require('handlebars');
26 | const tplStr = fs.readFileSync(path.resolve(__dirname, './template.hbs'), 'utf8');
27 |
28 | /**
29 | * [request: the request module]
30 | * @type {[type]}
31 | */
32 | const syncReq = require('sync-request');
33 |
34 | /**
35 | * [request: the request module]
36 | * @type {[type]}
37 | */
38 | const NodeCache = require('node-cache');
39 | const localCache = new NodeCache({});
40 |
41 | /** include qrcode.js */
42 | const qrcode = require('./qrcode.js');
43 |
44 | /** set Date protocol */
45 | // eslint-disable-next-line no-extend-native
46 | Date.prototype.format = function (format) {
47 | const date = {
48 | 'M+': this.getMonth() + 1,
49 | 'd+': this.getDate(),
50 | 'h+': this.getHours(),
51 | 'm+': this.getMinutes(),
52 | 's+': this.getSeconds(),
53 | 'q+': Math.floor((this.getMonth() + 3) / 3),
54 | 'S+': this.getMilliseconds(),
55 | };
56 |
57 | if (/(y+)/i.test(format)) {
58 | format = format.replace(RegExp.$1, `${this.getFullYear()}`.substr(4 - RegExp.$1.length));
59 | }
60 |
61 | Object.entries(date).forEach(([k, v]) => {
62 | if (new RegExp(`(${k})`).test(format)) {
63 | format = format.replace(RegExp.$1, RegExp.$1.length === 1 ? v : `00${v}`.substr(`${v}`.length));
64 | }
65 | });
66 | return format;
67 | };
68 |
69 | /**
70 | * [defaultOption: default option]
71 | * @type {Object}
72 | */
73 | const defaultOption = {
74 | description: 'modified at',
75 | signature: 'Aleen',
76 | wisdom: 'More than a coder, more than a designer',
77 | format: 'yyyy-MM-dd hh:mm:ss',
78 | copyright: 'Copyright © aleen42',
79 | timeColor: '#666',
80 | copyrightColor: '#666',
81 | utcOffset: '8',
82 | isShowQRCode: true,
83 | baseUri: 'https://aleen42.gitbooks.io/personalwiki/content/',
84 | isShowIssues: true,
85 | repo: 'aleen42/PersonalWiki',
86 | issueNum: '8',
87 | style: 'normal',
88 | super: '®',
89 | };
90 |
91 | // noinspection JSUnusedGlobalSymbols
92 | /**
93 | * [main module]
94 | * @type {Object}
95 | */
96 | module.exports = {
97 | generate,
98 | /** Map of new style */
99 | book: {
100 | assets: './assets',
101 | css: ['footer.css'],
102 | },
103 |
104 | /** Map of hooks */
105 | hooks: {
106 | 'page:before': function (page) {
107 | /** add contents to the original content */
108 | if (this.output.name === 'website') {
109 | page.content = page.content + generate(this.config.get('pluginsConfig')['page-footer']);
110 | }
111 |
112 | return page;
113 | },
114 | },
115 |
116 | /** Map of new filters */
117 | filters: {
118 | dateFormat: function (d, format, utc) {
119 | let reservedDate = new Date(d);
120 | /** convert to UTC firstly */
121 | reservedDate = new Date(
122 | reservedDate.getUTCFullYear(),
123 | reservedDate.getUTCMonth(),
124 | reservedDate.getUTCDate(),
125 | reservedDate.getUTCHours(),
126 | reservedDate.getUTCMinutes(),
127 | reservedDate.getUTCSeconds(),
128 | );
129 | reservedDate.setTime(reservedDate.getTime() + (!utc ? 8 : parseInt(utc)) * 60 * 60 * 1000);
130 | return reservedDate.format(format);
131 | },
132 |
133 | currentURI: function (d, baseUri) {
134 | // noinspection JSUnresolvedFunction
135 | return this.output.name === 'website' ? createQRCode(baseUri + this.output.toURL(d), 15, 'Q') : '';
136 | },
137 | },
138 | };
139 |
140 | function hex2rgb(hex) {
141 | return Array(3).fill('').map((_, i) => parseInt(hex.slice(2 * i, 2 * (i + 1)), 16));
142 | }
143 |
144 | function rgb2hsl(r, g, b) {
145 | r /= 255;
146 | g /= 255;
147 | b /= 255;
148 | const max = Math.max(r, g, b);
149 | const min = Math.min(r, g, b);
150 | let h = 0;
151 | let s;
152 | let l = (max + min) / 2;
153 | if (max === min) {
154 | h = s = 0;
155 | } else {
156 | const d = max - min;
157 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
158 | switch (max) {
159 | case r:
160 | h = (g - b) / d + (g < b ? 6 : 0);
161 | break;
162 | case g:
163 | h = (b - r) / d + 2;
164 | break;
165 | case b:
166 | h = (r - g) / d + 4;
167 | }
168 | h /= 6;
169 | }
170 | h = h * 360;
171 | s = s * 100;
172 | l = l * 100;
173 | return [h, s, l];
174 | }
175 |
176 | function createQRCode(text, typeNumber, errorCorrectLevel) {
177 | const qr = qrcode(typeNumber || 10, errorCorrectLevel || 'H');
178 | qr.addData(text);
179 | qr.make();
180 |
181 | return qr.createSvgTag();
182 | }
183 |
184 | function generate(configs) {
185 | const options = {
186 | ...defaultOption,
187 | ...configs,
188 | copyright: `${configs.copyright || defaultOption.copyright} all right reserved, powered by aleen42`,
189 | };
190 |
191 | return Handlebars.compile(tplStr)({
192 | ...options,
193 | style: /normal|symmetrical/.test(options.style) ? options.style : 'normal',
194 | issues : options.isShowIssues && listIssues(options),
195 | });
196 | }
197 |
198 | function listIssues({ token: configToken, repo, format, utcOffset, issueNum }) {
199 | const token = configToken || process.env.ACCESS_TOKEN;
200 |
201 | /** clear cache at the first time */
202 | if (localCache.get('cleared') !== 'true') {
203 | localCache.del('issues');
204 | localCache.set('cleared', 'true');
205 | }
206 |
207 | const result = localCache.get('issues') || (() => {
208 | const UA = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36';
209 | const res = syncReq('GET', `https://api.github.com/repos/${repo}/issues?per_page=${issueNum}`, {
210 | headers: {
211 | 'user-agent': UA,
212 | ...token && { Authorization: `token ${token}` },
213 | },
214 | });
215 |
216 | if (res.statusCode === 200) {
217 | // noinspection JSUnresolvedFunction
218 | localCache.set('issues', res.getBody().toString());
219 | return localCache.get('issues');
220 | }
221 | })();
222 |
223 | if (!result) return '';
224 |
225 | /** parse json */
226 | const issues = JSON.parse(result);
227 | return ` ${issues.length} issues reported
#${number} ${title}${date.format(format)}${(labels.map(label => { 233 | const rgb = hex2rgb(label.color); 234 | const r = rgb[0], g = rgb[1], b = rgb[2]; 235 | const hsl = rgb2hsl(r, g, b); 236 | const h = hsl[0], s = hsl[1], l = hsl[2]; 237 | 238 | return `${label.name}`; 241 | }).join(''))}
`; 242 | }).join(''); 243 | } 244 | -------------------------------------------------------------------------------- /lib/qrcode.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // 3 | // QR Code Generator for JavaScript 4 | // 5 | // Copyright (c) 2009 Kazuhiko Arase 6 | // 7 | // URL: http://www.d-project.com/ 8 | // 9 | // Licensed under the MIT license: 10 | // http://www.opensource.org/licenses/mit-license.php 11 | // 12 | // The word 'QR Code' is registered trademark of 13 | // DENSO WAVE INCORPORATED 14 | // http://www.denso-wave.com/qrcode/faqpatent-e.html 15 | // 16 | //--------------------------------------------------------------------- 17 | 18 | module.exports = function() { 19 | //--------------------------------------------------------------------- 20 | // qrcode 21 | //--------------------------------------------------------------------- 22 | 23 | /** 24 | * qrcode 25 | * @param typeNumber 1 to 40 26 | * @param errorCorrectLevel 'L','M','Q','H' 27 | */ 28 | var qrcode = function(typeNumber, errorCorrectLevel) { 29 | 30 | var PAD0 = 0xEC; 31 | var PAD1 = 0x11; 32 | 33 | var _typeNumber = typeNumber; 34 | var _errorCorrectLevel = QRErrorCorrectLevel[errorCorrectLevel]; 35 | var _modules = null; 36 | var _moduleCount = 0; 37 | var _dataCache = null; 38 | var _dataList = new Array(); 39 | 40 | var _this = {}; 41 | 42 | var makeImpl = function(test, maskPattern) { 43 | 44 | _moduleCount = _typeNumber * 4 + 17; 45 | _modules = function(moduleCount) { 46 | var modules = new Array(moduleCount); 47 | for (var row = 0; row < moduleCount; row += 1) { 48 | modules[row] = new Array(moduleCount); 49 | for (var col = 0; col < moduleCount; col += 1) { 50 | modules[row][col] = null; 51 | } 52 | } 53 | return modules; 54 | }(_moduleCount); 55 | 56 | setupPositionProbePattern(0, 0); 57 | setupPositionProbePattern(_moduleCount - 7, 0); 58 | setupPositionProbePattern(0, _moduleCount - 7); 59 | setupPositionAdjustPattern(); 60 | setupTimingPattern(); 61 | setupTypeInfo(test, maskPattern); 62 | 63 | if (_typeNumber >= 7) { 64 | setupTypeNumber(test); 65 | } 66 | 67 | if (_dataCache == null) { 68 | _dataCache = createData(_typeNumber, _errorCorrectLevel, _dataList); 69 | } 70 | 71 | mapData(_dataCache, maskPattern); 72 | }; 73 | 74 | var setupPositionProbePattern = function(row, col) { 75 | 76 | for (var r = -1; r <= 7; r += 1) { 77 | 78 | if (row + r <= -1 || _moduleCount <= row + r) continue; 79 | 80 | for (var c = -1; c <= 7; c += 1) { 81 | 82 | if (col + c <= -1 || _moduleCount <= col + c) continue; 83 | 84 | if ((0 <= r && r <= 6 && (c == 0 || c == 6)) || (0 <= c && c <= 6 && (r == 0 || r == 6)) || (2 <= r && r <= 4 && 2 <= c && c <= 4)) { 85 | _modules[row + r][col + c] = true; 86 | } else { 87 | _modules[row + r][col + c] = false; 88 | } 89 | } 90 | } 91 | }; 92 | 93 | var getBestMaskPattern = function() { 94 | 95 | var minLostPoint = 0; 96 | var pattern = 0; 97 | 98 | for (var i = 0; i < 8; i += 1) { 99 | 100 | makeImpl(true, i); 101 | 102 | var lostPoint = QRUtil.getLostPoint(_this); 103 | 104 | if (i == 0 || minLostPoint > lostPoint) { 105 | minLostPoint = lostPoint; 106 | pattern = i; 107 | } 108 | } 109 | 110 | return pattern; 111 | }; 112 | 113 | var setupTimingPattern = function() { 114 | 115 | for (var r = 8; r < _moduleCount - 8; r += 1) { 116 | if (_modules[r][6] != null) { 117 | continue; 118 | } 119 | _modules[r][6] = (r % 2 == 0); 120 | } 121 | 122 | for (var c = 8; c < _moduleCount - 8; c += 1) { 123 | if (_modules[6][c] != null) { 124 | continue; 125 | } 126 | _modules[6][c] = (c % 2 == 0); 127 | } 128 | }; 129 | 130 | var setupPositionAdjustPattern = function() { 131 | 132 | var pos = QRUtil.getPatternPosition(_typeNumber); 133 | 134 | for (var i = 0; i < pos.length; i += 1) { 135 | 136 | for (var j = 0; j < pos.length; j += 1) { 137 | 138 | var row = pos[i]; 139 | var col = pos[j]; 140 | 141 | if (_modules[row][col] != null) { 142 | continue; 143 | } 144 | 145 | for (var r = -2; r <= 2; r += 1) { 146 | 147 | for (var c = -2; c <= 2; c += 1) { 148 | 149 | if (r == -2 || r == 2 || c == -2 || c == 2 || (r == 0 && c == 0)) { 150 | _modules[row + r][col + c] = true; 151 | } else { 152 | _modules[row + r][col + c] = false; 153 | } 154 | } 155 | } 156 | } 157 | } 158 | }; 159 | 160 | var setupTypeNumber = function(test) { 161 | 162 | var bits = QRUtil.getBCHTypeNumber(_typeNumber); 163 | 164 | for (var i = 0; i < 18; i += 1) { 165 | var mod = (!test && ((bits >> i) & 1) == 1); 166 | _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod; 167 | } 168 | 169 | for (var i = 0; i < 18; i += 1) { 170 | var mod = (!test && ((bits >> i) & 1) == 1); 171 | _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod; 172 | } 173 | }; 174 | 175 | var setupTypeInfo = function(test, maskPattern) { 176 | 177 | var data = (_errorCorrectLevel << 3) | maskPattern; 178 | var bits = QRUtil.getBCHTypeInfo(data); 179 | 180 | // vertical 181 | for (var i = 0; i < 15; i += 1) { 182 | 183 | var mod = (!test && ((bits >> i) & 1) == 1); 184 | 185 | if (i < 6) { 186 | _modules[i][8] = mod; 187 | } else if (i < 8) { 188 | _modules[i + 1][8] = mod; 189 | } else { 190 | _modules[_moduleCount - 15 + i][8] = mod; 191 | } 192 | } 193 | 194 | // horizontal 195 | for (var i = 0; i < 15; i += 1) { 196 | 197 | var mod = (!test && ((bits >> i) & 1) == 1); 198 | 199 | if (i < 8) { 200 | _modules[8][_moduleCount - i - 1] = mod; 201 | } else if (i < 9) { 202 | _modules[8][15 - i - 1 + 1] = mod; 203 | } else { 204 | _modules[8][15 - i - 1] = mod; 205 | } 206 | } 207 | 208 | // fixed module 209 | _modules[_moduleCount - 8][8] = (!test); 210 | }; 211 | 212 | var mapData = function(data, maskPattern) { 213 | 214 | var inc = -1; 215 | var row = _moduleCount - 1; 216 | var bitIndex = 7; 217 | var byteIndex = 0; 218 | var maskFunc = QRUtil.getMaskFunction(maskPattern); 219 | 220 | for (var col = _moduleCount - 1; col > 0; col -= 2) { 221 | 222 | if (col == 6) col -= 1; 223 | 224 | while (true) { 225 | 226 | for (var c = 0; c < 2; c += 1) { 227 | 228 | if (_modules[row][col - c] == null) { 229 | 230 | var dark = false; 231 | 232 | if (byteIndex < data.length) { 233 | dark = (((data[byteIndex] >>> bitIndex) & 1) == 1); 234 | } 235 | 236 | var mask = maskFunc(row, col - c); 237 | 238 | if (mask) { 239 | dark = !dark; 240 | } 241 | 242 | _modules[row][col - c] = dark; 243 | bitIndex -= 1; 244 | 245 | if (bitIndex == -1) { 246 | byteIndex += 1; 247 | bitIndex = 7; 248 | } 249 | } 250 | } 251 | 252 | row += inc; 253 | 254 | if (row < 0 || _moduleCount <= row) { 255 | row -= inc; 256 | inc = -inc; 257 | break; 258 | } 259 | } 260 | } 261 | }; 262 | 263 | var createBytes = function(buffer, rsBlocks) { 264 | 265 | var offset = 0; 266 | 267 | var maxDcCount = 0; 268 | var maxEcCount = 0; 269 | 270 | var dcdata = new Array(rsBlocks.length); 271 | var ecdata = new Array(rsBlocks.length); 272 | 273 | for (var r = 0; r < rsBlocks.length; r += 1) { 274 | 275 | var dcCount = rsBlocks[r].dataCount; 276 | var ecCount = rsBlocks[r].totalCount - dcCount; 277 | 278 | maxDcCount = Math.max(maxDcCount, dcCount); 279 | maxEcCount = Math.max(maxEcCount, ecCount); 280 | 281 | dcdata[r] = new Array(dcCount); 282 | 283 | for (var i = 0; i < dcdata[r].length; i += 1) { 284 | dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset]; 285 | } 286 | offset += dcCount; 287 | 288 | var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount); 289 | var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1); 290 | 291 | var modPoly = rawPoly.mod(rsPoly); 292 | ecdata[r] = new Array(rsPoly.getLength() - 1); 293 | for (var i = 0; i < ecdata[r].length; i += 1) { 294 | var modIndex = i + modPoly.getLength() - ecdata[r].length; 295 | ecdata[r][i] = (modIndex >= 0) ? modPoly.getAt(modIndex) : 0; 296 | } 297 | } 298 | 299 | var totalCodeCount = 0; 300 | for (var i = 0; i < rsBlocks.length; i += 1) { 301 | totalCodeCount += rsBlocks[i].totalCount; 302 | } 303 | 304 | var data = new Array(totalCodeCount); 305 | var index = 0; 306 | 307 | for (var i = 0; i < maxDcCount; i += 1) { 308 | for (var r = 0; r < rsBlocks.length; r += 1) { 309 | if (i < dcdata[r].length) { 310 | data[index] = dcdata[r][i]; 311 | index += 1; 312 | } 313 | } 314 | } 315 | 316 | for (var i = 0; i < maxEcCount; i += 1) { 317 | for (var r = 0; r < rsBlocks.length; r += 1) { 318 | if (i < ecdata[r].length) { 319 | data[index] = ecdata[r][i]; 320 | index += 1; 321 | } 322 | } 323 | } 324 | 325 | return data; 326 | }; 327 | 328 | var createData = function(typeNumber, errorCorrectLevel, dataList) { 329 | 330 | var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel); 331 | 332 | var buffer = qrBitBuffer(); 333 | 334 | for (var i = 0; i < dataList.length; i += 1) { 335 | var data = dataList[i]; 336 | buffer.put(data.getMode(), 4); 337 | buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber)); 338 | data.write(buffer); 339 | } 340 | 341 | // calc num max data. 342 | var totalDataCount = 0; 343 | for (var i = 0; i < rsBlocks.length; i += 1) { 344 | totalDataCount += rsBlocks[i].dataCount; 345 | } 346 | 347 | if (buffer.getLengthInBits() > totalDataCount * 8) { 348 | throw new Error('code length overflow. (' + buffer.getLengthInBits() + '>' + totalDataCount * 8 + ')'); 349 | } 350 | 351 | // end code 352 | if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) { 353 | buffer.put(0, 4); 354 | } 355 | 356 | // padding 357 | while (buffer.getLengthInBits() % 8 != 0) { 358 | buffer.putBit(false); 359 | } 360 | 361 | // padding 362 | while (true) { 363 | 364 | if (buffer.getLengthInBits() >= totalDataCount * 8) { 365 | break; 366 | } 367 | buffer.put(PAD0, 8); 368 | 369 | if (buffer.getLengthInBits() >= totalDataCount * 8) { 370 | break; 371 | } 372 | buffer.put(PAD1, 8); 373 | } 374 | 375 | return createBytes(buffer, rsBlocks); 376 | }; 377 | 378 | _this.addData = function(data) { 379 | var newData = qr8BitByte(data); 380 | _dataList.push(newData); 381 | _dataCache = null; 382 | }; 383 | 384 | _this.isDark = function(row, col) { 385 | if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) { 386 | throw new Error(row + ',' + col); 387 | } 388 | return _modules[row][col]; 389 | }; 390 | 391 | _this.getModuleCount = function() { 392 | return _moduleCount; 393 | }; 394 | 395 | _this.make = function() { 396 | makeImpl(false, getBestMaskPattern()); 397 | }; 398 | 399 | _this.createTableTag = function(cellSize, margin) { 400 | 401 | cellSize = cellSize || 2; 402 | margin = (typeof margin == 'undefined') ? cellSize * 4 : margin; 403 | 404 | var qrHtml = ''; 405 | 406 | qrHtml += ''; 428 | } 429 | 430 | qrHtml += ' |