├── .editorconfig ├── .gitignore ├── LICENSE.txt ├── README.md ├── config.xml ├── docs ├── assets │ ├── icon │ │ └── favicon.ico │ ├── image │ │ ├── screenshot1.png │ │ └── screenshot2.png │ └── logo.svg ├── index.html ├── oauth │ └── callback.html └── statement.html ├── gitmug.keystore ├── ionic.config.json ├── package.json ├── resources ├── _icon.psd ├── _splash.psd ├── android │ ├── icon │ │ ├── drawable-hdpi-icon.png │ │ ├── drawable-ldpi-icon.png │ │ ├── drawable-mdpi-icon.png │ │ ├── drawable-xhdpi-icon.png │ │ ├── drawable-xxhdpi-icon.png │ │ └── drawable-xxxhdpi-icon.png │ └── splash │ │ ├── drawable-land-hdpi-screen.png │ │ ├── drawable-land-ldpi-screen.png │ │ ├── drawable-land-mdpi-screen.png │ │ ├── drawable-land-xhdpi-screen.png │ │ ├── drawable-land-xxhdpi-screen.png │ │ ├── drawable-land-xxxhdpi-screen.png │ │ ├── drawable-port-hdpi-screen.png │ │ ├── drawable-port-ldpi-screen.png │ │ ├── drawable-port-mdpi-screen.png │ │ ├── drawable-port-xhdpi-screen.png │ │ ├── drawable-port-xxhdpi-screen.png │ │ └── drawable-port-xxxhdpi-screen.png ├── glass.ico ├── glass.png ├── icon.png ├── icon.png.md5 ├── ios │ ├── icon │ │ ├── icon-1024.png │ │ ├── icon-40.png │ │ ├── icon-40@2x.png │ │ ├── icon-40@3x.png │ │ ├── icon-50.png │ │ ├── icon-50@2x.png │ │ ├── icon-60.png │ │ ├── icon-60@2x.png │ │ ├── icon-60@3x.png │ │ ├── icon-72.png │ │ ├── icon-72@2x.png │ │ ├── icon-76.png │ │ ├── icon-76@2x.png │ │ ├── icon-83.5@2x.png │ │ ├── icon-small.png │ │ ├── icon-small@2x.png │ │ ├── icon-small@3x.png │ │ ├── icon.png │ │ └── icon@2x.png │ └── splash │ │ ├── Default-568h@2x~iphone.png │ │ ├── Default-667h.png │ │ ├── Default-736h.png │ │ ├── Default-Landscape-736h.png │ │ ├── Default-Landscape@2x~ipad.png │ │ ├── Default-Landscape@~ipadpro.png │ │ ├── Default-Landscape~ipad.png │ │ ├── Default-Portrait@2x~ipad.png │ │ ├── Default-Portrait@~ipadpro.png │ │ ├── Default-Portrait~ipad.png │ │ ├── Default@2x~iphone.png │ │ ├── Default@2x~universal~anyany.png │ │ └── Default~iphone.png ├── logo.svg ├── splash.png └── splash.png.md5 ├── screenshots ├── homepage.png ├── iPad12.9 │ ├── Simulator Screen Shot 2017年8月22日 18.55.01.png │ ├── Simulator Screen Shot 2017年8月22日 18.55.05.png │ ├── Simulator Screen Shot 2017年8月22日 18.55.17.png │ └── Simulator Screen Shot 2017年8月23日 15.43.17.png ├── iPhone4.7 │ ├── Simulator Screen Shot 2017年8月22日 18.37.32.png │ ├── Simulator Screen Shot 2017年8月22日 18.37.34.png │ ├── Simulator Screen Shot 2017年8月22日 18.37.35.png │ └── Simulator Screen Shot 2017年8月23日 15.38.29.png └── iPhone5.5 │ ├── Simulator Screen Shot 2017年8月22日 18.51.21.png │ ├── Simulator Screen Shot 2017年8月22日 18.51.25.png │ ├── Simulator Screen Shot 2017年8月22日 18.51.34.png │ └── Simulator Screen Shot 2017年8月23日 15.40.38.png ├── signApk.sh ├── src ├── app │ ├── app.component.ts │ ├── app.html │ ├── app.module.ts │ ├── app.scss │ ├── const.ts │ └── main.ts ├── assets │ ├── icon │ │ └── favicon.ico │ └── logo.svg ├── classes │ ├── content.ts │ ├── language-color.ts │ ├── nodes-page.ts │ ├── repo.ts │ ├── user.ts │ └── version.ts ├── components │ ├── components.module.ts │ ├── markdown │ │ ├── markdown.html │ │ ├── markdown.scss │ │ └── markdown.ts │ ├── repo-item │ │ ├── repo-item.html │ │ ├── repo-item.scss │ │ └── repo-item.ts │ └── search-box │ │ ├── search-box.html │ │ ├── search-box.scss │ │ └── search-box.ts ├── index.html ├── manifest.json ├── pages │ ├── bootstrap │ │ ├── bootstrap.html │ │ ├── bootstrap.module.ts │ │ ├── bootstrap.scss │ │ └── bootstrap.ts │ ├── code-detail │ │ ├── code-detail.html │ │ ├── code-detail.module.ts │ │ ├── code-detail.scss │ │ └── code-detail.ts │ ├── code-list │ │ ├── code-list.html │ │ ├── code-list.module.ts │ │ ├── code-list.scss │ │ └── code-list.ts │ ├── content-list │ │ ├── content-list.html │ │ ├── content-list.module.ts │ │ ├── content-list.scss │ │ └── content-list.ts │ ├── dashboard │ │ ├── dashboard.html │ │ ├── dashboard.module.ts │ │ ├── dashboard.scss │ │ └── dashboard.ts │ ├── repo-list │ │ ├── hot-repos.ts │ │ ├── owned-repos.ts │ │ ├── repo-forks.ts │ │ ├── repo-list.html │ │ ├── repo-list.module.ts │ │ ├── repo-list.scss │ │ ├── repo-list.ts │ │ ├── search-repos.ts │ │ └── starred-repos.ts │ ├── repo │ │ ├── repo.html │ │ ├── repo.module.ts │ │ ├── repo.scss │ │ └── repo.ts │ ├── settings │ │ ├── settings.html │ │ ├── settings.module.ts │ │ ├── settings.scss │ │ └── settings.ts │ ├── tabs │ │ ├── tabs.html │ │ └── tabs.ts │ ├── user-list │ │ ├── followers.ts │ │ ├── following.ts │ │ ├── stargazers.ts │ │ ├── user-list.html │ │ ├── user-list.module.ts │ │ ├── user-list.scss │ │ ├── user-list.ts │ │ └── watchers.ts │ └── user │ │ ├── me.ts │ │ ├── user.html │ │ ├── user.module.ts │ │ ├── user.scss │ │ └── user.ts ├── pipes │ ├── markdown-to-html │ │ └── markdown-to-html.ts │ ├── pipes.module.ts │ └── sanitize-html │ │ └── sanitize-html.ts ├── service-worker.js ├── services │ ├── account.service.ts │ ├── api.service.ts │ ├── toast.service.ts │ └── update.service.ts └── theme │ └── variables.scss ├── tsconfig.json ├── tslint.json ├── version.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | 10 | # We recommend you to keep these unchanged 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | *~ 5 | *.sw[mnpcod] 6 | *.log 7 | *.tmp 8 | *.tmp.* 9 | log.txt 10 | *.sublime-project 11 | *.sublime-workspace 12 | .vscode/ 13 | npm-debug.log* 14 | /apk/ 15 | .sourcemaps/ 16 | 17 | .idea/ 18 | .sass-cache/ 19 | .tmp/ 20 | .versions/ 21 | coverage/ 22 | dist/ 23 | node_modules/ 24 | tmp/ 25 | temp/ 26 | hooks/ 27 | platforms/ 28 | plugins/ 29 | plugins/android.json 30 | plugins/ios.json 31 | www/ 32 | $RECYCLE.BIN/ 33 | 34 | .DS_Store 35 | Thumbs.db 36 | UserInterfaceState.xcuserstate 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitMug 2 | 3 | The GitHub app for minimalists. [HomePage](https://awmleer.github.io/gitmug/index.html) 4 | 5 | ![homepage screenshot](./screenshots/homepage.png) 6 | 7 | ## Overview 8 | 9 | GitMug is built with [ionic](https://ionicframework.com/) framework (Angular+typescript+cordova), and uses GitHub api v4. Thanks to Angular's aot compilation, the iOS app/Android apk has an incredible small size (about 3~5M), while offering quite a few features. 10 | 11 | Feel free to open issues if you find any bug or have any idea about GitMug. 12 | 13 | ## Develop 14 | 15 | Preparation: 16 | 17 | ```bash 18 | $ sudo npm install -g ionic cordova 19 | $ cd GitMug 20 | $ npm install 21 | ``` 22 | 23 | Start dev: 24 | 25 | ```bash 26 | $ ionic serve 27 | ``` 28 | 29 | Build: 30 | 31 | ```bash 32 | # iOS 33 | $ ionic cordova build ios --prod 34 | # Android 35 | $ ionic cordova build android --release --prod 36 | $ ./signApk.sh 37 | ``` 38 | 39 | IMPORTANT: 40 | 41 | If you change the html template, you may need to stop&restart to see the update. (This may be caused by page class inheritance, eg. UserPage & RepoListPage) 42 | 43 | 44 | ## Commit Notation 45 | 46 | - [+] add 47 | - [-] remove 48 | - [=] update 49 | - [$] init 50 | - [#] document 51 | - [^] improve 52 | - [~] refactor 53 | - [!] fix 54 | - [*] try 55 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | GitMug 4 | The GitHub app for minimalists. 5 | awmleer 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/assets/icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/docs/assets/icon/favicon.ico -------------------------------------------------------------------------------- /docs/assets/image/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/docs/assets/image/screenshot1.png -------------------------------------------------------------------------------- /docs/assets/image/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/docs/assets/image/screenshot2.png -------------------------------------------------------------------------------- /docs/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 875 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GitMug 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 35 | 36 | 37 |
38 |
39 |
40 |

41 | GitMug 42 |

43 |

44 | The GitHub app for minimalists. 45 |

46 |
47 | 48 | GitHub · awmleer/GitMug 49 | 50 |
51 |
52 | 53 | Android 54 | 55 | 56 | iOS 57 | 58 |
59 |
60 | Chat Groups:
61 | QQ: 340847491 JOIN
62 | Telegram: JOIN 63 |
64 |
65 |
66 | 67 | 68 |
69 |
70 | 71 |
72 | 73 |
74 |
75 |
76 |

77 | GitMug 78 |

79 |

80 | The GitHub app for minimalists. 81 |

82 |
83 | 84 | GitHub · awmleer/GitMug 85 | 86 |
87 |
88 | 89 | Android 90 | 91 | 92 | iOS 93 | 94 |
95 |
96 | Chat Groups:
97 | QQ: 340847491 JOIN
98 | Telegram: JOIN 99 |
100 |
101 |
102 |
103 |
104 |
105 | 106 |
107 |
108 |
109 | 110 |
111 | 117 |
118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /docs/oauth/callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GitMug 6 | 7 | 8 | 27 | 28 | 29 |
30 | 31 |
32 |

Loading...

33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/statement.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GitMug 6 | 7 | 8 | 9 | 10 |
11 | 由于app审核,现在只能进行内测版本的分发,麻烦您加入:
12 | QQ群:340847491 点此加入
13 | Telegram Group: 点此加入 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /gitmug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/gitmug.keystore -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GitMug", 3 | "app_id": "", 4 | "type": "ionic-angular", 5 | "proxies": [ 6 | { 7 | "path": "/api", 8 | "proxyUrl": "https://api.github.com" 9 | },{ 10 | "path": "/raw", 11 | "proxyUrl": "https://raw.githubusercontent.com" 12 | },{ 13 | "path": "/github", 14 | "proxyUrl": "https://github.com" 15 | } 16 | ], 17 | "integrations": { 18 | "cordova": {} 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitmug", 3 | "version": "0.3.0", 4 | "author": "awmleer", 5 | "homepage": "https://awmleer.github.io/gitmug", 6 | "private": true, 7 | "scripts": { 8 | "clean": "ionic-app-scripts clean", 9 | "build": "ionic-app-scripts build", 10 | "lint": "ionic-app-scripts lint", 11 | "ionic:build": "ionic-app-scripts build", 12 | "ionic:serve": "ionic-app-scripts serve" 13 | }, 14 | "dependencies": { 15 | "@angular/common": "5.0.3", 16 | "@angular/compiler": "5.0.3", 17 | "@angular/compiler-cli": "5.0.3", 18 | "@angular/core": "5.0.3", 19 | "@angular/forms": "5.0.3", 20 | "@angular/http": "5.0.3", 21 | "@angular/platform-browser": "5.0.3", 22 | "@angular/platform-browser-dynamic": "5.0.3", 23 | "@ionic-native/core": "4.x", 24 | "@ionic-native/in-app-browser": "4.x", 25 | "@ionic-native/splash-screen": "4.x", 26 | "@ionic-native/status-bar": "4.x", 27 | "@ionic/storage": "2.1.3", 28 | "cordova-android": "7.x", 29 | "cordova-ios": "4.x", 30 | "cordova-plugin-device": "1.x", 31 | "cordova-plugin-inappbrowser": "2.x", 32 | "cordova-plugin-splashscreen": "4.x", 33 | "cordova-plugin-statusbar": "2.x", 34 | "cordova-plugin-whitelist": "1.x", 35 | "cordova-sqlite-storage": "2.x", 36 | "graphql-request": "1.6.x", 37 | "highlightjs": "^9.10.0", 38 | "ionic-angular": "3.9.2", 39 | "ionic-plugin-keyboard": "~2.2.1", 40 | "ionicons": "4.x", 41 | "marked": "0.3.x", 42 | "moment": "2.x", 43 | "rxjs": "5.5.2", 44 | "sw-toolbox": "3.6.0", 45 | "zone.js": "0.8.x" 46 | }, 47 | "devDependencies": { 48 | "@ionic/app-scripts": "3.x", 49 | "typescript": "2.4.x" 50 | }, 51 | "description": "The GitHub app for minimalists.", 52 | "cordova": { 53 | "platforms": [ 54 | "android", 55 | "ios" 56 | ], 57 | "plugins": { 58 | "ionic-plugin-keyboard": {}, 59 | "cordova-plugin-whitelist": {}, 60 | "cordova-plugin-console": {}, 61 | "cordova-plugin-statusbar": {}, 62 | "cordova-plugin-device": {}, 63 | "cordova-plugin-splashscreen": {}, 64 | "cordova-plugin-inappbrowser": {}, 65 | "cordova-sqlite-storage": {} 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /resources/_icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/_icon.psd -------------------------------------------------------------------------------- /resources/_splash.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/_splash.psd -------------------------------------------------------------------------------- /resources/android/icon/drawable-hdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/icon/drawable-hdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-ldpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/icon/drawable-ldpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-mdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/icon/drawable-mdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/icon/drawable-xhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/icon/drawable-xxhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xxxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/icon/drawable-xxxhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/splash/drawable-land-hdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/splash/drawable-land-ldpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/splash/drawable-land-mdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/splash/drawable-land-xhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/splash/drawable-land-xxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/splash/drawable-land-xxxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/splash/drawable-port-hdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/splash/drawable-port-ldpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/splash/drawable-port-mdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/splash/drawable-port-xhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/splash/drawable-port-xxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/android/splash/drawable-port-xxxhdpi-screen.png -------------------------------------------------------------------------------- /resources/glass.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/glass.ico -------------------------------------------------------------------------------- /resources/glass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/glass.png -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/icon.png -------------------------------------------------------------------------------- /resources/icon.png.md5: -------------------------------------------------------------------------------- 1 | 629b4dd1f4c8c296e972a29c66747d5b -------------------------------------------------------------------------------- /resources/ios/icon/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-1024.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-40.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-40@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-40@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-50.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-50@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-60.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-60@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-60@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-72.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-72@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-76.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-76@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-83.5@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-small.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-small@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon-small@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon.png -------------------------------------------------------------------------------- /resources/ios/icon/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/icon/icon@2x.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default-568h@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-667h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default-667h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default-Landscape-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default-Landscape@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape@~ipadpro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default-Landscape@~ipadpro.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default-Landscape~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default-Portrait@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait@~ipadpro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default-Portrait@~ipadpro.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default-Portrait~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default@2x~universal~anyany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default@2x~universal~anyany.png -------------------------------------------------------------------------------- /resources/ios/splash/Default~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/ios/splash/Default~iphone.png -------------------------------------------------------------------------------- /resources/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 875 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /resources/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/resources/splash.png -------------------------------------------------------------------------------- /resources/splash.png.md5: -------------------------------------------------------------------------------- 1 | eb50da895b6b47404bd63b9084d89306 -------------------------------------------------------------------------------- /screenshots/homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/homepage.png -------------------------------------------------------------------------------- /screenshots/iPad12.9/Simulator Screen Shot 2017年8月22日 18.55.01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/iPad12.9/Simulator Screen Shot 2017年8月22日 18.55.01.png -------------------------------------------------------------------------------- /screenshots/iPad12.9/Simulator Screen Shot 2017年8月22日 18.55.05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/iPad12.9/Simulator Screen Shot 2017年8月22日 18.55.05.png -------------------------------------------------------------------------------- /screenshots/iPad12.9/Simulator Screen Shot 2017年8月22日 18.55.17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/iPad12.9/Simulator Screen Shot 2017年8月22日 18.55.17.png -------------------------------------------------------------------------------- /screenshots/iPad12.9/Simulator Screen Shot 2017年8月23日 15.43.17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/iPad12.9/Simulator Screen Shot 2017年8月23日 15.43.17.png -------------------------------------------------------------------------------- /screenshots/iPhone4.7/Simulator Screen Shot 2017年8月22日 18.37.32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/iPhone4.7/Simulator Screen Shot 2017年8月22日 18.37.32.png -------------------------------------------------------------------------------- /screenshots/iPhone4.7/Simulator Screen Shot 2017年8月22日 18.37.34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/iPhone4.7/Simulator Screen Shot 2017年8月22日 18.37.34.png -------------------------------------------------------------------------------- /screenshots/iPhone4.7/Simulator Screen Shot 2017年8月22日 18.37.35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/iPhone4.7/Simulator Screen Shot 2017年8月22日 18.37.35.png -------------------------------------------------------------------------------- /screenshots/iPhone4.7/Simulator Screen Shot 2017年8月23日 15.38.29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/iPhone4.7/Simulator Screen Shot 2017年8月23日 15.38.29.png -------------------------------------------------------------------------------- /screenshots/iPhone5.5/Simulator Screen Shot 2017年8月22日 18.51.21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/iPhone5.5/Simulator Screen Shot 2017年8月22日 18.51.21.png -------------------------------------------------------------------------------- /screenshots/iPhone5.5/Simulator Screen Shot 2017年8月22日 18.51.25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/iPhone5.5/Simulator Screen Shot 2017年8月22日 18.51.25.png -------------------------------------------------------------------------------- /screenshots/iPhone5.5/Simulator Screen Shot 2017年8月22日 18.51.34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/iPhone5.5/Simulator Screen Shot 2017年8月22日 18.51.34.png -------------------------------------------------------------------------------- /screenshots/iPhone5.5/Simulator Screen Shot 2017年8月23日 15.40.38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/screenshots/iPhone5.5/Simulator Screen Shot 2017年8月23日 15.40.38.png -------------------------------------------------------------------------------- /signApk.sh: -------------------------------------------------------------------------------- 1 | jarsigner -verbose -keystore gitmug.keystore -signedjar GitMug.apk platforms/android/build/outputs/apk/android-release-unsigned.apk gitmug.keystore 2 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {ModalController, Platform} from 'ionic-angular'; 3 | import { StatusBar } from '@ionic-native/status-bar'; 4 | import { SplashScreen } from '@ionic-native/splash-screen'; 5 | 6 | import { TabsPage } from '../pages/tabs/tabs'; 7 | import {AccountService} from "../services/account.service"; 8 | import {BootstrapPage} from "../pages/bootstrap/bootstrap"; 9 | import {UpdateService} from "../services/update.service"; 10 | 11 | @Component({ 12 | templateUrl: 'app.html' 13 | }) 14 | export class MyApp { 15 | rootPage:any = TabsPage; 16 | 17 | constructor( 18 | platform: Platform, 19 | statusBar: StatusBar, 20 | accountSvc: AccountService, 21 | updateSvc: UpdateService, 22 | modalCtrl: ModalController, 23 | splashScreen: SplashScreen, 24 | ) { 25 | platform.ready().then(() => { 26 | // Okay, so the platform is ready and our plugins are available. 27 | // Here you can do any higher level native things you might need. 28 | statusBar.styleDefault(); 29 | 30 | accountSvc.fetchUserDataFromStorage().then(()=>{ 31 | if (accountSvc.user.accessToken) { 32 | splashScreen.hide(); 33 | accountSvc.freshUser(); 34 | }else { 35 | modalCtrl.create(BootstrapPage,{},{ 36 | enableBackdropDismiss:false, 37 | showBackdrop:false 38 | }).present().then(()=>{ 39 | splashScreen.hide(); 40 | }); 41 | } 42 | }); 43 | 44 | setTimeout(()=>{ 45 | updateSvc.checkUpdate(); 46 | },3000); 47 | 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/app.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ErrorHandler } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular'; 4 | import { MyApp } from './app.component'; 5 | 6 | import { TabsPage } from '../pages/tabs/tabs'; 7 | 8 | import { StatusBar } from '@ionic-native/status-bar'; 9 | import { SplashScreen } from '@ionic-native/splash-screen'; 10 | import {InAppBrowser} from "@ionic-native/in-app-browser"; 11 | import {AccountService} from "../services/account.service"; 12 | import {HttpModule} from "@angular/http"; 13 | import {IonicStorageModule} from "@ionic/storage"; 14 | import {ApiService} from "../services/api.service"; 15 | import {UserPageModule} from "../pages/user/user.module"; 16 | import {UserListPageModule} from "../pages/user-list/user-list.module"; 17 | import {ToastService} from "../services/toast.service"; 18 | import {RepoListPageModule} from "../pages/repo-list/repo-list.module"; 19 | import {RepoPageModule} from "../pages/repo/repo.module"; 20 | import {DashboardPageModule} from "../pages/dashboard/dashboard.module"; 21 | import {BootstrapPageModule} from "../pages/bootstrap/bootstrap.module"; 22 | import {SettingsPageModule} from "../pages/settings/settings.module"; 23 | import {UpdateService} from "../services/update.service"; 24 | import {ContentListPageModule} from "../pages/content-list/content-list.module"; 25 | import {CodeListPageModule} from "../pages/code-list/code-list.module"; 26 | import {CodeDetailPageModule} from "../pages/code-detail/code-detail.module"; 27 | 28 | 29 | @NgModule({ 30 | declarations: [ 31 | MyApp, 32 | TabsPage, 33 | ], 34 | imports: [ 35 | BrowserModule, 36 | HttpModule, 37 | IonicModule.forRoot(MyApp), 38 | IonicStorageModule.forRoot(), 39 | BootstrapPageModule, 40 | DashboardPageModule, 41 | UserPageModule, 42 | UserListPageModule, 43 | RepoListPageModule, 44 | ContentListPageModule, 45 | RepoPageModule, 46 | SettingsPageModule, 47 | CodeListPageModule, 48 | CodeDetailPageModule, 49 | ], 50 | bootstrap: [IonicApp], 51 | entryComponents: [ 52 | MyApp, 53 | TabsPage, 54 | ], 55 | providers: [ 56 | StatusBar, 57 | SplashScreen, 58 | InAppBrowser, 59 | ToastService, 60 | ApiService, 61 | AccountService, 62 | UpdateService, 63 | {provide: ErrorHandler, useClass: IonicErrorHandler} 64 | ] 65 | }) 66 | export class AppModule {} 67 | -------------------------------------------------------------------------------- /src/app/app.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/theming/ 2 | 3 | 4 | // App Global Sass 5 | // -------------------------------------------------- 6 | // Put style rules here that you want to apply globally. These 7 | // styles are for the entire app and not just one component. 8 | // Additionally, this file can be also used as an entry point 9 | // to import other Sass files to be included in the output CSS. 10 | // 11 | // Shared Sass variables, which can be used to adjust Ionic's 12 | // default Sass variables, belong in "theme/variables.scss". 13 | // 14 | // To declare rules for a specific mode, create a child rule 15 | // for the .md, .ios, or .wp mode classes. The mode class is 16 | // automatically applied to the element in the app. 17 | 18 | 19 | .header.header-md::after{ 20 | display: none; 21 | } 22 | 23 | a:active{ 24 | opacity: 0.5; 25 | } 26 | 27 | div.list-end-footer{ 28 | text-align: center; 29 | padding-bottom: 4rem; 30 | color: #6b6b6b; 31 | } 32 | 33 | div.empty-list-placeholder{ 34 | width:100%; 35 | text-align: center; 36 | margin-top: 8em; 37 | ion-icon{ 38 | display: inline-block; 39 | font-size: 6em; 40 | color: #bfbfbf; 41 | } 42 | } 43 | 44 | .label-ios{ 45 | margin: 11px 8px 11px 0; 46 | display: block; 47 | overflow: hidden; 48 | -webkit-box-flex: 1; 49 | -webkit-flex: 1; 50 | -ms-flex: 1; 51 | flex: 1; 52 | font-size: inherit; 53 | text-overflow: ellipsis; 54 | white-space: nowrap; 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/app/const.ts: -------------------------------------------------------------------------------- 1 | export const CONST={ 2 | graphqlUrl: window['cordova']?'https://api.github.com/graphql':'/api/graphql', 3 | rawUrl: window['cordova']?'https://raw.githubusercontent.com':'/raw', 4 | apiUrl: window['cordova']?'https://api.github.com':'/api', 5 | githubUrl: window['cordova']?'https://github.com':'/github', 6 | version:{ 7 | major:0, 8 | minor:3, 9 | patch:0 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/app/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app.module'; 4 | 5 | platformBrowserDynamic().bootstrapModule(AppModule); 6 | -------------------------------------------------------------------------------- /src/assets/icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmleer/gitmug/c53144366db7f616f0d0d763bb213d060d288857/src/assets/icon/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 875 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/classes/content.ts: -------------------------------------------------------------------------------- 1 | export abstract class Content { 2 | type:'file'|'dir'|'symlink'|'submodule'; 3 | _links:{ 4 | git:string; 5 | self:string; 6 | html:string; 7 | }; 8 | size:number; 9 | name:string; 10 | path:string; 11 | sha:string; 12 | url:string; 13 | git_url:string; 14 | html_url:string; 15 | download_url:string; 16 | } 17 | 18 | export class File extends Content { 19 | encoding:string; //e.g. "base64" 20 | content:string; // "encoded content ..." 21 | } 22 | 23 | export class Directory extends Content { 24 | size=0; 25 | download_url=null; 26 | } 27 | 28 | export class Symlink extends Content { 29 | target: string; //"/path/to/symlink/target" 30 | } 31 | 32 | 33 | export class Submodule extends Content { 34 | submodule_git_url:string; 35 | size=0; 36 | download_url=null; 37 | } 38 | -------------------------------------------------------------------------------- /src/classes/language-color.ts: -------------------------------------------------------------------------------- 1 | export const colors={ 2 | "Mercury": "#ff2b2b", 3 | "TypeScript": "#2b7489", 4 | "PureBasic": "#5a6986", 5 | "Objective-C++": "#6866fb", 6 | "Self": "#0579aa", 7 | "edn": "#db5855", 8 | "NewLisp": "#87AED7", 9 | "Jupyter Notebook": "#DA5B0B", 10 | "Rebol": "#358a5b", 11 | "Frege": "#00cafe", 12 | "Dart": "#00B4AB", 13 | "AspectJ": "#a957b0", 14 | "Shell": "#89e051", 15 | "Web Ontology Language": "#9cc9dd", 16 | "xBase": "#403a40", 17 | "Eiffel": "#946d57", 18 | "Nix": "#7e7eff", 19 | "RAML": "#77d9fb", 20 | "MTML": "#b7e1f4", 21 | "Racket": "#22228f", 22 | "Elixir": "#6e4a7e", 23 | "SAS": "#B34936", 24 | "Agda": "#315665", 25 | "wisp": "#7582D1", 26 | "D": "#ba595e", 27 | "Kotlin": "#F18E33", 28 | "Opal": "#f7ede0", 29 | "Crystal": "#776791", 30 | "Objective-C": "#438eff", 31 | "ColdFusion CFC": "#ed2cd6", 32 | "Oz": "#fab738", 33 | "Mirah": "#c7a938", 34 | "Objective-J": "#ff0c5a", 35 | "Gosu": "#82937f", 36 | "FreeMarker": "#0050b2", 37 | "Ruby": "#701516", 38 | "Component Pascal": "#b0ce4e", 39 | "Arc": "#aa2afe", 40 | "Brainfuck": "#2F2530", 41 | "Nit": "#009917", 42 | "APL": "#5A8164", 43 | "Go": "#375eab", 44 | "Visual Basic": "#945db7", 45 | "PHP": "#4F5D95", 46 | "Cirru": "#ccccff", 47 | "SQF": "#3F3F3F", 48 | "Glyph": "#e4cc98", 49 | "Java": "#b07219", 50 | "MAXScript": "#00a6a6", 51 | "Scala": "#DC322F", 52 | "Makefile": "#427819", 53 | "ColdFusion": "#ed2cd6", 54 | "Perl": "#0298c3", 55 | "Lua": "#000080", 56 | "Vue": "#2c3e50", 57 | "Verilog": "#b2b7f8", 58 | "Factor": "#636746", 59 | "Haxe": "#df7900", 60 | "Pure Data": "#91de79", 61 | "Forth": "#341708", 62 | "Red": "#ee0000", 63 | "Hy": "#7790B2", 64 | "Volt": "#1F1F1F", 65 | "LSL": "#3d9970", 66 | "eC": "#913960", 67 | "CoffeeScript": "#244776", 68 | "HTML": "#e44b23", 69 | "Lex": "#DBCA00", 70 | "API Blueprint": "#2ACCA8", 71 | "Swift": "#ffac45", 72 | "C": "#555555", 73 | "AutoHotkey": "#6594b9", 74 | "Isabelle": "#FEFE00", 75 | "Metal": "#8f14e9", 76 | "Clarion": "#db901e", 77 | "JSONiq": "#40d47e", 78 | "Boo": "#d4bec1", 79 | "AutoIt": "#1C3552", 80 | "Clojure": "#db5855", 81 | "Rust": "#dea584", 82 | "Prolog": "#74283c", 83 | "SourcePawn": "#5c7611", 84 | "AMPL": "#E6EFBB", 85 | "FORTRAN": "#4d41b1", 86 | "ANTLR": "#9DC3FF", 87 | "Harbour": "#0e60e3", 88 | "Tcl": "#e4cc98", 89 | "BlitzMax": "#cd6400", 90 | "PigLatin": "#fcd7de", 91 | "Lasso": "#999999", 92 | "ECL": "#8a1267", 93 | "VHDL": "#adb2cb", 94 | "Elm": "#60B5CC", 95 | "Propeller Spin": "#7fa2a7", 96 | "X10": "#4B6BEF", 97 | "IDL": "#a3522f", 98 | "ATS": "#1ac620", 99 | "Ada": "#02f88c", 100 | "Unity3D Asset": "#ab69a1", 101 | "Nu": "#c9df40", 102 | "LFE": "#004200", 103 | "SuperCollider": "#46390b", 104 | "Oxygene": "#cdd0e3", 105 | "ASP": "#6a40fd", 106 | "Assembly": "#6E4C13", 107 | "Gnuplot": "#f0a9f0", 108 | "JFlex": "#DBCA00", 109 | "NetLinx": "#0aa0ff", 110 | "Turing": "#45f715", 111 | "Vala": "#fbe5cd", 112 | "Processing": "#0096D8", 113 | "Arduino": "#bd79d1", 114 | "FLUX": "#88ccff", 115 | "NetLogo": "#ff6375", 116 | "C Sharp": "#178600", 117 | "CSS": "#563d7c", 118 | "Emacs Lisp": "#c065db", 119 | "Stan": "#b2011d", 120 | "SaltStack": "#646464", 121 | "QML": "#44a51c", 122 | "Pike": "#005390", 123 | "LOLCODE": "#cc9900", 124 | "ooc": "#b0b77e", 125 | "Handlebars": "#01a9d6", 126 | "J": "#9EEDFF", 127 | "Mask": "#f97732", 128 | "EmberScript": "#FFF4F3", 129 | "TeX": "#3D6117", 130 | "Nemerle": "#3d3c6e", 131 | "KRL": "#28431f", 132 | "Ren'Py": "#ff7f7f", 133 | "Unified Parallel C": "#4e3617", 134 | "Golo": "#88562A", 135 | "Fancy": "#7b9db4", 136 | "OCaml": "#3be133", 137 | "Shen": "#120F14", 138 | "Pascal": "#b0ce4e", 139 | "F#": "#b845fc", 140 | "Puppet": "#302B6D", 141 | "ActionScript": "#882B0F", 142 | "Diff": "#88dddd", 143 | "Ragel in Ruby Host": "#9d5200", 144 | "Fantom": "#dbded5", 145 | "Zephir": "#118f9e", 146 | "Click": "#E4E6F3", 147 | "Smalltalk": "#596706", 148 | "DM": "#447265", 149 | "Ioke": "#078193", 150 | "PogoScript": "#d80074", 151 | "LiveScript": "#499886", 152 | "JavaScript": "#f1e05a", 153 | "VimL": "#199f4b", 154 | "PureScript": "#1D222D", 155 | "ABAP": "#E8274B", 156 | "Matlab": "#bb92ac", 157 | "Slash": "#007eff", 158 | "R": "#198ce7", 159 | "Erlang": "#B83998", 160 | "Pan": "#cc0000", 161 | "LookML": "#652B81", 162 | "Eagle": "#814C05", 163 | "Scheme": "#1e4aec", 164 | "PLSQL": "#dad8d8", 165 | "Python": "#3572A5", 166 | "Max": "#c4a79c", 167 | "Common Lisp": "#3fb68b", 168 | "Latte": "#A8FF97", 169 | "XQuery": "#5232e7", 170 | "Omgrofl": "#cabbff", 171 | "XC": "#99DA07", 172 | "Nimrod": "#37775b", 173 | "SystemVerilog": "#DAE1C2", 174 | "Chapel": "#8dc63f", 175 | "Groovy": "#e69f56", 176 | "Dylan": "#6c616e", 177 | "E": "#ccce35", 178 | "Parrot": "#f3ca0a", 179 | "Grammatical Framework": "#79aa7a", 180 | "Game Maker Language": "#8fb200", 181 | "Papyrus": "#6600cc", 182 | "NetLinx+ERB": "#747faa", 183 | "Clean": "#3F85AF", 184 | "Alloy": "#64C800", 185 | "Squirrel": "#800000", 186 | "PAWN": "#dbb284", 187 | "UnrealScript": "#a54c4d", 188 | "Standard ML": "#dc566d", 189 | "Slim": "#ff8f77", 190 | "Perl6": "#0000fb", 191 | "Julia": "#a270ba", 192 | "Haskell": "#29b544", 193 | "NCL": "#28431f", 194 | "Io": "#a9188d", 195 | "Rouge": "#cc0088", 196 | "cpp": "#f34b7d", 197 | "AGS Script": "#B9D9FF", 198 | "Dogescript": "#cca760", 199 | "nesC": "#94B0C7" 200 | } 201 | -------------------------------------------------------------------------------- /src/classes/nodes-page.ts: -------------------------------------------------------------------------------- 1 | export interface PageInfo { 2 | hasNextPage: boolean; 3 | endCursor: string; 4 | } 5 | 6 | 7 | export interface NodesPage { 8 | totalCount:number; 9 | pageInfo:PageInfo; 10 | nodes:T[]; 11 | } 12 | 13 | export function nodesPageSchema(nodeSchema:string){ 14 | return `{ 15 | totalCount 16 | pageInfo { 17 | hasNextPage 18 | endCursor 19 | } 20 | nodes ${nodeSchema} 21 | }` 22 | } 23 | -------------------------------------------------------------------------------- /src/classes/repo.ts: -------------------------------------------------------------------------------- 1 | export class RepoParam { 2 | owner:string; 3 | name:string; 4 | } 5 | 6 | export class RepoItem { 7 | owner:{ 8 | login:string; 9 | }; 10 | name:string; 11 | description:string; 12 | stargazers:{ 13 | totalCount:number; 14 | }; 15 | forks:{ 16 | totalCount:number; 17 | }; 18 | primaryLanguage:{ 19 | name:string; 20 | }; 21 | isPrivate:boolean; 22 | } 23 | 24 | export class RepoDetail extends RepoItem { 25 | id:string; 26 | watchers:{ 27 | totalCount:number; 28 | }; 29 | issues:{ 30 | totalCount:number; 31 | }; 32 | pullRequests:{ 33 | totalCount:number; 34 | }; 35 | releases:{ 36 | totalCount:number; 37 | }; 38 | updatedAt:string; 39 | isFork:boolean; 40 | isMirror:boolean; 41 | diskUsage:number; 42 | license:string; 43 | repositoryTopics:{ 44 | nodes:{ 45 | topic:{ 46 | name:string; 47 | } 48 | }[]; 49 | }; 50 | viewerHasStarred:boolean; 51 | } 52 | 53 | 54 | export const repoItemSchema=`{ 55 | owner{ 56 | login 57 | } 58 | name 59 | description 60 | stargazers{ 61 | totalCount 62 | } 63 | forks{ 64 | totalCount 65 | } 66 | primaryLanguage{ 67 | name 68 | } 69 | isPrivate 70 | }`; 71 | 72 | export const repoDetailSchema=`{ 73 | name 74 | owner { 75 | login 76 | } 77 | description 78 | stargazers { 79 | totalCount 80 | } 81 | forks { 82 | totalCount 83 | } 84 | primaryLanguage { 85 | name 86 | } 87 | 88 | id 89 | watchers{ 90 | totalCount 91 | } 92 | issues { 93 | totalCount 94 | } 95 | pullRequests { 96 | totalCount 97 | } 98 | releases { 99 | totalCount 100 | } 101 | updatedAt 102 | isFork 103 | isPrivate 104 | isMirror 105 | diskUsage 106 | license 107 | repositoryTopics(first:100){ 108 | nodes{ 109 | topic{ 110 | name 111 | } 112 | } 113 | } 114 | viewerHasStarred 115 | }`; 116 | -------------------------------------------------------------------------------- /src/classes/user.ts: -------------------------------------------------------------------------------- 1 | import {RepoItem, repoItemSchema} from "./repo"; 2 | 3 | 4 | export class User { 5 | name:string=''; 6 | login:string=''; 7 | } 8 | 9 | export class UserStorage extends User { 10 | accessToken:string; 11 | } 12 | 13 | export class UserItem extends User { 14 | bio:string; 15 | avatarUrl:string; 16 | } 17 | 18 | export class UserProfile extends UserItem { 19 | followers:{ 20 | totalCount:number; 21 | }; 22 | following:{ 23 | totalCount:number; 24 | }; 25 | starredRepositories:{ 26 | totalCount:number; 27 | }; 28 | repositories:{ 29 | totalCount:number; 30 | }; 31 | pinnedRepositories:{ 32 | nodes:RepoItem[]; 33 | }; 34 | company:string; 35 | location:string; 36 | email:string; 37 | viewerCanFollow:boolean; 38 | viewerIsFollowing:boolean; 39 | } 40 | 41 | export const userSchema=`{ 42 | name 43 | login 44 | }`; 45 | 46 | export const userItemSchema=`{ 47 | name 48 | login 49 | bio 50 | avatarUrl 51 | }`; 52 | 53 | export const userProfileSchema=`{ 54 | name 55 | login 56 | bio 57 | avatarUrl 58 | followers{ 59 | totalCount 60 | } 61 | following{ 62 | totalCount 63 | } 64 | starredRepositories{ 65 | totalCount 66 | } 67 | repositories{ 68 | totalCount 69 | } 70 | pinnedRepositories(first: 10){ 71 | nodes ${repoItemSchema} 72 | } 73 | company 74 | location 75 | email 76 | viewerCanFollow 77 | viewerIsFollowing 78 | }`; 79 | -------------------------------------------------------------------------------- /src/classes/version.ts: -------------------------------------------------------------------------------- 1 | export class Version { 2 | major:number; 3 | minor:number; 4 | patch:number; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/components.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RepoItemComponent } from './repo-item/repo-item'; 3 | import {CommonModule} from "@angular/common"; 4 | import {SearchBoxComponent} from "./search-box/search-box"; 5 | import {IonicModule} from "ionic-angular"; 6 | import { MarkdownComponent } from './markdown/markdown'; 7 | import {PipesModule} from "../pipes/pipes.module"; 8 | 9 | 10 | @NgModule({ 11 | declarations: [ 12 | RepoItemComponent, 13 | SearchBoxComponent, 14 | MarkdownComponent, 15 | ], 16 | imports: [ 17 | CommonModule, 18 | IonicModule, 19 | PipesModule, 20 | ], 21 | exports: [ 22 | RepoItemComponent, 23 | SearchBoxComponent, 24 | MarkdownComponent, 25 | ] 26 | }) 27 | export class ComponentsModule {} 28 | -------------------------------------------------------------------------------- /src/components/markdown/markdown.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /src/components/markdown/markdown.scss: -------------------------------------------------------------------------------- 1 | markdown { 2 | @font-face { 3 | font-family: octicons-link; 4 | src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff'); 5 | } 6 | 7 | .markdown { 8 | -ms-text-size-adjust: 100%; 9 | -webkit-text-size-adjust: 100%; 10 | color: #24292e; 11 | font-family: octicons-link, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 12 | line-height: 1.5em; 13 | word-wrap: break-word; 14 | } 15 | 16 | .markdown /deep/ .pl-c { 17 | color: #6a737d; 18 | } 19 | 20 | .markdown /deep/ .pl-c1, 21 | .markdown /deep/ .pl-s .pl-v { 22 | color: #005cc5; 23 | } 24 | 25 | .markdown /deep/ .pl-e, 26 | .markdown /deep/ .pl-en { 27 | color: #6f42c1; 28 | } 29 | 30 | .markdown /deep/ .pl-smi, 31 | .markdown /deep/ .pl-s .pl-s1 { 32 | color: #24292e; 33 | } 34 | 35 | .markdown /deep/ .pl-ent { 36 | color: #22863a; 37 | } 38 | 39 | .markdown /deep/ .pl-k { 40 | color: #d73a49; 41 | } 42 | 43 | .markdown /deep/ .pl-s, 44 | .markdown /deep/ .pl-pds, 45 | .markdown /deep/ .pl-s .pl-pse .pl-s1, 46 | .markdown /deep/ .pl-sr, 47 | .markdown /deep/ .pl-sr .pl-cce, 48 | .markdown /deep/ .pl-sr .pl-sre, 49 | .markdown /deep/ .pl-sr .pl-sra { 50 | color: #032f62; 51 | } 52 | 53 | .markdown /deep/ .pl-v, 54 | .markdown /deep/ .pl-smw { 55 | color: #e36209; 56 | } 57 | 58 | .markdown /deep/ .pl-bu { 59 | color: #b31d28; 60 | } 61 | 62 | .markdown /deep/ .pl-ii { 63 | color: #fafbfc; 64 | background-color: #b31d28; 65 | } 66 | 67 | .markdown /deep/ .pl-c2 { 68 | color: #fafbfc; 69 | background-color: #d73a49; 70 | } 71 | 72 | .markdown /deep/ .pl-c2::before { 73 | content: "^M"; 74 | } 75 | 76 | .markdown /deep/ .pl-sr .pl-cce { 77 | font-weight: bold; 78 | color: #22863a; 79 | } 80 | 81 | .markdown /deep/ .pl-ml { 82 | color: #735c0f; 83 | } 84 | 85 | .markdown /deep/ .pl-mh, 86 | .markdown /deep/ .pl-mh .pl-en, 87 | .markdown /deep/ .pl-ms { 88 | font-weight: bold; 89 | color: #005cc5; 90 | } 91 | 92 | .markdown /deep/ .pl-mi { 93 | font-style: italic; 94 | color: #24292e; 95 | } 96 | 97 | .markdown /deep/ .pl-mb { 98 | font-weight: bold; 99 | color: #24292e; 100 | } 101 | 102 | .markdown /deep/ .pl-md { 103 | color: #b31d28; 104 | background-color: #ffeef0; 105 | } 106 | 107 | .markdown /deep/ .pl-mi1 { 108 | color: #22863a; 109 | background-color: #f0fff4; 110 | } 111 | 112 | .markdown /deep/ .pl-mc { 113 | color: #e36209; 114 | background-color: #ffebda; 115 | } 116 | 117 | .markdown /deep/ .pl-mi2 { 118 | color: #f6f8fa; 119 | background-color: #005cc5; 120 | } 121 | 122 | .markdown /deep/ .pl-mdr { 123 | font-weight: bold; 124 | color: #6f42c1; 125 | } 126 | 127 | .markdown /deep/ .pl-ba { 128 | color: #586069; 129 | } 130 | 131 | .markdown /deep/ .pl-sg { 132 | color: #959da5; 133 | } 134 | 135 | .markdown /deep/ .pl-corl { 136 | text-decoration: underline; 137 | color: #032f62; 138 | } 139 | 140 | .markdown /deep/ .octicon { 141 | display: inline-block; 142 | vertical-align: text-top; 143 | fill: currentColor; 144 | } 145 | 146 | .markdown /deep/ a { 147 | background-color: transparent; 148 | -webkit-text-decoration-skip: objects; 149 | } 150 | 151 | .markdown /deep/ a:active, 152 | .markdown /deep/ a:hover { 153 | outline-width: 0; 154 | } 155 | 156 | .markdown /deep/ strong { 157 | font-weight: inherit; 158 | } 159 | 160 | .markdown /deep/ strong { 161 | font-weight: bolder; 162 | } 163 | 164 | .markdown /deep/ h1 { 165 | text-align: left; 166 | padding-bottom: 0.12em; 167 | line-height: 1em; 168 | } 169 | 170 | .markdown /deep/ h1 + p { 171 | text-align: left; 172 | color: #5b5b5b; 173 | } 174 | 175 | .markdown /deep/ img { 176 | border-style: none; 177 | } 178 | 179 | .markdown /deep/ svg:not(:root) { 180 | overflow: hidden; 181 | } 182 | 183 | .markdown /deep/ code, 184 | .markdown /deep/ kbd, 185 | .markdown /deep/ pre { 186 | font-family: monospace, monospace; 187 | font-size: 1em; 188 | } 189 | 190 | .markdown /deep/ hr { 191 | box-sizing: content-box; 192 | height: 0; 193 | overflow: visible; 194 | } 195 | 196 | .markdown /deep/ input { 197 | font: inherit; 198 | margin: 0; 199 | } 200 | 201 | .markdown /deep/ input { 202 | overflow: visible; 203 | } 204 | 205 | .markdown /deep/ [type="checkbox"] { 206 | box-sizing: border-box; 207 | padding: 0; 208 | } 209 | 210 | .markdown /deep/ * { 211 | box-sizing: border-box; 212 | } 213 | 214 | .markdown /deep/ input { 215 | font-family: inherit; 216 | font-size: inherit; 217 | line-height: inherit; 218 | } 219 | 220 | .markdown /deep/ a { 221 | color: #0366d6; 222 | text-decoration: none; 223 | } 224 | 225 | .markdown /deep/ a:hover { 226 | text-decoration: underline; 227 | } 228 | 229 | .markdown /deep/ strong { 230 | font-weight: 600; 231 | } 232 | 233 | .markdown /deep/ hr { 234 | height: 0; 235 | margin: 15px 0; 236 | overflow: hidden; 237 | background: transparent; 238 | border: 0; 239 | border-bottom: 1px solid #dfe2e5; 240 | } 241 | 242 | .markdown /deep/ hr::before { 243 | display: table; 244 | content: ""; 245 | } 246 | 247 | .markdown /deep/ hr::after { 248 | display: table; 249 | clear: both; 250 | content: ""; 251 | } 252 | 253 | .markdown /deep/ table { 254 | border-spacing: 0; 255 | border-collapse: collapse; 256 | } 257 | 258 | .markdown /deep/ td, 259 | .markdown /deep/ th { 260 | padding: 0; 261 | } 262 | 263 | .markdown /deep/ h1, 264 | .markdown /deep/ h2, 265 | .markdown /deep/ h3, 266 | .markdown /deep/ h4, 267 | .markdown /deep/ h5, 268 | .markdown /deep/ h6 { 269 | margin-top: 0; 270 | margin-bottom: 5px; 271 | 272 | line-height: 1.1em; 273 | } 274 | 275 | 276 | .markdown /deep/ h1 { 277 | font-size: 2em; 278 | font-weight: 600; 279 | } 280 | 281 | .markdown /deep/ h2 { 282 | font-size: 1.8em; 283 | font-weight: 600; 284 | } 285 | 286 | .markdown /deep/ h3 { 287 | font-size: 1.6em; 288 | font-weight: 600; 289 | } 290 | 291 | .markdown /deep/ h4 { 292 | font-size: 1.4em; 293 | font-weight: 600; 294 | } 295 | 296 | .markdown /deep/ h5 { 297 | font-size: 1.2em; 298 | font-weight: 600; 299 | } 300 | 301 | .markdown /deep/ h6 { 302 | font-size: 1em; 303 | font-weight: 600; 304 | } 305 | 306 | .markdown /deep/ p { 307 | margin-top: 0; 308 | margin-bottom: 10px; 309 | } 310 | 311 | .markdown /deep/ blockquote { 312 | margin: 0; 313 | } 314 | 315 | .markdown /deep/ ul, 316 | .markdown /deep/ ol { 317 | padding-left: 0; 318 | margin-top: 0; 319 | margin-bottom: 0; 320 | } 321 | 322 | .markdown /deep/ ol ol, 323 | .markdown /deep/ ul ol { 324 | list-style-type: lower-roman; 325 | } 326 | 327 | .markdown /deep/ ul ul ol, 328 | .markdown /deep/ ul ol ol, 329 | .markdown /deep/ ol ul ol, 330 | .markdown /deep/ ol ol ol { 331 | list-style-type: lower-alpha; 332 | } 333 | 334 | .markdown /deep/ dd { 335 | margin-left: 0; 336 | } 337 | 338 | .markdown /deep/ code { 339 | font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 340 | font-size: 12px; 341 | } 342 | 343 | .markdown /deep/ pre { 344 | margin-top: 0; 345 | margin-bottom: 0; 346 | font: 12px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 347 | } 348 | 349 | .markdown /deep/ .octicon { 350 | vertical-align: text-bottom; 351 | } 352 | 353 | .markdown /deep/ .pl-0 { 354 | padding-left: 0 !important; 355 | } 356 | 357 | .markdown /deep/ .pl-1 { 358 | padding-left: 4px !important; 359 | } 360 | 361 | .markdown /deep/ .pl-2 { 362 | padding-left: 8px !important; 363 | } 364 | 365 | .markdown /deep/ .pl-3 { 366 | padding-left: 16px !important; 367 | } 368 | 369 | .markdown /deep/ .pl-4 { 370 | padding-left: 24px !important; 371 | } 372 | 373 | .markdown /deep/ .pl-5 { 374 | padding-left: 32px !important; 375 | } 376 | 377 | .markdown /deep/ .pl-6 { 378 | padding-left: 40px !important; 379 | } 380 | 381 | .markdown::before { 382 | display: table; 383 | content: ""; 384 | } 385 | 386 | .markdown::after { 387 | display: table; 388 | clear: both; 389 | content: ""; 390 | } 391 | 392 | .markdown>*:first-child { 393 | margin-top: 0 !important; 394 | } 395 | 396 | .markdown>*:last-child { 397 | margin-bottom: 0 !important; 398 | } 399 | 400 | .markdown /deep/ a:not([href]) { 401 | color: inherit; 402 | text-decoration: none; 403 | } 404 | 405 | .markdown /deep/ .anchor { 406 | float: left; 407 | padding-right: 4px; 408 | margin-left: -20px; 409 | line-height: 1; 410 | } 411 | 412 | .markdown /deep/ .anchor:focus { 413 | outline: none; 414 | } 415 | 416 | .markdown /deep/ p, 417 | .markdown /deep/ blockquote, 418 | .markdown /deep/ ul, 419 | .markdown /deep/ ol, 420 | .markdown /deep/ dl, 421 | .markdown /deep/ table, 422 | .markdown /deep/ pre { 423 | margin-top: 0; 424 | margin-bottom: 16px; 425 | } 426 | 427 | .markdown /deep/ hr { 428 | height: 1px; 429 | padding: 0; 430 | margin: 24px 0; 431 | background-color: #e1e4e8; 432 | border: 0; 433 | } 434 | 435 | .markdown /deep/ blockquote { 436 | padding: 0 1em; 437 | color: #6a737d; 438 | border-left: 0.25em solid #dfe2e5; 439 | } 440 | 441 | .markdown /deep/ blockquote>:first-child { 442 | margin-top: 0; 443 | } 444 | 445 | .markdown /deep/ blockquote>:last-child { 446 | margin-bottom: 0; 447 | } 448 | 449 | .markdown /deep/ kbd { 450 | display: inline-block; 451 | padding: 3px 5px; 452 | font-size: 11px; 453 | line-height: 10px; 454 | color: #444d56; 455 | vertical-align: middle; 456 | background-color: #fafbfc; 457 | border: solid 2px #c6cbd1; 458 | border-bottom-color: #959da5; 459 | border-radius: 6px; 460 | box-shadow: inset 0 -1px 0 #959da5; 461 | } 462 | 463 | .markdown /deep/ h1 .octicon-link, 464 | .markdown /deep/ h2 .octicon-link, 465 | .markdown /deep/ h3 .octicon-link, 466 | .markdown /deep/ h4 .octicon-link, 467 | .markdown /deep/ h5 .octicon-link, 468 | .markdown /deep/ h6 .octicon-link { 469 | color: #1b1f23; 470 | vertical-align: middle; 471 | visibility: hidden; 472 | } 473 | 474 | .markdown /deep/ h1:hover .anchor, 475 | .markdown /deep/ h2:hover .anchor, 476 | .markdown /deep/ h3:hover .anchor, 477 | .markdown /deep/ h4:hover .anchor, 478 | .markdown /deep/ h5:hover .anchor, 479 | .markdown /deep/ h6:hover .anchor { 480 | text-decoration: none; 481 | } 482 | 483 | .markdown /deep/ h1:hover .anchor .octicon-link, 484 | .markdown /deep/ h2:hover .anchor .octicon-link, 485 | .markdown /deep/ h3:hover .anchor .octicon-link, 486 | .markdown /deep/ h4:hover .anchor .octicon-link, 487 | .markdown /deep/ h5:hover .anchor .octicon-link, 488 | .markdown /deep/ h6:hover .anchor .octicon-link { 489 | visibility: visible; 490 | } 491 | 492 | .markdown /deep/ h1 { 493 | border-bottom: 1px solid #bfbfbf; 494 | } 495 | 496 | .markdown /deep/ h2 { 497 | padding-bottom: 0.2em; 498 | margin-top: 0.5em; 499 | border-bottom: 1px solid #bfbfbf; 500 | } 501 | 502 | .markdown /deep/ h3 { 503 | padding-bottom: 0.1em; 504 | margin-bottom: 0.3em; 505 | border-bottom: 1px solid #bfbfbf; 506 | } 507 | 508 | .markdown /deep/ ul, 509 | .markdown /deep/ ol { 510 | padding-left: 2em; 511 | } 512 | 513 | .markdown /deep/ ul ul, 514 | .markdown /deep/ ul ol, 515 | .markdown /deep/ ol ol, 516 | .markdown /deep/ ol ul { 517 | margin-top: 0; 518 | margin-bottom: 0; 519 | } 520 | 521 | .markdown /deep/ li>p { 522 | margin-top: 16px; 523 | } 524 | 525 | .markdown /deep/ li+li { 526 | margin-top: 0.25em; 527 | } 528 | 529 | .markdown /deep/ dl { 530 | padding: 0; 531 | } 532 | 533 | .markdown /deep/ dl dt { 534 | padding: 0; 535 | margin-top: 16px; 536 | font-size: 1em; 537 | font-style: italic; 538 | font-weight: 600; 539 | } 540 | 541 | .markdown /deep/ dl dd { 542 | padding: 0 16px; 543 | margin-bottom: 16px; 544 | } 545 | 546 | .markdown /deep/ table th { 547 | font-weight: 600; 548 | } 549 | 550 | .markdown /deep/ table th, 551 | .markdown /deep/ table td { 552 | padding: 6px 13px; 553 | border: 2px solid #dfe2e5; 554 | } 555 | 556 | .markdown /deep/ table tr { 557 | background-color: #fff; 558 | border-top: 2px solid #c6cbd1; 559 | } 560 | 561 | .markdown /deep/ table tr:nth-child(2n) { 562 | background-color: #f6f8fa; 563 | } 564 | 565 | .markdown /deep/ img { 566 | max-width: 100%; 567 | box-sizing: content-box; 568 | background-color: #fff; 569 | } 570 | 571 | .markdown /deep/ code { 572 | padding: 0; 573 | padding-top: 0.2em; 574 | padding-bottom: 0.2em; 575 | margin: 0; 576 | font-size: 85%; 577 | background-color: rgba(27,31,35,0.05); 578 | border-radius: 3px; 579 | } 580 | 581 | .markdown /deep/ code::before, 582 | .markdown /deep/ code::after { 583 | letter-spacing: -0.2em; 584 | content: "\00a0"; 585 | } 586 | 587 | .markdown /deep/ pre { 588 | word-wrap: normal; 589 | } 590 | 591 | .markdown /deep/ pre>code { 592 | padding: 0; 593 | margin: 0; 594 | font-size: 100%; 595 | word-break: normal; 596 | white-space: pre; 597 | background: transparent; 598 | border: 0; 599 | } 600 | 601 | .markdown /deep/ .highlight { 602 | margin-bottom: 16px; 603 | } 604 | 605 | .markdown /deep/ .highlight pre { 606 | margin-bottom: 0; 607 | word-break: normal; 608 | } 609 | 610 | .markdown /deep/ .highlight pre, 611 | .markdown /deep/ pre { 612 | padding: 16px; 613 | overflow: auto; 614 | font-size: 85%; 615 | line-height: 1.45; 616 | background-color: #f6f8fa; 617 | border-radius: 3px; 618 | } 619 | 620 | .markdown /deep/ pre code { 621 | display: inline; 622 | max-width: auto; 623 | padding: 0; 624 | margin: 0; 625 | overflow: visible; 626 | line-height: inherit; 627 | word-wrap: normal; 628 | background-color: transparent; 629 | border: 0; 630 | } 631 | 632 | .markdown /deep/ pre code::before, 633 | .markdown /deep/ pre code::after { 634 | content: normal; 635 | } 636 | 637 | .markdown /deep/ .full-commit .btn-outline:not(:disabled):hover { 638 | color: #005cc5; 639 | border-color: #005cc5; 640 | } 641 | 642 | .markdown /deep/ kbd { 643 | display: inline-block; 644 | padding: 3px 5px; 645 | font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 646 | line-height: 10px; 647 | color: #444d56; 648 | vertical-align: middle; 649 | background-color: #fafbfc; 650 | border: solid 2px #d1d5da; 651 | border-bottom-color: #c6cbd1; 652 | border-radius: 3px; 653 | box-shadow: inset 0 -1px 0 #c6cbd1; 654 | } 655 | 656 | .markdown /deep/ :checked+.radio-label { 657 | position: relative; 658 | z-index: 1; 659 | border-color: #0366d6; 660 | } 661 | 662 | .markdown /deep/ .task-list-item { 663 | list-style-type: none; 664 | } 665 | 666 | .markdown /deep/ .task-list-item+.task-list-item { 667 | margin-top: 3px; 668 | } 669 | 670 | .markdown /deep/ .task-list-item input { 671 | margin: 0 0.2em 0.25em -1.6em; 672 | vertical-align: middle; 673 | } 674 | 675 | .markdown /deep/ hr { 676 | border-bottom-color: #eee; 677 | } 678 | 679 | 680 | 681 | 682 | .markdown /deep/ .hljs { 683 | display: block; 684 | overflow-x: auto; 685 | padding: 0.5em; 686 | color: #333; 687 | background: #f8f8f8; 688 | } 689 | 690 | .markdown /deep/ .hljs-comment, 691 | .markdown /deep/ .hljs-quote { 692 | color: #998; 693 | font-style: italic; 694 | } 695 | 696 | .markdown /deep/ .hljs-keyword, 697 | .markdown /deep/ .hljs-selector-tag, 698 | .markdown /deep/ .hljs-subst { 699 | color: #333; 700 | font-weight: bold; 701 | } 702 | 703 | .markdown /deep/ .hljs-number, 704 | .markdown /deep/ .hljs-literal, 705 | .markdown /deep/ .hljs-variable, 706 | .markdown /deep/ .hljs-template-variable, 707 | .markdown /deep/ .hljs-tag .hljs-attr { 708 | color: #008080; 709 | } 710 | 711 | .markdown /deep/ .hljs-string, 712 | .markdown /deep/ .hljs-doctag { 713 | color: #d14; 714 | } 715 | 716 | .markdown /deep/ .hljs-title, 717 | .markdown /deep/ .hljs-section, 718 | .markdown /deep/ .hljs-selector-id { 719 | color: #900; 720 | font-weight: bold; 721 | } 722 | 723 | .markdown /deep/ .hljs-subst { 724 | font-weight: normal; 725 | } 726 | 727 | .markdown /deep/ .hljs-type, 728 | .markdown /deep/ .hljs-class .hljs-title { 729 | color: #458; 730 | font-weight: bold; 731 | } 732 | 733 | .markdown /deep/ .hljs-tag, 734 | .markdown /deep/ .hljs-name, 735 | .markdown /deep/ .hljs-attribute { 736 | color: #000080; 737 | font-weight: normal; 738 | } 739 | 740 | .markdown /deep/ .hljs-regexp, 741 | .markdown /deep/ .hljs-link { 742 | color: #009926; 743 | } 744 | 745 | .markdown /deep/ .hljs-symbol, 746 | .markdown /deep/ .hljs-bullet { 747 | color: #990073; 748 | } 749 | 750 | .markdown /deep/ .hljs-built_in, 751 | .markdown /deep/ .hljs-builtin-name { 752 | color: #0086b3; 753 | } 754 | 755 | .markdown /deep/ .hljs-meta { 756 | color: #999; 757 | font-weight: bold; 758 | } 759 | 760 | .markdown /deep/ .hljs-deletion { 761 | background: #fdd; 762 | } 763 | 764 | .markdown /deep/ .hljs-addition { 765 | background: #dfd; 766 | } 767 | 768 | .markdown /deep/ .hljs-emphasis { 769 | font-style: italic; 770 | } 771 | 772 | .markdown /deep/ .hljs-strong { 773 | font-weight: bold; 774 | } 775 | 776 | } 777 | -------------------------------------------------------------------------------- /src/components/markdown/markdown.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from '@angular/core'; 2 | 3 | 4 | @Component({ 5 | selector: 'markdown', 6 | templateUrl: 'markdown.html' 7 | }) 8 | export class MarkdownComponent { 9 | @Input()text: string; 10 | @Input()baseUrl: string; 11 | 12 | constructor() {} 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/components/repo-item/repo-item.html: -------------------------------------------------------------------------------- 1 |

2 | {{repo.owner.login}}/{{repo.name}} 3 | 4 |

5 |

{{repo.description}}

6 |

7 | 8 | 9 | {{repo.primaryLanguage.name}} 10 | 11 | 12 | 13 | {{repo.stargazers.totalCount<1000?repo.stargazers.totalCount:(repo.stargazers.totalCount/1000).toFixed(1)+'k'}} 14 | 15 | 16 | 17 | {{repo.forks.totalCount<1000?repo.forks.totalCount:(repo.forks.totalCount/1000).toFixed(1)+'k'}} 18 | 19 |

20 | 21 | -------------------------------------------------------------------------------- /src/components/repo-item/repo-item.scss: -------------------------------------------------------------------------------- 1 | repo-item { 2 | h2, p.description{ 3 | text-overflow: ellipsis; 4 | overflow: hidden; 5 | margin-bottom: 4px; 6 | } 7 | h2{ 8 | ion-icon{ 9 | color: #5d5d5d; 10 | display: inline-block; 11 | margin:0 .2em; 12 | } 13 | } 14 | span.language-block{ 15 | display: inline-block; 16 | width:1em; 17 | height:1em; 18 | position: relative; 19 | top: 0.15em; 20 | } 21 | span.widget{ 22 | display: inline-block; 23 | padding-right: 1em; 24 | ion-icon{ 25 | position: relative; 26 | top:0.05em; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/repo-item/repo-item.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from '@angular/core'; 2 | import {RepoItem} from "../../classes/repo"; 3 | import {colors} from "../../classes/language-color"; 4 | 5 | 6 | @Component({ 7 | selector: 'repo-item', 8 | templateUrl: 'repo-item.html' 9 | }) 10 | export class RepoItemComponent { 11 | @Input() repo:RepoItem; 12 | @Input() showOwnerLogin:boolean=true; 13 | 14 | get languageColor():string{ 15 | let color=colors[this.repo.primaryLanguage.name]; 16 | if (!color) { 17 | color='#808080'; 18 | } 19 | return color; 20 | } 21 | 22 | constructor() {} 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/components/search-box/search-box.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/search-box/search-box.scss: -------------------------------------------------------------------------------- 1 | search-box { 2 | $transition-time: 300ms; 3 | 4 | div.search-box{ 5 | position: relative; 6 | box-shadow: 0 0 10px 5px rgba(158, 158, 158, 0.12); 7 | border-radius: 3px; 8 | width:100%; 9 | transition: width $transition-time ease; 10 | button.action-button{ 11 | position: absolute; 12 | right:-100px; 13 | top:-3px; 14 | transition: right $transition-time ease; 15 | } 16 | } 17 | div.search-box.focusing{ 18 | width:calc(100% - 45px); 19 | button.action-button { 20 | right: -62px; 21 | } 22 | } 23 | .search-icon{ 24 | position: absolute; 25 | font-size: 2.4rem; 26 | top: 9px; 27 | left: 13px; 28 | } 29 | input.search-input{ 30 | -webkit-appearance: none; 31 | appearance: none; 32 | display: block; 33 | width: 100%; 34 | border: 0; 35 | border-radius: 3px; 36 | padding: 6px 30px 6px 43px; 37 | background-position: left 8px center; 38 | height: auto; 39 | font-size: 1.6rem; 40 | font-weight: 400; 41 | line-height: 3rem; 42 | color: #141414; 43 | background-color: #fff; 44 | } 45 | input.search-input:focus, input.search-input:active{ 46 | outline: none; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/components/search-box/search-box.ts: -------------------------------------------------------------------------------- 1 | import {Component, EventEmitter, Input, Output} from '@angular/core'; 2 | 3 | 4 | @Component({ 5 | selector: 'search-box', 6 | templateUrl: 'search-box.html' 7 | }) 8 | export class SearchBoxComponent { 9 | focusing:boolean=false; 10 | 11 | @Input()text: string; 12 | @Output()doSearch = new EventEmitter(); 13 | @Output() textChange:EventEmitter = new EventEmitter(); 14 | 15 | constructor() {} 16 | 17 | actionButtonClicked(){ 18 | // this.focusing=true;//prevent button sliding to right 19 | this.textChange.emit(this.text); 20 | this.doSearch.emit(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ionic App 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ionic", 3 | "short_name": "Ionic", 4 | "start_url": "index.html", 5 | "display": "standalone", 6 | "icons": [{ 7 | "src": "assets/imgs/logo.png", 8 | "sizes": "512x512", 9 | "type": "image/png" 10 | }], 11 | "background_color": "#4e8ef7", 12 | "theme_color": "#4e8ef7" 13 | } -------------------------------------------------------------------------------- /src/pages/bootstrap/bootstrap.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 9 |
10 | -------------------------------------------------------------------------------- /src/pages/bootstrap/bootstrap.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { BootstrapPage } from './bootstrap'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | BootstrapPage, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(BootstrapPage), 11 | ], 12 | }) 13 | export class BootstrapPageModule {} 14 | -------------------------------------------------------------------------------- /src/pages/bootstrap/bootstrap.scss: -------------------------------------------------------------------------------- 1 | page-bootstrap { 2 | img.logo{ 3 | background-color: #ffffff; 4 | width:40%; 5 | max-width: 10em; 6 | display: inline-block; 7 | border-radius: 1em; 8 | padding:1em; 9 | margin-top: 20vh; 10 | margin-bottom: 5em; 11 | } 12 | .scroll-content{ 13 | background-color: #000000; 14 | text-align: center; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/bootstrap/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {IonicPage, LoadingController, NavController} from 'ionic-angular'; 3 | import {AccountService} from "../../services/account.service"; 4 | import {ToastService} from "../../services/toast.service"; 5 | 6 | 7 | @IonicPage() 8 | @Component({ 9 | selector: 'page-bootstrap', 10 | templateUrl: 'bootstrap.html', 11 | }) 12 | export class BootstrapPage { 13 | 14 | constructor( 15 | private navCtrl: NavController, 16 | private accountSvc: AccountService, 17 | private loadingCtrl: LoadingController, 18 | private toastSvc: ToastService, 19 | ) {} 20 | 21 | 22 | auth(){ 23 | let loading=this.loadingCtrl.create({ 24 | spinner: 'dots', 25 | content: 'Loading' 26 | }); 27 | loading.present(); 28 | this.accountSvc.oAuth().then(()=>{ 29 | this.navCtrl.pop(); 30 | }).catch(()=>{ 31 | this.toastSvc.toast('Auth failed'); 32 | }).then(()=>{ 33 | loading.dismissAll(); 34 | }); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/pages/code-detail/code-detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{repoParam.name}} 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | -------------------------------------------------------------------------------- /src/pages/code-detail/code-detail.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { CodeDetailPage } from './code-detail'; 4 | import {PipesModule} from "../../pipes/pipes.module"; 5 | 6 | @NgModule({ 7 | declarations: [ 8 | CodeDetailPage, 9 | ], 10 | imports: [ 11 | IonicPageModule.forChild(CodeDetailPage), 12 | PipesModule, 13 | ], 14 | }) 15 | export class CodeDetailPageModule {} 16 | -------------------------------------------------------------------------------- /src/pages/code-detail/code-detail.scss: -------------------------------------------------------------------------------- 1 | page-code-detail { 2 | .code{ 3 | background-color: #f8f8f8; 4 | white-space: pre; 5 | padding: 10px; 6 | font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 7 | font-size: 12px; 8 | border-radius: 4px; 9 | overflow-x: scroll; 10 | } 11 | 12 | .code /deep/ .hljs { 13 | display: block; 14 | overflow-x: auto; 15 | padding: 0.5em; 16 | color: #333; 17 | background: #f8f8f8; 18 | } 19 | 20 | .code /deep/ .hljs-comment, 21 | .code /deep/ .hljs-quote { 22 | color: #998; 23 | font-style: italic; 24 | } 25 | 26 | .code /deep/ .hljs-keyword, 27 | .code /deep/ .hljs-selector-tag, 28 | .code /deep/ .hljs-subst { 29 | color: #333; 30 | font-weight: bold; 31 | } 32 | 33 | .code /deep/ .hljs-number, 34 | .code /deep/ .hljs-literal, 35 | .code /deep/ .hljs-variable, 36 | .code /deep/ .hljs-template-variable, 37 | .code /deep/ .hljs-tag .hljs-attr { 38 | color: #008080; 39 | } 40 | 41 | .code /deep/ .hljs-string, 42 | .code /deep/ .hljs-doctag { 43 | color: #d14; 44 | } 45 | 46 | .code /deep/ .hljs-title, 47 | .code /deep/ .hljs-section, 48 | .code /deep/ .hljs-selector-id { 49 | color: #900; 50 | font-weight: bold; 51 | } 52 | 53 | .code /deep/ .hljs-subst { 54 | font-weight: normal; 55 | } 56 | 57 | .code /deep/ .hljs-type, 58 | .code /deep/ .hljs-class .hljs-title { 59 | color: #458; 60 | font-weight: bold; 61 | } 62 | 63 | .code /deep/ .hljs-tag, 64 | .code /deep/ .hljs-name, 65 | .code /deep/ .hljs-attribute { 66 | color: #000080; 67 | font-weight: normal; 68 | } 69 | 70 | .code /deep/ .hljs-regexp, 71 | .code /deep/ .hljs-link { 72 | color: #009926; 73 | } 74 | 75 | .code /deep/ .hljs-symbol, 76 | .code /deep/ .hljs-bullet { 77 | color: #990073; 78 | } 79 | 80 | .code /deep/ .hljs-built_in, 81 | .code /deep/ .hljs-builtin-name { 82 | color: #0086b3; 83 | } 84 | 85 | .code /deep/ .hljs-meta { 86 | color: #999; 87 | font-weight: bold; 88 | } 89 | 90 | .code /deep/ .hljs-deletion { 91 | background: #fdd; 92 | } 93 | 94 | .code /deep/ .hljs-addition { 95 | background: #dfd; 96 | } 97 | 98 | .code /deep/ .hljs-emphasis { 99 | font-style: italic; 100 | } 101 | 102 | .code /deep/ .hljs-strong { 103 | font-weight: bold; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/pages/code-detail/code-detail.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {IonicPage, LoadingController, NavController, NavParams} from 'ionic-angular'; 3 | import {RepoParam} from "../../classes/repo"; 4 | import {ApiService} from "../../services/api.service"; 5 | import * as highlight from 'highlightjs'; 6 | import {ToastService} from "../../services/toast.service"; 7 | 8 | 9 | @IonicPage() 10 | @Component({ 11 | selector: 'page-code-detail', 12 | templateUrl: 'code-detail.html', 13 | }) 14 | export class CodeDetailPage { 15 | 16 | content:string; 17 | 18 | constructor( 19 | private navCtrl: NavController, 20 | private navParams: NavParams, 21 | private apiSvc: ApiService, 22 | private loadingCtrl: LoadingController, 23 | private toastSvc: ToastService, 24 | ) {} 25 | 26 | ionViewWillLoad(){ 27 | let loading=this.loadingCtrl.create({ 28 | spinner: 'dots', 29 | content: 'Loading' 30 | }); 31 | loading.present(); 32 | this.apiSvc.getFileContent(this.repoParam, this.path).then((content:string) => { 33 | this.content=highlight.highlightAuto(content).value.replace(/\n/g,'
'); 34 | loading.dismiss(); 35 | }).catch(()=>{ 36 | this.navCtrl.pop(); 37 | loading.dismiss().then(()=>{ 38 | this.toastSvc.toast('Fail to load file list'); 39 | }); 40 | }); 41 | } 42 | 43 | get repoParam():RepoParam{ 44 | return this.navParams.get('repoParam'); 45 | } 46 | 47 | get path():string{ 48 | return this.navParams.get('path'); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/pages/code-list/code-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{repoParam.name}} 5 | 6 | 7 | 8 | 9 | 10 | 11 |

12 | /{{path}} 13 |

14 | 15 | 16 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | {{content.name}} 30 | 31 | 32 | 33 | {{content.name}} 34 | 35 | 36 | 37 | 38 |
39 | -------------------------------------------------------------------------------- /src/pages/code-list/code-list.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { CodeListPage } from './code-list'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | CodeListPage, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(CodeListPage), 11 | ], 12 | }) 13 | export class CodeListPageModule {} 14 | -------------------------------------------------------------------------------- /src/pages/code-list/code-list.scss: -------------------------------------------------------------------------------- 1 | page-code-list { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/code-list/code-list.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {IonicPage, LoadingController, NavController, NavParams} from 'ionic-angular'; 3 | import {ApiService} from "../../services/api.service"; 4 | import {RepoParam} from "../../classes/repo"; 5 | import {Content} from "../../classes/content"; 6 | import {CodeDetailPage} from "../code-detail/code-detail"; 7 | import {ToastService} from "../../services/toast.service"; 8 | 9 | 10 | @IonicPage() 11 | @Component({ 12 | selector: 'page-code-list', 13 | templateUrl: 'code-list.html', 14 | }) 15 | export class CodeListPage { 16 | 17 | contents:Content[]; 18 | 19 | constructor( 20 | private navCtrl: NavController, 21 | private navParams: NavParams, 22 | private apiSvc: ApiService, 23 | private loadingCtrl: LoadingController, 24 | private toastSvc: ToastService, 25 | ) {} 26 | 27 | ionViewWillLoad(){ 28 | let loading=this.loadingCtrl.create({ 29 | spinner: 'dots', 30 | content: 'Loading' 31 | }); 32 | loading.present(); 33 | this.apiSvc.getContents(this.repoParam, this.path).then((contents:Content[]) => { 34 | this.contents=contents; 35 | loading.dismiss(); 36 | }).catch(()=>{ 37 | this.navCtrl.pop(); 38 | loading.dismiss().then(()=>{ 39 | this.toastSvc.toast('Fail to load file list'); 40 | }); 41 | });; 42 | } 43 | 44 | get repoParam():RepoParam{ 45 | return this.navParams.get('repoParam'); 46 | } 47 | 48 | get path():string{ 49 | return this.navParams.get('path'); 50 | } 51 | 52 | viewDir(path:string){ 53 | this.navCtrl.push(CodeListPage,{ 54 | repoParam: this.repoParam, 55 | path: path 56 | }); 57 | } 58 | 59 | viewFile(path:string){ 60 | this.navCtrl.push(CodeDetailPage,{ 61 | repoParam: this.repoParam, 62 | path: path 63 | }); 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/pages/content-list/content-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Contents 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/pages/content-list/content-list.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { ContentListPage } from './content-list'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | ContentListPage, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(ContentListPage), 11 | ], 12 | }) 13 | export class ContentListPageModule {} 14 | -------------------------------------------------------------------------------- /src/pages/content-list/content-list.scss: -------------------------------------------------------------------------------- 1 | page-content-list { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/content-list/content-list.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { IonicPage, NavController, NavParams } from 'ionic-angular'; 3 | import {ApiService} from "../../services/api.service"; 4 | 5 | 6 | @IonicPage() 7 | @Component({ 8 | selector: 'page-content-list', 9 | templateUrl: 'content-list.html', 10 | }) 11 | export class ContentListPage { 12 | 13 | constructor( 14 | public navCtrl: NavController, 15 | public navParams: NavParams, 16 | private apiSvc: ApiService, 17 | ) {} 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/dashboard/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dashboard 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

25 | {{event.actor.display_login}} {{eventVerb(event)}} {{event.repo.name}} 26 |

27 |
28 |
29 | 30 |
31 | 32 |
33 | 34 |
35 | -------------------------------------------------------------------------------- /src/pages/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { DashboardPage } from './dashboard'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | DashboardPage, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(DashboardPage), 11 | ], 12 | }) 13 | export class DashboardPageModule {} 14 | -------------------------------------------------------------------------------- /src/pages/dashboard/dashboard.scss: -------------------------------------------------------------------------------- 1 | page-dashboard { 2 | ion-item{ 3 | ion-avatar{ 4 | font-size: 1.5em; 5 | margin: 0 0.4em 0 0!important; 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | ion-icon{ 10 | color: #959595; 11 | } 12 | } 13 | p{ 14 | white-space: normal; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/dashboard/dashboard.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { IonicPage, NavController, NavParams } from 'ionic-angular'; 3 | import {ApiService} from "../../services/api.service"; 4 | import {AccountService} from "../../services/account.service"; 5 | import {UserPage} from "../user/user"; 6 | import {RepoPage} from "../repo/repo"; 7 | 8 | 9 | 10 | @IonicPage() 11 | @Component({ 12 | selector: 'page-dashboard', 13 | templateUrl: 'dashboard.html', 14 | }) 15 | export class DashboardPage { 16 | events=[]; 17 | 18 | constructor( 19 | protected navCtrl: NavController, 20 | protected navParams: NavParams, 21 | protected accountSvc: AccountService, 22 | protected apiSvc: ApiService, 23 | ) {} 24 | 25 | doRefresh(refresher){ 26 | this.freshenEvents().catch(()=>{ 27 | return; 28 | }).then(()=>{ 29 | refresher.complete(); 30 | }) 31 | } 32 | 33 | eventIcon(eventType:string):string{ 34 | switch (eventType){ 35 | case 'WatchEvent': return 'md-star'; 36 | case 'PushEvent': return 'md-git-commit'; 37 | case 'ForkEvent': return 'md-git-network'; 38 | case 'IssuesEvent': return 'md-alert'; 39 | case 'IssueCommentEvent': return 'md-chatboxes'; 40 | case 'PullRequestEvent': return 'md-git-pull-request'; 41 | default: return 'md-notifications'; 42 | } 43 | } 44 | 45 | eventVerb(event):string{ 46 | switch (event.type){ 47 | case 'WatchEvent': return 'starred'; 48 | case 'PushEvent': return 'pushed to'; 49 | case 'ForkEvent': return 'forked'; 50 | case 'IssuesEvent': return `${event.payload.action} issue #${event.payload.issue.number} in`; 51 | case 'IssueCommentEvent': return `commented on issue #${event.payload.issue.number} in`; 52 | case 'PullRequestEvent': return `${event.payload.action} a pull request in`; 53 | default: return '...'; 54 | } 55 | } 56 | 57 | ionViewDidLoad(){ 58 | this.freshenEvents(); 59 | this.accountSvc.userUpdated.subscribe(() => { 60 | this.freshenEvents(); 61 | }); 62 | } 63 | 64 | freshenEvents():Promise{ 65 | if (this.accountSvc.user.login) { 66 | return this.apiSvc.getReceivedEvents(this.accountSvc.user.login).then(events=>{ 67 | this.events=events; 68 | // console.log(this.events); 69 | }); 70 | }else{ 71 | return Promise.resolve(); 72 | } 73 | } 74 | 75 | 76 | viewRepo(repoString:string){ 77 | let t=repoString.split('/'); 78 | this.navCtrl.push(RepoPage,{ 79 | 'ownerLogin':t[0], 80 | 'name':t[1] 81 | }); 82 | } 83 | 84 | viewUser(login:string){ 85 | this.navCtrl.push(UserPage,{ 86 | 'login':login 87 | }); 88 | } 89 | 90 | 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/pages/repo-list/hot-repos.ts: -------------------------------------------------------------------------------- 1 | import {Component} from "@angular/core"; 2 | import {RepoListPage} from "./repo-list"; 3 | import {SearchReposPage} from "./search-repos"; 4 | 5 | 6 | @Component({ 7 | selector: 'page-hot-repos', 8 | templateUrl: 'repo-list.html', 9 | }) 10 | export class HotReposPage extends RepoListPage { 11 | title='Hot Repos'; 12 | showSearchBox=true; 13 | 14 | getRepos(cursor:string){ 15 | return this.apiSvc.getHotRepos(); 16 | } 17 | 18 | doSearch(){ 19 | this.navCtrl.push(SearchReposPage,{ 20 | 'searchText': this.searchText 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/repo-list/owned-repos.ts: -------------------------------------------------------------------------------- 1 | import {Component} from "@angular/core"; 2 | import {RepoListPage} from "./repo-list"; 3 | 4 | 5 | @Component({ 6 | selector: 'page-owned-repos', 7 | templateUrl: 'repo-list.html', 8 | }) 9 | export class OwnedReposPage extends RepoListPage { 10 | title='Owned Repos'; 11 | hideOwnerLogin=true; 12 | 13 | getRepos(cursor:string){ 14 | return this.apiSvc.getOwnedRepos(this.login,cursor); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/repo-list/repo-forks.ts: -------------------------------------------------------------------------------- 1 | import {Component} from "@angular/core"; 2 | import {RepoListPage} from "./repo-list"; 3 | 4 | 5 | @Component({ 6 | selector: 'page-repo-forks', 7 | templateUrl: 'repo-list.html', 8 | }) 9 | export class RepoForksPage extends RepoListPage { 10 | title='Forks'; 11 | 12 | getRepos(cursor:string){ 13 | return this.apiSvc.getRepoForks(this.navParams.get('repoParam'),cursor); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/repo-list/repo-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 |
24 | 25 | 26 | {{totalCount}} Repos 27 | 28 | 29 | 30 | 33 | 34 | 35 |
36 | 37 |
38 | 39 | 40 | 43 | 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | -------------------------------------------------------------------------------- /src/pages/repo-list/repo-list.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import {ComponentsModule} from "../../components/components.module"; 4 | import {StarredReposPage} from "./starred-repos"; 5 | import {OwnedReposPage} from "./owned-repos"; 6 | import {HotReposPage} from "./hot-repos"; 7 | import {RepoForksPage} from "./repo-forks"; 8 | import {SearchReposPage} from "./search-repos"; 9 | import {RepoListPage} from "./repo-list"; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | RepoListPage, 14 | StarredReposPage, 15 | OwnedReposPage, 16 | RepoForksPage, 17 | HotReposPage, 18 | SearchReposPage, 19 | ], 20 | imports: [ 21 | ComponentsModule, 22 | IonicPageModule.forChild(StarredReposPage), 23 | IonicPageModule.forChild(OwnedReposPage), 24 | IonicPageModule.forChild(RepoForksPage), 25 | IonicPageModule.forChild(HotReposPage), 26 | IonicPageModule.forChild(SearchReposPage), 27 | ], 28 | }) 29 | export class RepoListPageModule {} 30 | -------------------------------------------------------------------------------- /src/pages/repo-list/repo-list.scss: -------------------------------------------------------------------------------- 1 | page-repo-list, page-hot-repos, page-owned-repos, page-repo-forks, page-search-repos, page-starred-repos { 2 | div.search-box-container{ 3 | width:100%; 4 | padding:20px 16px; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/repo-list/repo-list.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {IonicPage, LoadingController, NavController, NavParams} from 'ionic-angular'; 3 | import {ToastService} from "../../services/toast.service"; 4 | import {ApiService} from "../../services/api.service"; 5 | import {RepoItem} from "../../classes/repo"; 6 | import {NodesPage, PageInfo} from "../../classes/nodes-page"; 7 | import {RepoPage} from "../repo/repo"; 8 | 9 | 10 | /** 11 | * This is a base class 12 | * **/ 13 | @IonicPage() 14 | @Component({ 15 | selector: 'page-repo-list', 16 | templateUrl: 'repo-list.html', 17 | }) 18 | export class RepoListPage { 19 | title:string='Repo List'; 20 | hideOwnerLogin:boolean=false; 21 | showSearchBox:boolean=false; 22 | disableRefresher:boolean=false; 23 | 24 | searchText:string; 25 | totalCount:number=-1; 26 | pageInfo:PageInfo; 27 | repos:RepoItem[]=[]; 28 | login:string; 29 | 30 | constructor( 31 | protected navCtrl: NavController, 32 | protected navParams: NavParams, 33 | protected apiSvc: ApiService, 34 | protected loadingCtrl: LoadingController, 35 | protected toastSvc: ToastService, 36 | ) { 37 | this.login=navParams.get('login'); 38 | this.searchText=navParams.get('searchText')||''; 39 | } 40 | 41 | ionViewWillLoad() { 42 | this.initRepos().catch(()=>{ 43 | this.navCtrl.pop(); 44 | }); 45 | } 46 | 47 | doRefresh(refresher){ 48 | this.initRepos().catch(()=>{ 49 | return; 50 | }).then(()=>{ 51 | refresher.complete(); 52 | }) 53 | } 54 | 55 | 56 | startLoading(){ 57 | let loading=this.loadingCtrl.create({ 58 | spinner: 'dots', 59 | content: 'Loading' 60 | }); 61 | loading.present(); 62 | return loading; 63 | } 64 | 65 | initRepos():Promise{ 66 | let loading=this.startLoading(); 67 | this.repos=[]; 68 | this.pageInfo=null; 69 | this.totalCount=-1; 70 | return this.appendRepos().then(()=>{ 71 | loading.dismiss(); 72 | }).catch(()=>{ 73 | loading.dismiss(); 74 | this.toastSvc.toast('Fail to load repos'); 75 | throw new Error(); 76 | }); 77 | } 78 | 79 | appendRepos():Promise{ 80 | return this.getRepos(this.pageInfo?this.pageInfo.endCursor:null).then(data=>{ 81 | this.totalCount=data.totalCount; 82 | this.pageInfo=data.pageInfo; 83 | Array.prototype.push.apply(this.repos,data.nodes); 84 | // console.log(this.repos); 85 | }); 86 | } 87 | 88 | getRepos(cursor:string):Promise>{ 89 | return Promise.resolve(null); 90 | } 91 | 92 | viewRepo(repo:RepoItem){ 93 | this.navCtrl.push(RepoPage,{ 94 | 'ownerLogin':repo.owner.login, 95 | 'name':repo.name 96 | }); 97 | } 98 | 99 | doSearch(){} 100 | 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/pages/repo-list/search-repos.ts: -------------------------------------------------------------------------------- 1 | import {Component} from "@angular/core"; 2 | import {RepoListPage} from "./repo-list"; 3 | 4 | 5 | @Component({ 6 | selector: 'page-search-repos', 7 | templateUrl: 'repo-list.html', 8 | }) 9 | export class SearchReposPage extends RepoListPage { 10 | title='Search Repos'; 11 | showSearchBox=true; 12 | disableRefresher=true; 13 | 14 | getRepos(){ 15 | return this.apiSvc.searchRepos(this.searchText); 16 | } 17 | 18 | initRepos(){ 19 | let loading=this.startLoading(); 20 | this.repos=[]; 21 | this.pageInfo=null; 22 | this.totalCount=-1; 23 | return this.appendRepos().then(()=>{ 24 | loading.dismiss(); 25 | }).catch(()=>{ 26 | loading.dismiss(); 27 | this.toastSvc.toast('Search failed'); 28 | throw new Error(); 29 | }); 30 | } 31 | 32 | doSearch(){ 33 | this.initRepos().catch(()=>{}); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/pages/repo-list/starred-repos.ts: -------------------------------------------------------------------------------- 1 | import {Component} from "@angular/core"; 2 | import {RepoListPage} from "./repo-list"; 3 | 4 | @Component({ 5 | selector: 'page-starred-repos', 6 | templateUrl: 'repo-list.html', 7 | }) 8 | export class StarredReposPage extends RepoListPage { 9 | title='Starred Repos'; 10 | 11 | getRepos(cursor:string){ 12 | return this.apiSvc.getStarredRepos(this.login,cursor); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/pages/repo/repo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Repo 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 |
23 |
24 | 25 |

{{repo.name}}

26 |

27 | 28 | {{topic.topic.name}} 29 | 30 |

31 |

32 | 33 | 34 | {{repo.primaryLanguage.name}} 35 | 36 | 37 | 38 | {{repo.diskUsage}} KB 39 | 40 | 41 | 42 | private 43 | 44 | 45 | 46 | fork 47 | 48 | 49 | 50 | mirror 51 | 52 |

53 |

{{repo.description}}

54 |
55 | 56 | 75 | 76 |
77 | 78 | 79 | 80 | {{repo.issues.totalCount}} issues 81 | 82 | 83 | 84 | {{repo.pullRequests.totalCount}} pull requests 85 | 86 | 87 | 88 | {{repo.releases.totalCount}} releases 89 | 90 | 91 | 92 | {{repo.license?repo.license:'No license'}} 93 | 94 | 95 | 96 | Updated at {{repo.updatedAt}} 97 | 98 | 99 | 100 | 101 | 102 |
103 | 104 |
105 |
106 | README 107 |
108 | 109 |
110 | 111 |
112 | 113 |
114 | -------------------------------------------------------------------------------- /src/pages/repo/repo.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { RepoPage } from './repo'; 4 | import {ComponentsModule} from "../../components/components.module"; 5 | 6 | @NgModule({ 7 | declarations: [ 8 | RepoPage, 9 | ], 10 | imports: [ 11 | IonicPageModule.forChild(RepoPage), 12 | ComponentsModule, 13 | ], 14 | }) 15 | export class RepoPageModule {} 16 | -------------------------------------------------------------------------------- /src/pages/repo/repo.scss: -------------------------------------------------------------------------------- 1 | page-repo { 2 | 3 | div.meta{ 4 | padding:3rem 20px 0; 5 | text-align: center; 6 | h2.name{ 7 | font-size: 3rem; 8 | } 9 | h3.login{ 10 | color: #9f9f9f; 11 | font-size: 2rem; 12 | } 13 | p.topics{ 14 | span{ 15 | display: inline-block; 16 | background-color: #959595; 17 | margin:0.15em 0.25em; 18 | padding: 0.2em 0.4em; 19 | border-radius: 0.3em; 20 | color: #ffffff; 21 | } 22 | } 23 | p.widgets{ 24 | span.widget{ 25 | display: inline-block; 26 | margin-right: 1.2em; 27 | } 28 | span.language-block{ 29 | display: inline-block; 30 | width:1em; 31 | height: 1em; 32 | position: relative; 33 | top:0.1em; 34 | } 35 | } 36 | p.description{ 37 | font-size: 1.4rem; 38 | } 39 | } 40 | 41 | 42 | div.statistic{ 43 | padding:5px 15px 10px; 44 | ion-col{ 45 | text-align: center; 46 | b{ 47 | font-size: 2rem; 48 | } 49 | } 50 | } 51 | 52 | 53 | div.readme{ 54 | padding:1.2em; 55 | div.header{ 56 | background-color: #959595; 57 | color: #ffffff; 58 | padding:0.5em 1em; 59 | } 60 | markdown .markdown{ 61 | border: solid 1px #959595; 62 | padding: 1em; 63 | } 64 | } 65 | 66 | 67 | div.secondary{ 68 | padding:0 20px; 69 | ion-col{ 70 | text-overflow: ellipsis; 71 | overflow: hidden; 72 | ion-icon{ 73 | display: inline-block; 74 | width:1.1em; 75 | position: relative; 76 | top:0.05em; 77 | color: #5d5d5d; 78 | } 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/pages/repo/repo.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {IonicPage, LoadingController, NavController, NavParams} from 'ionic-angular'; 3 | import {ApiService} from "../../services/api.service"; 4 | import {ToastService} from "../../services/toast.service"; 5 | import {RepoDetail, RepoParam} from "../../classes/repo"; 6 | import {colors} from "../../classes/language-color"; 7 | import {WatchersPage} from "../user-list/watchers"; 8 | import {StargazersPage} from "../user-list/stargazers"; 9 | import {RepoForksPage} from "../repo-list/repo-forks"; 10 | import {CodeListPage} from "../code-list/code-list"; 11 | 12 | 13 | 14 | @IonicPage() 15 | @Component({ 16 | selector: 'page-repo', 17 | templateUrl: 'repo.html', 18 | }) 19 | export class RepoPage { 20 | repo:RepoDetail; 21 | readme:string; 22 | togglingStar:boolean=false; 23 | 24 | constructor( 25 | public navCtrl: NavController, 26 | public navParams: NavParams, 27 | protected apiSvc: ApiService, 28 | protected loadingCtrl: LoadingController, 29 | protected toastSvc: ToastService, 30 | ) {} 31 | 32 | get languageColor():string{ 33 | let color=colors[this.repo.primaryLanguage.name]; 34 | if (!color) { 35 | color='#808080'; 36 | } 37 | return color; 38 | } 39 | 40 | get markdownBaseUrl():string{ 41 | return `https://github.com/${this.repoParam.owner}/${this.repoParam.name}/raw/master`; 42 | } 43 | 44 | ionViewDidLoad() { 45 | let loading=this.loadingCtrl.create({ 46 | spinner: 'dots', 47 | content: 'Loading' 48 | }); 49 | loading.present(); 50 | this.freshenRepoDetail().catch(()=>{ 51 | this.navCtrl.pop(); 52 | }).then(()=>{ 53 | loading.dismiss(); 54 | }); 55 | } 56 | 57 | doRefresh(refresher){ 58 | this.freshenRepoDetail().catch(()=>{ 59 | return; 60 | }).then(()=>{ 61 | refresher.complete(); 62 | }) 63 | } 64 | 65 | 66 | freshenRepoDetail():Promise{ 67 | this.freshenRepoReadme(); 68 | return this.apiSvc.getRepo(this.repoParam.owner,this.repoParam.name).then((repo)=>{ 69 | this.repo=repo; 70 | }).catch(() => { 71 | this.toastSvc.toast('Fail to load repo info'); 72 | throw new Error(); 73 | }); 74 | } 75 | 76 | 77 | get repoParam():RepoParam{ 78 | return { 79 | owner:this.navParams.get('ownerLogin'), 80 | name:this.navParams.get('name') 81 | }; 82 | } 83 | 84 | viewStargazers(){ 85 | this.navCtrl.push(StargazersPage,{ 86 | repoParam: this.repoParam 87 | }); 88 | } 89 | 90 | viewForks(){ 91 | this.navCtrl.push(RepoForksPage,{ 92 | repoParam: this.repoParam 93 | }); 94 | } 95 | 96 | viewWatchers(){ 97 | this.navCtrl.push(WatchersPage,{ 98 | repoParam: this.repoParam 99 | }); 100 | } 101 | 102 | viewCode(){ 103 | this.navCtrl.push(CodeListPage, { 104 | repoParam: this.repoParam, 105 | path: '' 106 | }); 107 | } 108 | 109 | freshenRepoReadme():Promise{ 110 | return this.apiSvc.getRepoReadme(this.repoParam).then((readme:string)=>{ 111 | this.readme=readme; 112 | }); 113 | } 114 | 115 | toggleStar(){ 116 | if (this.repo.viewerHasStarred) { 117 | this.togglingStar=true; 118 | this.apiSvc.removeStar(this.repo.id).then(() => { 119 | this.repo.viewerHasStarred=false; 120 | this.repo.stargazers.totalCount-=1; 121 | this.togglingStar=false; 122 | }); 123 | }else{ 124 | this.togglingStar=true; 125 | this.apiSvc.addStar(this.repo.id).then(() => { 126 | this.repo.viewerHasStarred=true; 127 | this.repo.stargazers.totalCount+=1; 128 | this.togglingStar=false; 129 | }); 130 | } 131 | } 132 | 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/pages/settings/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Settings 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |

14 | GitMug
15 | v{{version.major}}.{{version.minor}}.{{version.patch}}
16 |
17 |

18 |

19 | Found a new release  20 | New 21 |
22 | v{{version.major}}.{{version.minor}}.{{version.patch}} -> v{{updateSvc.latestVersion.major}}.{{updateSvc.latestVersion.minor}}.{{updateSvc.latestVersion.patch}}
23 | 24 |

25 | 28 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /src/pages/settings/settings.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { SettingsPage } from './settings'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | SettingsPage, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(SettingsPage), 11 | ], 12 | }) 13 | export class SettingsPageModule {} 14 | -------------------------------------------------------------------------------- /src/pages/settings/settings.scss: -------------------------------------------------------------------------------- 1 | page-settings { 2 | .big-logo{ 3 | background-color: #ffffff; 4 | width:40%; 5 | max-width: 10em; 6 | display: inline-block; 7 | border-radius: 1em; 8 | padding:1em; 9 | margin-top: 6em; 10 | margin-bottom: 1em; 11 | box-shadow: 0 0 10px 5px rgba(158, 158, 158, 0.1); 12 | } 13 | p{ 14 | line-height: 1.6em; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/settings/settings.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {AlertController, IonicPage, NavController, NavParams, Platform} from 'ionic-angular'; 3 | import {CONST} from "../../app/const"; 4 | import {InAppBrowser} from "@ionic-native/in-app-browser"; 5 | import {UpdateService} from "../../services/update.service"; 6 | import {AccountService} from "../../services/account.service"; 7 | 8 | 9 | 10 | @IonicPage() 11 | @Component({ 12 | selector: 'page-settings', 13 | templateUrl: 'settings.html', 14 | }) 15 | export class SettingsPage { 16 | version=CONST.version; 17 | 18 | constructor( 19 | private navCtrl: NavController, 20 | private navParams: NavParams, 21 | private platform: Platform, 22 | private alertCtrl: AlertController, 23 | private accountSvc: AccountService, 24 | private inAppBrowser: InAppBrowser, 25 | public updateSvc: UpdateService, 26 | ) {} 27 | 28 | openGitHubLink(){ 29 | this.inAppBrowser.create('https://github.com/awmleer/GitMug','_system'); 30 | } 31 | 32 | update(){ 33 | if (this.platform.is('android')) { 34 | this.inAppBrowser.create(this.updateSvc.androidDownloadLink,'_system'); 35 | }else{ 36 | this.alertCtrl.create({ 37 | title: 'Update', 38 | subTitle: 'Please update in App Store', 39 | buttons: ['OK'] 40 | }).present(); 41 | } 42 | } 43 | 44 | logout(){ 45 | this.accountSvc.logout().then(() => { 46 | this.alertCtrl.create({ 47 | title: 'Log out', 48 | subTitle: 'Logged out successfully. Please reopen GitMug.', 49 | buttons: [{ 50 | text: 'OK', 51 | handler: data => { 52 | this.platform.exitApp(); 53 | } 54 | }] 55 | }).present(); 56 | }); 57 | } 58 | 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/pages/tabs/tabs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/pages/tabs/tabs.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {DashboardPage} from "../dashboard/dashboard"; 3 | import {MePage} from "../user/me"; 4 | import {HotReposPage} from "../repo-list/hot-repos"; 5 | 6 | @Component({ 7 | templateUrl: 'tabs.html' 8 | }) 9 | export class TabsPage { 10 | 11 | tab1Root = DashboardPage; 12 | tab2Root = HotReposPage; 13 | tab3Root = MePage; 14 | 15 | constructor() {} 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/user-list/followers.ts: -------------------------------------------------------------------------------- 1 | import {Component} from "@angular/core"; 2 | import {UserListPage} from "./user-list"; 3 | 4 | 5 | @Component({ 6 | selector: 'page-followers', 7 | templateUrl: 'user-list.html', 8 | }) 9 | export class FollowersPage extends UserListPage { 10 | title='Followers'; 11 | 12 | getUsers(cursor:string){ 13 | return this.apiSvc.getFollowers(this.navParams.get('login'),cursor); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/user-list/following.ts: -------------------------------------------------------------------------------- 1 | import {Component} from "@angular/core"; 2 | import {UserListPage} from "./user-list"; 3 | 4 | @Component({ 5 | selector: 'page-following', 6 | templateUrl: 'user-list.html', 7 | }) 8 | export class FollowingPage extends UserListPage { 9 | title='Following'; 10 | 11 | getUsers(cursor:string){ 12 | return this.apiSvc.getFollowing(this.navParams.get('login'),cursor); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/user-list/stargazers.ts: -------------------------------------------------------------------------------- 1 | import {Component} from "@angular/core"; 2 | import {UserListPage} from "./user-list"; 3 | 4 | 5 | @Component({ 6 | selector: 'page-stargazers', 7 | templateUrl: 'user-list.html', 8 | }) 9 | export class StargazersPage extends UserListPage { 10 | title='Stargazers'; 11 | getUsers(cursor:string){ 12 | return this.apiSvc.getStargazers(this.navParams.get('repoParam'),cursor); // repo:{owner:string;name:string;} 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/pages/user-list/user-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | {{totalCount}} Users 23 | 24 | 25 | 26 | 33 | 34 | 35 |
36 | 37 |
38 | 39 | 42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | -------------------------------------------------------------------------------- /src/pages/user-list/user-list.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import {FollowersPage} from "./followers"; 4 | import {FollowingPage} from "./following"; 5 | import {StargazersPage} from "./stargazers"; 6 | import {WatchersPage} from "./watchers"; 7 | import {UserListPage} from "./user-list"; 8 | 9 | @NgModule({ 10 | declarations: [ 11 | UserListPage, 12 | FollowersPage, 13 | FollowingPage, 14 | StargazersPage, 15 | WatchersPage, 16 | ], 17 | imports: [ 18 | IonicPageModule.forChild(FollowersPage), 19 | IonicPageModule.forChild(FollowingPage), 20 | IonicPageModule.forChild(StargazersPage), 21 | IonicPageModule.forChild(WatchersPage), 22 | ], 23 | }) 24 | export class UserListPageModule {} 25 | -------------------------------------------------------------------------------- /src/pages/user-list/user-list.scss: -------------------------------------------------------------------------------- 1 | page-user-list, page-followers, page-following, page-stargazers, page-watchers { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/user-list/user-list.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {IonicPage, LoadingController, NavController, NavParams} from 'ionic-angular'; 3 | import {ApiService} from "../../services/api.service"; 4 | import {UserItem} from "../../classes/user"; 5 | import {NodesPage, PageInfo} from "../../classes/nodes-page"; 6 | import {ToastService} from "../../services/toast.service"; 7 | import {UserPage} from "../user/user"; 8 | 9 | 10 | /** 11 | * This is a base class 12 | * **/ 13 | @IonicPage() 14 | @Component({ 15 | selector: 'page-user-list', 16 | templateUrl: 'user-list.html', 17 | }) 18 | export class UserListPage { 19 | title='User List'; 20 | users:UserItem[]=[]; 21 | totalCount:number=-1; 22 | pageInfo:PageInfo; 23 | 24 | constructor( 25 | protected navCtrl: NavController, 26 | protected apiSvc: ApiService, 27 | protected toastSvc: ToastService, 28 | public navParams: NavParams, 29 | protected loadingCtrl: LoadingController 30 | ) {} 31 | 32 | ionViewWillLoad(){ 33 | this.initUsers(); 34 | } 35 | 36 | doRefresh(refresher){ 37 | this.initUsers().catch(()=>{ 38 | return; 39 | }).then(()=>{ 40 | refresher.complete(); 41 | }) 42 | } 43 | 44 | 45 | initUsers():Promise{ 46 | let loading=this.loadingCtrl.create({ 47 | spinner: 'dots', 48 | content: 'Loading' 49 | }); 50 | loading.present(); 51 | this.users=[]; 52 | this.pageInfo=null; 53 | this.totalCount=-1; 54 | return this.appendUsers().then(()=>{ 55 | loading.dismiss(); 56 | }).catch(()=>{ 57 | this.navCtrl.pop(); 58 | loading.dismiss().then(()=>{ 59 | this.toastSvc.toast('Fail to load users'); 60 | }); 61 | }); 62 | } 63 | 64 | appendUsers():Promise{ 65 | return this.getUsers(this.pageInfo?this.pageInfo.endCursor:null).then(data=>{ 66 | this.totalCount=data.totalCount; 67 | this.pageInfo=data.pageInfo; 68 | Array.prototype.push.apply(this.users,data.nodes); 69 | // console.log(this.users); 70 | }); 71 | } 72 | 73 | getUsers(cursor:string):Promise>{ 74 | return Promise.resolve(null); 75 | } 76 | 77 | viewUser(user:UserItem){ 78 | this.navCtrl.push(UserPage,{ 79 | 'login':user.login 80 | }); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/pages/user-list/watchers.ts: -------------------------------------------------------------------------------- 1 | import {Component} from "@angular/core"; 2 | import {UserListPage} from "./user-list"; 3 | 4 | @Component({ 5 | selector: 'page-watchers', 6 | templateUrl: 'user-list.html', 7 | }) 8 | export class WatchersPage extends UserListPage { 9 | title='Watchers'; 10 | getUsers(cursor:string){ 11 | return this.apiSvc.getWatchers(this.navParams.get('repoParam'),cursor); // repo:{owner:string;name:string;} 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/user/me.ts: -------------------------------------------------------------------------------- 1 | import {Component} from "@angular/core"; 2 | import {UserPage} from "./user"; 3 | import {UserProfile} from "../../classes/user"; 4 | import {StarredReposPage} from "../repo-list/starred-repos"; 5 | import {OwnedReposPage} from "../repo-list/owned-repos"; 6 | 7 | 8 | @Component({ 9 | selector: 'page-me', 10 | templateUrl: 'user.html', 11 | }) 12 | export class MePage extends UserPage { 13 | showSettingsButton=true; 14 | 15 | getUserProfile():Promise{ 16 | return this.apiSvc.getMyProfile(); 17 | } 18 | 19 | viewStars(){ 20 | this.navCtrl.push(StarredReposPage,{ 21 | 'login':'' 22 | }); 23 | } 24 | 25 | viewRepos(){ 26 | this.navCtrl.push(OwnedReposPage,{ 27 | 'login':'' 28 | }); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/user/user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | User 4 | 5 |
6 | New 7 | 10 |
11 | 14 |
15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 |
29 | 30 |
31 | 32 |

{{userProfile.name}}

33 | 34 |

{{userProfile.bio}}

35 |
36 | 37 | 61 | 62 | 63 | Info 64 | 65 | 66 | 67 | 68 | 69 | 70 |

Company

71 |

{{userProfile.company?userProfile.company:'Unknown'}}

72 |
73 | 74 | 75 | 76 | 77 |

Location

78 |

{{userProfile.location?userProfile.location:'Unknown'}}

79 |
80 | 81 | 82 | 83 | 84 |

Email

85 |

{{userProfile.email?userProfile.email:'Unknown'}}

86 |
87 |
88 | 89 |
90 | 91 | Pinned Repos 92 | 93 | 94 | 97 | 98 |
99 | 100 |
101 | 102 |
103 | -------------------------------------------------------------------------------- /src/pages/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import {UserPage} from './user'; 4 | import {ComponentsModule} from "../../components/components.module"; 5 | import {MePage} from "./me"; 6 | 7 | @NgModule({ 8 | declarations: [ 9 | UserPage, 10 | MePage, 11 | ], 12 | imports: [ 13 | ComponentsModule, 14 | IonicPageModule.forChild(UserPage), 15 | IonicPageModule.forChild(MePage), 16 | ], 17 | }) 18 | export class UserPageModule {} 19 | -------------------------------------------------------------------------------- /src/pages/user/user.scss: -------------------------------------------------------------------------------- 1 | page-user, page-me { 2 | div.meta{ 3 | padding:3rem 0 0; 4 | text-align: center; 5 | img.big-avatar{ 6 | border-radius: 1rem; 7 | width:30vw; 8 | height:30vw; 9 | background-color: #ffffff; 10 | max-width:150px; 11 | max-height: 150px; 12 | box-shadow: 0 0 10px 5px rgba(158, 158, 158, 0.1); 13 | } 14 | h2.name{ 15 | font-size: 3rem; 16 | } 17 | h3.login{ 18 | color: #9f9f9f; 19 | font-size: 2rem; 20 | } 21 | p.bio{ 22 | font-size: 1.4rem; 23 | } 24 | } 25 | div.statistic{ 26 | padding:5px 15px 25px; 27 | ion-col{ 28 | text-align: center; 29 | b{ 30 | font-size: 2rem; 31 | } 32 | } 33 | } 34 | 35 | ion-list{ 36 | ion-avatar{ 37 | text-align: center; 38 | ion-icon{ 39 | font-size: 3.6rem; 40 | } 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/pages/user/user.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {IonicPage, LoadingController, NavController, NavParams} from 'ionic-angular'; 3 | import {ApiService} from "../../services/api.service"; 4 | import {UserProfile} from "../../classes/user"; 5 | import {FollowersPage} from "../user-list/followers"; 6 | import {FollowingPage} from "../user-list/following"; 7 | import {RepoItem} from "../../classes/repo"; 8 | import {RepoPage} from "../repo/repo"; 9 | import {ToastService} from "../../services/toast.service"; 10 | import {SettingsPage} from "../settings/settings"; 11 | import {UpdateService} from "../../services/update.service"; 12 | import {StarredReposPage} from "../repo-list/starred-repos"; 13 | import {OwnedReposPage} from "../repo-list/owned-repos"; 14 | 15 | 16 | @IonicPage() 17 | @Component({ 18 | selector: 'page-user', 19 | templateUrl: 'user.html', 20 | }) 21 | export class UserPage { 22 | showSettingsButton:boolean=false; 23 | 24 | userProfile:UserProfile; 25 | togglingFollow:boolean=false; 26 | 27 | constructor( 28 | protected navCtrl: NavController, 29 | protected navParams: NavParams, 30 | protected loadingCtrl: LoadingController, 31 | protected toastSvc: ToastService, 32 | public updateSvc: UpdateService, 33 | protected apiSvc: ApiService, 34 | ) {} 35 | 36 | 37 | ionViewWillLoad(){ 38 | let loading=this.loadingCtrl.create({ 39 | spinner: 'dots', 40 | content: 'Loading' 41 | }); 42 | loading.present(); 43 | this.freshenUserProfile().catch(()=>{ 44 | this.navCtrl.pop(); 45 | }).then(()=>{ 46 | loading.dismiss(); 47 | }); 48 | } 49 | 50 | doRefresh(refresher){ 51 | this.freshenUserProfile().catch(()=>{ 52 | return; 53 | }).then(()=>{ 54 | refresher.complete(); 55 | }) 56 | } 57 | 58 | freshenUserProfile():Promise{ 59 | return this.getUserProfile().then((userProfile:UserProfile)=>{ 60 | this.userProfile=userProfile; 61 | }).catch(() => { 62 | this.toastSvc.toast('Fail to load user profile'); 63 | throw new Error(); 64 | }); 65 | } 66 | 67 | getUserProfile():Promise{ 68 | return this.apiSvc.getUserProfile(this.navParams.get('login')); 69 | } 70 | 71 | viewFollowers(){ 72 | this.navCtrl.push(FollowersPage,{ 73 | 'login':this.userProfile.login 74 | }); 75 | } 76 | 77 | viewFollowing(){ 78 | this.navCtrl.push(FollowingPage,{ 79 | 'login':this.userProfile.login 80 | }); 81 | } 82 | 83 | viewStars(){ 84 | this.navCtrl.push(StarredReposPage,{ 85 | 'login':this.userProfile.login 86 | }); 87 | } 88 | 89 | viewRepos(){ 90 | this.navCtrl.push(OwnedReposPage,{ 91 | 'login':this.userProfile.login 92 | }); 93 | } 94 | 95 | viewRepo(repo:RepoItem){ 96 | this.navCtrl.push(RepoPage,{ 97 | 'ownerLogin':repo.owner.login, 98 | 'name':repo.name 99 | }); 100 | } 101 | 102 | goSettingsPage(){ 103 | this.navCtrl.push(SettingsPage); 104 | } 105 | 106 | toggleFollow(){ 107 | if (this.userProfile.viewerIsFollowing) { 108 | this.togglingFollow=true; 109 | this.apiSvc.unfollowUser(this.userProfile.login).then(() => { 110 | this.userProfile.viewerIsFollowing=false; 111 | this.userProfile.followers.totalCount-=1; 112 | this.togglingFollow=false; 113 | }); 114 | }else{ 115 | this.togglingFollow=true; 116 | this.apiSvc.followUser(this.userProfile.login).then(() => { 117 | this.userProfile.viewerIsFollowing=true; 118 | this.userProfile.followers.totalCount+=1; 119 | this.togglingFollow=false; 120 | }); 121 | } 122 | } 123 | 124 | } 125 | 126 | -------------------------------------------------------------------------------- /src/pipes/markdown-to-html/markdown-to-html.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import marked from 'marked'; 3 | import * as highlight from 'highlightjs'; 4 | 5 | @Pipe({ 6 | name: 'markdownToHtml' 7 | }) 8 | export class MarkdownToHtmlPipe implements PipeTransform { 9 | 10 | transform(text:string,baseUrl:string): string { 11 | //fix image file paths 12 | let m = marked.setOptions({ 13 | highlight: function (code) { 14 | return highlight.highlightAuto(code).value; 15 | } 16 | }); 17 | let html=m.parse(text); 18 | let dom=(new DOMParser()).parseFromString(html,'text/html'); 19 | //fix 20 | let imgs=dom.getElementsByTagName('img'); 21 | for(let i=0;i 30 | let aTags=dom.getElementsByTagName('a'); 31 | for(let i=0;i=new EventEmitter(); 16 | // public accessToken:string; 17 | 18 | constructor( 19 | private inAppBrowser: InAppBrowser, 20 | private alertCtrl: AlertController, 21 | private storage: Storage, 22 | private apiSvc: ApiService, 23 | private http: Http, 24 | ) {} 25 | 26 | oAuth():Promise{ 27 | let state:string=''; 28 | let possible="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 29 | for (let i = 0; i < 10; i++){ 30 | state += possible.charAt(Math.floor(Math.random() * possible.length)); 31 | } 32 | const browser=this.inAppBrowser.create(`https://github.com/login/oauth/authorize?client_id=ca1ddf99e44c2e6cb787&redirect_uri=https%3A%2F%2Fawmleer.github.io%2FGitMug%2Foauth%2Fcallback.html&scope=user%20public_repo%20repo%20delete_repo%20notifications%20gist%20read:org%20read:public_key%20read:gpg_key&state=${state}`,'_blank'); 33 | let code:string; 34 | if (!window['cordova']) { 35 | return new Promise(resolve => { 36 | this.alertCtrl.create({ 37 | title: 'Please input the code parameter in the new page', 38 | inputs: [ 39 | { 40 | name: 'code', 41 | placeholder: 'Code' 42 | } 43 | ], 44 | buttons: [ 45 | { 46 | text: 'OK', 47 | handler: data => { 48 | this.obtainAccessToken(data.code,state).then(()=>{ 49 | resolve(); 50 | }); 51 | } 52 | } 53 | ] 54 | }).present(); 55 | }); 56 | 57 | // code=window.prompt('Please input the code parameter in the new page'); 58 | }else{ 59 | browser.show(); 60 | return new Promise((resolve, reject) => { 61 | let callbacksuccess=false; 62 | browser.on('loadstart').subscribe((event:InAppBrowserEvent)=>{ 63 | // console.log(event); 64 | // console.log(event.url); 65 | if (event.url.indexOf('https://awmleer.github.io/GitMug/oauth/callback.html')!=-1) { 66 | callbacksuccess=true; 67 | browser.close(); 68 | code=event.url.match(/code=\w+/)[0].replace('code=',''); 69 | let returnState=event.url.match(/state=\w+/)[0].replace('state=',''); 70 | if (returnState != state) { 71 | reject('State check fail'); 72 | } 73 | this.obtainAccessToken(code,state).then(() => { 74 | resolve(); 75 | }); 76 | } 77 | }); 78 | browser.on('exit').subscribe(() => { 79 | if (!callbacksuccess) { 80 | reject('Window closed'); 81 | } 82 | }); 83 | }); 84 | 85 | } 86 | 87 | } 88 | 89 | 90 | obtainAccessToken(code,state):Promise{ 91 | return this.http.post(`${CONST.githubUrl}/login/oauth/access_token`, 92 | `client_id=ca1ddf99e44c2e6cb787&client_secret=61335ff161c4c0fda8fbf68beb0e1bee55380414&code=${code}&redirect_uri=https%3A%2F%2Fawmleer.github.io%2FGitMug%2Foauth%2Fcallback.html&state=${state}`, 93 | { 94 | headers: new Headers({ 95 | 'Accept':'application/json', 96 | 'Content-Type':'application/x-www-form-urlencoded' 97 | }) 98 | } 99 | ).toPromise().then((response:Response)=>{ 100 | let data = response.json(); 101 | if (data['error']) { 102 | throw new Error(data['error']); 103 | } 104 | let accessToken=data['access_token']; 105 | this.apiSvc.setAccessToken(accessToken); 106 | this.user.accessToken=accessToken; 107 | this.userUpdated.emit(); 108 | this.saveUserData(); 109 | this.freshUser(); 110 | return; 111 | }); 112 | } 113 | 114 | saveUserData():Promise{ 115 | return this.storage.set('user',this.user); 116 | } 117 | 118 | fetchUserDataFromStorage():Promise{ 119 | return this.storage.get('user').then((user:UserStorage)=>{ 120 | if (user) { 121 | this.user.login=user.login; 122 | this.user.name=user.name; 123 | this.user.accessToken=user.accessToken; 124 | this.userUpdated.emit(); 125 | this.apiSvc.setAccessToken(this.user.accessToken); 126 | } 127 | return; 128 | }); 129 | } 130 | 131 | 132 | freshUser():Promise{ 133 | if (!this.apiSvc.getAccessToken()) { 134 | throw new Error('No token'); 135 | } 136 | return this.apiSvc.getViewer().then(user=>{ 137 | this.user.login=user.login; 138 | this.user.name=user.name; 139 | this.userUpdated.emit(); 140 | this.saveUserData(); 141 | }); 142 | } 143 | 144 | 145 | logout():Promise{ 146 | this.user=new UserStorage(); 147 | this.userUpdated.emit(); 148 | return this.saveUserData(); 149 | } 150 | 151 | 152 | 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/services/api.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import { request, GraphQLClient } from 'graphql-request' 3 | import {CONST} from "../app/const"; 4 | import {Headers, Http, Response} from "@angular/http"; 5 | import 'rxjs/add/operator/toPromise'; 6 | import {User, UserItem, UserProfile, userProfileSchema, userSchema, userItemSchema} from "../classes/user"; 7 | import {NodesPage, nodesPageSchema} from "../classes/nodes-page"; 8 | import {RepoDetail, repoDetailSchema, RepoItem, repoItemSchema, RepoParam} from "../classes/repo"; 9 | import * as moment from 'moment'; 10 | import {Content} from "../classes/content"; 11 | 12 | @Injectable() 13 | export class ApiService { 14 | private accessToken:string; 15 | private restfulHeaders:Headers; 16 | client:GraphQLClient; 17 | 18 | constructor( 19 | private http: Http 20 | ) { 21 | this.client=new GraphQLClient(CONST.graphqlUrl); 22 | } 23 | 24 | getAccessToken():string{ 25 | return this.accessToken; 26 | } 27 | 28 | setAccessToken(newToken:string){ 29 | this.accessToken=newToken; 30 | if (this.accessToken) { 31 | this.client=new GraphQLClient(CONST.graphqlUrl, { headers: {'Authorization':`bearer ${this.accessToken}`} }); 32 | this.restfulHeaders=new Headers({'Authorization':`token ${this.accessToken}`}) 33 | }else{ 34 | this.client=new GraphQLClient(CONST.graphqlUrl); 35 | this.restfulHeaders=new Headers({}); 36 | } 37 | } 38 | 39 | private restfulGet(url:string,params?:object):Promise{ 40 | return this.http.get(url,{ 41 | params:params, 42 | headers:this.restfulHeaders 43 | }).toPromise(); 44 | } 45 | 46 | 47 | private userQueryString(login?:string):string{ 48 | return login?`user(login: "${login}")`:'viewer'; 49 | } 50 | 51 | private afterCursorString(cursor?:string):string{ 52 | return cursor?`,after:"${cursor}"`:''; 53 | } 54 | 55 | 56 | getReceivedEvents(login):Promise{ 57 | return this.restfulGet(CONST.apiUrl+`/users/${login}/received_events`).then((response)=>{ 58 | return response.json(); 59 | }); 60 | } 61 | 62 | 63 | getViewer():Promise{ 64 | const query=`{ 65 | viewer ${userSchema} 66 | }`; 67 | return this.client.request(query).then(data=>{ 68 | return data['viewer']; 69 | }); 70 | } 71 | 72 | 73 | 74 | getMyProfile():Promise{ 75 | const query=`{ 76 | viewer ${userProfileSchema} 77 | }`; 78 | return this.client.request(query).then(data=>{ 79 | return data['viewer']; 80 | }); 81 | } 82 | 83 | getUserProfile(login:string):Promise{ 84 | const query=`{ 85 | user(login: "${login}") ${userProfileSchema} 86 | }`; 87 | return this.client.request(query).then(data=>{ 88 | return data['user']; 89 | }); 90 | } 91 | 92 | 93 | getFollowers(login:string,cursor?:string):Promise>{ 94 | const query=`{ 95 | user(login: "${login}") { 96 | followers(first:30 ${this.afterCursorString(cursor)} ) ${nodesPageSchema(userItemSchema)} 97 | } 98 | }`; 99 | return this.client.request(query).then(data=>{ 100 | return data['user']['followers']; 101 | }); 102 | } 103 | 104 | getFollowing(login:string,cursor?:string):Promise>{ 105 | const query=`{ 106 | user(login: "${login}") { 107 | following(first:30 ${this.afterCursorString(cursor)} ) ${nodesPageSchema(userItemSchema)} 108 | } 109 | }`; 110 | return this.client.request(query).then(data=>{ 111 | return data['user']['following']; 112 | }); 113 | } 114 | 115 | getStargazers(repo:RepoParam,cursor?:string):Promise>{ 116 | const query=`{ 117 | repository(owner: "${repo.owner}", name: "${repo.name}") { 118 | stargazers(first:30 ${this.afterCursorString(cursor)} ) ${nodesPageSchema(userItemSchema)} 119 | } 120 | }`; 121 | return this.client.request(query).then(data=>{ 122 | return data['repository']['stargazers']; 123 | }); 124 | } 125 | 126 | getWatchers(repo:RepoParam,cursor?:string):Promise>{ 127 | const query=`{ 128 | repository(owner: "${repo.owner}", name: "${repo.name}") { 129 | watchers(first:30 ${this.afterCursorString(cursor)} ) ${nodesPageSchema(userItemSchema)} 130 | } 131 | }`; 132 | return this.client.request(query).then(data=>{ 133 | return data['repository']['watchers']; 134 | }); 135 | } 136 | 137 | getStarredRepos(login:''|string,cursor?:string):Promise>{ 138 | const query=`{ 139 | ${this.userQueryString(login)} { 140 | starredRepositories(first:30 ${this.afterCursorString(cursor)} , orderBy:{ 141 | field:STARRED_AT, 142 | direction:DESC 143 | }) ${nodesPageSchema(repoItemSchema)} 144 | } 145 | }`; 146 | return this.client.request(query).then(data=>{ 147 | if (login) { 148 | return data['user']['starredRepositories']; 149 | }else{ 150 | return data['viewer']['starredRepositories']; 151 | } 152 | }); 153 | } 154 | 155 | getOwnedRepos(login:''|string,cursor?:string):Promise>{ 156 | const query=`{ 157 | ${this.userQueryString(login)} { 158 | repositories(first:30 ${this.afterCursorString(cursor)} , orderBy:{ 159 | field:UPDATED_AT, 160 | direction:DESC 161 | }) ${nodesPageSchema(repoItemSchema)} 162 | } 163 | }`; 164 | return this.client.request(query).then(data=>{ 165 | if (login) { 166 | return data['user']['repositories']; 167 | }else{ 168 | return data['viewer']['repositories']; 169 | } 170 | }); 171 | } 172 | 173 | getRepoForks(repo:RepoParam,cursor?:string):Promise>{ 174 | const query=`{ 175 | repository(owner: "${repo.owner}", name: "${repo.name}") { 176 | forks(first:30 ${this.afterCursorString(cursor)} ) ${nodesPageSchema(repoItemSchema)} 177 | } 178 | }`; 179 | return this.client.request(query).then(data=>{ 180 | return data['repository']['forks']; 181 | }); 182 | } 183 | 184 | getHotRepos():Promise>{ 185 | //TODO use api V4 186 | return this.restfulGet(CONST.apiUrl+'/search/repositories',{ 187 | 'q':`created:>${moment().subtract(1,'months').format('YYYY-MM-DD')}`, 188 | 'sort':'stars', 189 | 'order':'desc' 190 | }).then((response:Response)=>{ 191 | let items = response.json()['items']; 192 | let repos:RepoItem[]=[]; 193 | for(let i=0; i= 30) break; 195 | let item=items[i]; 196 | repos.push({ 197 | owner:{ 198 | login:item['owner']['login'] 199 | }, 200 | name:item['name'], 201 | description:item['description'], 202 | stargazers:{ 203 | totalCount:item['stargazers_count'] 204 | }, 205 | forks:{ 206 | totalCount:item['forks'] 207 | }, 208 | isPrivate:null, 209 | primaryLanguage:null 210 | }); 211 | } 212 | return { 213 | totalCount:repos.length, 214 | pageInfo:{ 215 | hasNextPage: false, 216 | endCursor: null 217 | }, 218 | nodes:repos 219 | }; 220 | }); 221 | } 222 | 223 | searchRepos(searchText:string):Promise>{ 224 | const query=`{ 225 | search(type: REPOSITORY, first: 30, query: "${searchText}") { 226 | totalCount: repositoryCount 227 | pageInfo{ 228 | hasNextPage 229 | endCursor 230 | } 231 | nodes{ 232 | ... on Repository ${repoItemSchema} 233 | } 234 | } 235 | }`; 236 | return this.client.request(query).then(data=>{ 237 | return data['search']; 238 | }); 239 | 240 | } 241 | 242 | 243 | getRepo(ownerLogin:string,name:string):Promise{//TODO refactor to RepoParam class 244 | const query=`{ 245 | repository(owner: "${ownerLogin}", name: "${name}") ${repoDetailSchema} 246 | }`; 247 | return this.client.request(query).then(data=>{ 248 | return data['repository']; 249 | }) 250 | } 251 | 252 | getRepoReadme(repo:RepoParam):Promise{ 253 | return this.restfulGet(CONST.rawUrl+`/${repo.owner}/${repo.name}/master/README.md`).then((response:Response)=>{ 254 | return response.text(); 255 | }).catch(()=>{ 256 | return null; 257 | }); 258 | } 259 | 260 | starMutation(action:'add'|'remove',starrableId:string):Promise{ 261 | let mutationId:string=''; 262 | let possible="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 263 | for (let i = 0; i < 10; i++){ 264 | mutationId += possible.charAt(Math.floor(Math.random() * possible.length)); 265 | } 266 | const query=`mutation{ 267 | ${action}Star(input:{ 268 | clientMutationId: "${mutationId}" 269 | starrableId: "${starrableId}" 270 | }){ 271 | clientMutationId 272 | } 273 | }`; 274 | return this.client.request(query).then(data=>{ 275 | if (data[`${action}Star`]['clientMutationId']!=mutationId) { 276 | throw new Error('ClientMutationId check failed'); 277 | } 278 | }); 279 | } 280 | 281 | addStar(starrableId:string):Promise{ 282 | return this.starMutation('add',starrableId); 283 | } 284 | 285 | removeStar(starrabledId:string):Promise{ 286 | return this.starMutation('remove',starrabledId); 287 | } 288 | 289 | 290 | followUser(login:string):Promise{ 291 | let headers=new Headers(this.restfulHeaders); 292 | headers.set('Content-Length','zero'); 293 | return this.http.put(CONST.apiUrl+`/user/following/${login}`,'',{ 294 | headers:headers 295 | }).toPromise().then(() => {}); 296 | } 297 | 298 | unfollowUser(login:string):Promise{ 299 | return this.http.delete(CONST.apiUrl+`/user/following/${login}`,{ 300 | headers:this.restfulHeaders 301 | }).toPromise().then(() => {}); 302 | } 303 | 304 | getContents(repoParam:RepoParam, path:string):Promise{ 305 | return this.restfulGet(CONST.apiUrl+`/repos/${repoParam.owner}/${repoParam.name}/contents/${path}`).then((response) => { 306 | return response.json(); 307 | }); 308 | } 309 | 310 | getFileContent(repo:RepoParam, path:string):Promise{ 311 | return this.restfulGet(CONST.rawUrl+`/${repo.owner}/${repo.name}/master/${path}`).then((response)=>{ 312 | return response.text(); 313 | }).catch(()=>{ 314 | return null; 315 | }); 316 | } 317 | 318 | // getRepoReadmeUrl(repo:RepoParam):Promise{ 319 | // return this.restfulGet(CONST.apiUrl+`/repos/${repo.owner}/${repo.name}/readme`).then((response:Response)=>{ 320 | // return response.json()['html_url']; 321 | // }).catch(()=>{ 322 | // return null; 323 | // }); 324 | // } 325 | 326 | } 327 | -------------------------------------------------------------------------------- /src/services/toast.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from "@angular/core"; 2 | import {Toast, ToastController} from "ionic-angular"; 3 | 4 | 5 | @Injectable() 6 | export class ToastService { 7 | constructor( 8 | private toastCtrl: ToastController 9 | ){} 10 | 11 | t: Toast; 12 | 13 | toast(message:string,duration:number=2000):void{ 14 | if (this.t) { 15 | this.t.dismiss(); 16 | } 17 | this.t=this.toastCtrl.create({ 18 | message: message, 19 | duration: duration, 20 | showCloseButton: true, 21 | closeButtonText: 'OK', 22 | position: 'bottom' 23 | }); 24 | this.t.present(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/services/update.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Http, Response} from '@angular/http'; 3 | import 'rxjs/add/operator/toPromise'; 4 | import {Version} from "../classes/version"; 5 | import {CONST} from "../app/const"; 6 | 7 | 8 | @Injectable() 9 | export class UpdateService { 10 | latestVersion:Version; 11 | hasNewVersion:boolean=false; 12 | 13 | constructor( 14 | private http: Http 15 | ) {} 16 | 17 | checkUpdate():Promise{ 18 | return this.http.get(CONST.rawUrl+`/awmleer/GitMug/release/version.json?t=${Date.now()}`).toPromise().then((response:Response)=>{ 19 | this.latestVersion=response.json(); 20 | this.hasNewVersion=( 21 | (this.latestVersion.major > CONST.version.major)|| 22 | (this.latestVersion.major==CONST.version.major&&this.latestVersion.minor>CONST.version.minor)|| 23 | (this.latestVersion.major==CONST.version.major&&this.latestVersion.minor==CONST.version.minor&&(this.latestVersion.patch>CONST.version.patch)) 24 | ); 25 | return this.hasNewVersion; 26 | }); 27 | } 28 | 29 | get androidDownloadLink(){ 30 | let versionString=`${this.latestVersion.major}.${this.latestVersion.minor}.${this.latestVersion.patch}`; 31 | return `https://github.com/awmleer/GitMug/releases/download/v${versionString}/GitMug${versionString}.apk`; 32 | } 33 | 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/theme/variables.scss: -------------------------------------------------------------------------------- 1 | // Ionic Variables and Theming. For more info, please see: 2 | // http://ionicframework.com/docs/theming/ 3 | 4 | // Font path is used to include ionicons, 5 | // roboto, and noto sans fonts 6 | $font-path: "../assets/fonts"; 7 | 8 | 9 | // The app direction is used to include 10 | // rtl styles in your app. For more info, please see: 11 | // http://ionicframework.com/docs/theming/rtl-support/ 12 | $app-direction: ltr; 13 | 14 | 15 | @import "ionic.globals"; 16 | 17 | 18 | // Shared Variables 19 | // -------------------------------------------------- 20 | // To customize the look and feel of this app, you can override 21 | // the Sass variables found in Ionic's source scss files. 22 | // To view all the possible Ionic variables, see: 23 | // http://ionicframework.com/docs/theming/overriding-ionic-variables/ 24 | 25 | 26 | 27 | 28 | // Named Color Variables 29 | // -------------------------------------------------- 30 | // Named colors makes it easy to reuse colors on various components. 31 | // It's highly recommended to change the default colors 32 | // to match your app's branding. Ionic uses a Sass map of 33 | // colors so you can add, rename and remove colors as needed. 34 | // The "primary" color is the only required color in the map. 35 | 36 | $colors: ( 37 | primary: #488aff, 38 | secondary: #32db64, 39 | danger: #f53d3d, 40 | light: #f4f4f4, 41 | dark: #222, 42 | gray: #939393 43 | ); 44 | 45 | 46 | $link-color: #000; 47 | 48 | $toolbar-background: rgba(0,0,0,0); 49 | $toolbar-border-color: rgba(0,0,0,0); 50 | $toolbar-text-color: #000; 51 | $toolbar-md-title-text-color: $toolbar-text-color; 52 | $toolbar-ios-title-text-color: $toolbar-text-color; 53 | $toolbar-ios-border-color: $toolbar-border-color; 54 | $toolbar-ios-title-font-weight:normal; 55 | $toolbar-ios-button-color: #000; 56 | 57 | $tabs-tab-color-active: #000; 58 | $tabs-md-tab-color-inactive: #969696; 59 | $tabs-ios-border-color: rgba(0,0,0,0.1); 60 | $tabs-ios-tab-min-height: 56px; 61 | $tabs-md-tab-min-height: 4.8rem; 62 | $tabs-md-tab-icon-size: 3.2rem; 63 | 64 | $card-box-shadow: 0 0 10px 5px rgba(158, 158, 158, 0.1); 65 | $card-ios-box-shadow: $card-box-shadow; 66 | $card-md-box-shadow: $card-box-shadow; 67 | $card-ios-border-radius: 5px; 68 | $card-md-border-radius: 5px; 69 | 70 | $refresher-border-color: rgba(0,0,0,0); 71 | $refresher-icon-font-size: 20px; 72 | 73 | $button-md-text-transform: none; 74 | 75 | $list-border-color: #dadada; 76 | 77 | 78 | // App Theme 79 | // -------------------------------------------------- 80 | // Ionic apps can have different themes applied, which can 81 | // then be future customized. This import comes last 82 | // so that the above variables are used and Ionic's 83 | // default are overridden. 84 | 85 | @import "ionic.theme.default"; 86 | 87 | 88 | // Ionicons 89 | // -------------------------------------------------- 90 | // The premium icon font for Ionic. For more info, please see: 91 | // http://ionicframework.com/docs/ionicons/ 92 | 93 | @import "ionic.ionicons"; 94 | 95 | 96 | // Fonts 97 | // -------------------------------------------------- 98 | 99 | @import "roboto"; 100 | @import "noto-sans"; 101 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "dom", 9 | "es2015" 10 | ], 11 | "module": "es2015", 12 | "moduleResolution": "node", 13 | "sourceMap": true, 14 | "target": "es5" 15 | }, 16 | "include": [ 17 | "src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "node_modules" 21 | ], 22 | "compileOnSave": false, 23 | "atom": { 24 | "rewriteTsconfig": false 25 | } 26 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-duplicate-variable": true, 4 | "no-unused-variable": [ 5 | true 6 | ] 7 | }, 8 | "rulesDirectory": [ 9 | "node_modules/tslint-eslint-rules/dist/rules" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "major":0, 3 | "minor":2, 4 | "patch":0 5 | } 6 | --------------------------------------------------------------------------------