├── .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 | 
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 |
51 |
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 |
87 |
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 | 0" class="widget">
12 |
13 | {{repo.stargazers.totalCount<1000?repo.stargazers.totalCount:(repo.stargazers.totalCount/1000).toFixed(1)+'k'}}
14 |
15 | 0" class="widget">
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 |
2 |
3 |
4 |
5 |
6 |
7 |
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 |
6 | Sign in with
7 |
8 |
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 |
17 |
18 | {{content.name}}
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{content.name}}
26 |
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 | =0">
24 |
25 |
26 | {{totalCount}} Repos
27 |
28 |
29 |
30 |
31 |
32 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
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 | View Code
100 |
101 |
102 |
103 |
104 |
105 |
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 | Update now
24 |
25 |
26 | GitHub · awmleer/GitMug
27 |
28 |
Log out
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 | =0">
20 |
21 |
22 | {{totalCount}} Users
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{user.name}} {{user.login}}
31 | {{user.bio}}
32 |
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 |
8 |
9 |
10 |
11 |
12 | {{userProfile.viewerIsFollowing?'Unfollow':'Follow'}}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
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 |
0">
90 |
91 | Pinned Repos
92 |
93 |
94 |
95 |
96 |
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 |
--------------------------------------------------------------------------------