├── .gitignore
├── .idea
├── .gitignore
├── artifacts
│ ├── super_github_v1_0_0_alpha01.xml
│ ├── super_github_v1_0_3.xml
│ └── super_github_v1_0_4.xml
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── jarRepositories.xml
├── libraries-with-intellij-classes.xml
├── markdown-navigator-enh.xml
├── misc.xml
├── modules
│ ├── super-github.main.iml
│ └── super-github.test.iml
├── runConfigurations.xml
├── super-github.iml
└── vcs.xml
├── README.md
├── assets
└── summary.svg
├── build.gradle
├── cover.jpeg
├── extras
├── auto_comment.gif
└── profile-summary-demo.gif
├── gpm.json
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── logo.png
├── settings.gradle
└── src
├── main
├── kotlin
│ └── com
│ │ └── theapache64
│ │ └── supergithub
│ │ ├── data
│ │ ├── remote
│ │ │ └── repos
│ │ │ │ └── GetRepoResponse.kt
│ │ └── repositories
│ │ │ └── GitHubRepo.kt
│ │ ├── features
│ │ ├── BaseFeature.kt
│ │ ├── FoldableContent.kt
│ │ ├── ProfileSummary.kt
│ │ ├── RepoCreatedAt.kt
│ │ └── ReviewComment.kt
│ │ ├── main.kt
│ │ └── utils
│ │ ├── PathUtils.kt
│ │ ├── StringUtils.kt
│ │ ├── Svgs.kt
│ │ └── TimeUtils.kt
└── resources
│ ├── icons
│ ├── icon128.png
│ ├── icon16.png
│ └── icon48.png
│ └── manifest.json
└── test
└── kotlin
└── com
└── theapache64
└── supergithub
├── data
└── repositories
│ └── GitHubRepoTest.kt
└── utils
├── PathUtilsTest.kt
├── StringUtilsTest.kt
└── TimeUtilsTest.kt
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/kotlin,intellij,gradle
3 | # Edit at https://www.gitignore.io/?templates=kotlin,intellij,gradle
4 |
5 | ### Intellij ###
6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
8 |
9 | # User-specific stuff
10 | .idea/**/workspace.xml
11 | .idea/**/tasks.xml
12 | .idea/**/usage.statistics.xml
13 | .idea/**/dictionaries
14 | .idea/**/shelf
15 |
16 | # Generated files
17 | .idea/**/contentModel.xml
18 |
19 | # Sensitive or high-churn files
20 | .idea/**/dataSources/
21 | .idea/**/dataSources.ids
22 | .idea/**/dataSources.local.xml
23 | .idea/**/sqlDataSources.xml
24 | .idea/**/dynamic.xml
25 | .idea/**/uiDesigner.xml
26 | .idea/**/dbnavigator.xml
27 |
28 | # Gradle
29 | .idea/**/gradle.xml
30 | .idea/**/libraries
31 |
32 | # Gradle and Maven with auto-import
33 | # When using Gradle or Maven with auto-import, you should exclude module files,
34 | # since they will be recreated, and may cause churn. Uncomment if using
35 | # auto-import.
36 | # .idea/modules.xml
37 | # .idea/*.iml
38 | # .idea/modules
39 | # *.iml
40 | # *.ipr
41 |
42 | # CMake
43 | cmake-build-*/
44 |
45 | # Mongo Explorer plugin
46 | .idea/**/mongoSettings.xml
47 |
48 | # File-based project format
49 | *.iws
50 |
51 | # IntelliJ
52 | out/
53 |
54 | # mpeltonen/sbt-idea plugin
55 | .idea_modules/
56 |
57 | # JIRA plugin
58 | atlassian-ide-plugin.xml
59 |
60 | # Cursive Clojure plugin
61 | .idea/replstate.xml
62 |
63 | # Crashlytics plugin (for Android Studio and IntelliJ)
64 | com_crashlytics_export_strings.xml
65 | crashlytics.properties
66 | crashlytics-build.properties
67 | fabric.properties
68 |
69 | # Editor-based Rest Client
70 | .idea/httpRequests
71 |
72 | # Android studio 3.1+ serialized cache file
73 | .idea/caches/build_file_checksums.ser
74 |
75 | ### Intellij Patch ###
76 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
77 |
78 | # *.iml
79 | # modules.xml
80 | # .idea/misc.xml
81 | # *.ipr
82 |
83 | # Sonarlint plugin
84 | .idea/**/sonarlint/
85 |
86 | # SonarQube Plugin
87 | .idea/**/sonarIssues.xml
88 |
89 | # Markdown Navigator plugin
90 | .idea/**/markdown-navigator.xml
91 | .idea/**/markdown-navigator/
92 |
93 | ### Kotlin ###
94 | # Compiled class file
95 | *.class
96 |
97 | # Log file
98 | *.log
99 |
100 | # BlueJ files
101 | *.ctxt
102 |
103 | # Mobile Tools for Java (J2ME)
104 | .mtj.tmp/
105 |
106 | # Package Files #
107 | *.jar
108 | *.war
109 | *.nar
110 | *.ear
111 | *.zip
112 | *.tar.gz
113 | *.rar
114 |
115 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
116 | hs_err_pid*
117 |
118 | ### Gradle ###
119 | .gradle
120 | build/
121 |
122 | # Ignore Gradle GUI config
123 | gradle-app.setting
124 |
125 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
126 | !gradle-wrapper.jar
127 |
128 | # Cache of project
129 | .gradletasknamecache
130 |
131 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
132 | # gradle/wrapper/gradle-wrapper.properties
133 |
134 | ### Gradle Patch ###
135 | **/build/
136 |
137 | # End of https://www.gitignore.io/api/kotlin,intellij,gradle
138 | SecretConstants.kt
139 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/artifacts/super_github_v1_0_0_alpha01.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | $PROJECT_DIR$/build/libs
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/artifacts/super_github_v1_0_3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | $PROJECT_DIR$/build/libs
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/artifacts/super_github_v1_0_4.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | $PROJECT_DIR$/build/libs
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/libraries-with-intellij-classes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
65 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator-enh.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/modules/super-github.main.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | COMPILATION_AND_SOURCE_SET_HOLDER
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 |
--------------------------------------------------------------------------------
/.idea/modules/super-github.test.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | super-github.main
8 |
9 | COMPILATION_AND_SOURCE_SET_HOLDER
10 |
11 | browserTest|super-github:test|js
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 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/super-github.iml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # :rocket: super-github
4 |
5 | Enhance your GitHub experience
6 |
7 |
8 | ## Install
9 |
10 | Open an issue, and I'll publish it in Chrome store 😉
11 |
12 | ## :paintbrush: Features
13 |
14 | ### :baby_bottle: Repo Birth Date
15 | Ever wondered when's your favorite repo born ?
16 |
17 | 
18 |
19 | The emoji specifies project started time.
20 |
21 | If the time
22 |
23 | - between 12AM and 5AM : 🌙
24 | - between 5AM and 10AM : 🌞
25 | - between 10AM and 3PM : ☀️
26 | - between 3PM and 7PM : 🌥
27 | - after 7PM : 🌓
28 |
29 | ### :bar_chart: Profile Summary
30 |
31 | 
32 |
33 | Click on the chart icon to view profile summary.
34 |
35 | 
36 |
37 | It also supports organizations too.
38 |
39 | 
40 |
41 | Thanks to [@tipsy](https://github.com/tipsy/profile-summary-for-github) for his hard work. :hugs:
42 |
43 | ### 👌 Auto Comment (PR Approval)
44 |
45 | 
46 |
47 | Every time you click on the `Approve` radio button, your comment box will be filled with a random LGTM message.
48 |
49 | ### 🙏 Foldable Code
50 |
51 | Helps you write foldable code blocks when you paste code/stacktrace into a comment body.
52 | Watch the demo [here](https://twitter.com/theapache64/status/1365004116003446793)
53 |
54 | ## :ballot_box_with_check: TODO
55 |
56 | - [x] Repo birth date
57 | - [x] Profile summary
58 | - [x] Random `LGTM` message for PR approval
59 | - [x] Foldable code
60 | - [ ] Do you have a feature in mind? Let's discuss it [here](https://github.com/theapache64/super-github/issues/new?labels=enhancement)
61 |
62 | ## :writing_hand: Author
63 |
64 | - theapache64
--------------------------------------------------------------------------------
/assets/summary.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
66 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.jetbrains.kotlin.js' version '1.5.0'
3 | }
4 |
5 | group 'com.theapache64'
6 | version 'v1.0.4'
7 |
8 | repositories {
9 | mavenCentral()
10 | }
11 |
12 | dependencies {
13 | implementation "org.jetbrains.kotlin:kotlin-stdlib-js"
14 | testImplementation "org.jetbrains.kotlin:kotlin-test-js"
15 |
16 | // Kotlinx Coroutines Core:Coroutines support libraries for Kotlin
17 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.7'
18 | }
19 |
20 | kotlin.target.browser {}
--------------------------------------------------------------------------------
/cover.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theapache64/super-github/3460987708952b27eb38df1aed829f65547b4824/cover.jpeg
--------------------------------------------------------------------------------
/extras/auto_comment.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theapache64/super-github/3460987708952b27eb38df1aed829f65547b4824/extras/auto_comment.gif
--------------------------------------------------------------------------------
/extras/profile-summary-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theapache64/super-github/3460987708952b27eb38df1aed829f65547b4824/extras/profile-summary-demo.gif
--------------------------------------------------------------------------------
/gpm.json:
--------------------------------------------------------------------------------
1 | {
2 | "added": [
3 | {
4 | "id": 1,
5 | "type": "testImplementation",
6 | "installed_name": "junit",
7 | "gpm_dep": {
8 | "artifact_id": "junit",
9 | "default_type": "implementation",
10 | "docs": "https://mvnrepository.com/artifact/junit/junit",
11 | "get_from": "Central",
12 | "group_id": "junit",
13 | "name": "JUnit",
14 | "description": "JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck."
15 | }
16 | },
17 | {
18 | "id": 2,
19 | "type": "implementation",
20 | "installed_name": "moshi",
21 | "gpm_dep": {
22 | "artifact_id": "moshi",
23 | "default_type": "implementation",
24 | "docs": "https://mvnrepository.com/artifact/com.squareup.moshi/moshi",
25 | "get_from": "Central",
26 | "group_id": "com.squareup.moshi",
27 | "name": "Moshi",
28 | "description": "Moshi"
29 | }
30 | },
31 | {
32 | "id": 3,
33 | "type": "implementation",
34 | "installed_name": "coroutines",
35 | "gpm_dep": {
36 | "artifact_id": "kotlinx-coroutines-core-common",
37 | "default_type": "implementation",
38 | "docs": "https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-common",
39 | "get_from": "Central",
40 | "group_id": "org.jetbrains.kotlinx",
41 | "name": "Kotlinx Coroutines Core",
42 | "description": "Coroutines support libraries for Kotlin"
43 | }
44 | },
45 | {
46 | "id": 4,
47 | "type": "implementation",
48 | "installed_name": "coroutines js",
49 | "gpm_dep": {
50 | "artifact_id": "kotlinx-coroutines-core-js",
51 | "default_type": "implementation",
52 | "docs": "https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js",
53 | "get_from": "Central",
54 | "group_id": "org.jetbrains.kotlinx",
55 | "name": "Kotlinx Coroutines Core",
56 | "description": "Coroutines support libraries for Kotlin"
57 | }
58 | }
59 | ]
60 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theapache64/super-github/3460987708952b27eb38df1aed829f65547b4824/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu May 21 17:27:54 IST 2020
2 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-all.zip
3 | distributionBase=GRADLE_USER_HOME
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin or MSYS, switch paths to Windows format before running java
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=`expr $i + 1`
158 | done
159 | case $i in
160 | 0) set -- ;;
161 | 1) set -- "$args0" ;;
162 | 2) set -- "$args0" "$args1" ;;
163 | 3) set -- "$args0" "$args1" "$args2" ;;
164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=`save "$@"`
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | exec "$JAVACMD" "$@"
184 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theapache64/super-github/3460987708952b27eb38df1aed829f65547b4824/logo.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'super-github'
2 |
3 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/theapache64/supergithub/data/remote/repos/GetRepoResponse.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.data.remote.repos
2 |
3 | data class GetRepoResponse(
4 | val created_at: String? // 2019-02-11T04:03:06Z
5 | )
--------------------------------------------------------------------------------
/src/main/kotlin/com/theapache64/supergithub/data/repositories/GitHubRepo.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.data.repositories
2 |
3 | import com.theapache64.supergithub.data.remote.repos.GetRepoResponse
4 | import kotlinx.coroutines.await
5 | import kotlinx.browser.window
6 |
7 | object GitHubRepo {
8 |
9 | private const val BASE_URL = "https://api.github.com"
10 |
11 | suspend fun getRepo(repoPath: String): GetRepoResponse {
12 | val url = "$BASE_URL/repos/$repoPath"
13 | return window.fetch(url)
14 | .await()
15 | .json()
16 | .await()
17 | .unsafeCast()
18 | }
19 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/theapache64/supergithub/features/BaseFeature.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.features
2 |
3 | interface BaseFeature {
4 | suspend fun onGitHubPageLoaded()
5 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/theapache64/supergithub/features/FoldableContent.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.features
2 |
3 | import com.theapache64.supergithub.utils.foldableSvg
4 | import kotlinx.browser.document
5 | import kotlinx.browser.window
6 | import org.w3c.dom.HTMLTextAreaElement
7 | import org.w3c.dom.events.Event
8 | import org.w3c.dom.events.EventListener
9 |
10 | class FoldableContent : BaseFeature {
11 | companion object {
12 | private val COMMENTABLE_PAGE_URL_REGEX =
13 | "https:\\/\\/github\\.com\\/.+?\\/.+?\\/(issues|pull|issues)\\/(new|\\d+.*)".toRegex()
14 |
15 | private const val TARGET_CONTAINER_EXISTING_ISSUE_SELECTOR =
16 | "#issuecomment-new > div.border-0.border-md.timeline-comment.timeline-comment--caret > form > fieldset > tab-container > div.comment-form-head.tabnav.d-flex.flex-justify-between.mb-2.p-0.tabnav--responsive.flex-column.border-bottom-0.mb-0.mb-lg-2.flex-items-stretch.border-lg-bottom.color-border-primary.flex-lg-items-center.flex-lg-row > markdown-toolbar > div.flex-nowrap.d-none.d-md-inline-block.mr-3"
17 |
18 | private const val TARGET_CONTAINER_NEW_ISSUE_SELECTOR =
19 | "#new_issue > div > div > div.flex-shrink-0.col-12.col-md-9.mb-4.mb-md-0 > div > div.timeline-comment.color-bg-canvas.hx_comment-box--tip > div > tab-container > div.comment-form-head.tabnav.d-flex.flex-justify-between.mb-2.p-0.tabnav--responsive.flex-column.border-bottom-0.mb-0.mb-lg-2.flex-items-stretch.border-lg-bottom.color-border-primary.flex-lg-items-center.flex-lg-row > markdown-toolbar > div.flex-nowrap.d-none.d-md-inline-block.mr-3"
20 | private val MD_FOLDABLE by lazy {
21 | """
22 |
23 | $foldableSvg
24 |
25 | """
26 | }
27 |
28 | private val FOLDABLE_TEMPLATE = """
29 |
30 | SUMMARY_GOES_HERE
31 |
32 | ```
33 | PASTE_CONTENT_HERE
34 | ```
35 |
36 | """.trimIndent()
37 |
38 | private const val TEXTAREA_EXISTING_SELECTOR = "#new_comment_field"
39 | private const val TEXTAREA_NEW_SELECTOR = "#issue_body"
40 |
41 |
42 | }
43 |
44 | override suspend fun onGitHubPageLoaded() {
45 | val url = window.location.href
46 | val isCommentablePage = COMMENTABLE_PAGE_URL_REGEX.matches(url)
47 |
48 |
49 | println("Is commentable ('$url') : $isCommentablePage")
50 |
51 | if (isCommentablePage) {
52 | document
53 | .querySelector(TARGET_CONTAINER_EXISTING_ISSUE_SELECTOR)
54 | .let { container ->
55 | container ?: document.querySelector(TARGET_CONTAINER_NEW_ISSUE_SELECTOR)
56 | }
57 | ?.let { container ->
58 | container.innerHTML = MD_FOLDABLE + container.innerHTML
59 |
60 | // Add click listener
61 | document.querySelector("md-foldable")
62 | ?.addEventListener(
63 | "click",
64 | object : EventListener {
65 | override fun handleEvent(event: Event) {
66 | // add content
67 | (document.querySelector(TEXTAREA_EXISTING_SELECTOR) ?: document.querySelector(TEXTAREA_NEW_SELECTOR))
68 | ?.let { textArea ->
69 | textArea as HTMLTextAreaElement
70 | val startPos = textArea.selectionStart
71 | val endPos = textArea.selectionEnd
72 | require(startPos != null) { "startPos is null" }
73 | require(endPos != null) { "startPos is null" }
74 | val currentValue = textArea.value
75 | textArea.value = currentValue.substring(
76 | 0,
77 | startPos
78 | ) + FOLDABLE_TEMPLATE + currentValue.substring(endPos, currentValue.length)
79 | }
80 | }
81 | }
82 | ) ?: println("Couldn't find foldable")
83 | }
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/theapache64/supergithub/features/ProfileSummary.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.features
2 |
3 | import com.theapache64.supergithub.utils.PathUtils
4 | import kotlinx.browser.document
5 | import kotlinx.browser.window
6 |
7 | class ProfileSummary : BaseFeature {
8 | companion object{
9 | private const val PROFILE_NAME_SELECTOR = "#js-pjax-container > div.container-xl.px-3.px-md-4.px-lg-5 > div > div.Layout-sidebar > div > div.js-profile-editable-replace > div.clearfix.d-flex.d-md-block.flex-items-center.mb-4.mb-md-0 > div.vcard-names-container.float-left.js-profile-editable-names.col-12.py-3.js-sticky.js-user-profile-sticky-fields > h1 > span.p-name.vcard-fullname.d-block.overflow-hidden"
10 | private const val ORG_SELECTOR = "#org-profile-repositories > div > div.my-3 > div > form > div"
11 | }
12 | override suspend fun onGitHubPageLoaded() {
13 | val url = window.location.href
14 | if (PathUtils.isProfilePage(url)) {
15 |
16 | val profileNameElement = document.querySelector(PROFILE_NAME_SELECTOR)
17 |
18 | val username = PathUtils.getUsername(url)
19 | if (username == null) {
20 | println("Couldn't find username from $url")
21 | return
22 | }
23 |
24 | println("Username is $username")
25 |
26 | if (profileNameElement != null) {
27 | println("Okay")
28 | val summaryButton = getSummaryButton(username)
29 | profileNameElement.innerHTML += summaryButton
30 |
31 | } else {
32 |
33 | // Maybe organization
34 | val element =
35 | document.querySelector(ORG_SELECTOR)
36 | if (element != null) {
37 | val html = getSummaryButton(username, "margin-top: 4px; margin-right:10px;")
38 | element.innerHTML = "$html ${element.innerHTML}"
39 | } else {
40 | println("Query selector failed")
41 | }
42 | }
43 |
44 | } else {
45 | println("$url is not profile page")
46 | }
47 | }
48 |
49 | private fun getSummaryButton(username: String, imageStyle: String = ""): String {
50 | return """
51 |
52 |
53 |
54 | """.trimIndent()
55 | }
56 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/theapache64/supergithub/features/RepoCreatedAt.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.features
2 |
3 | import com.theapache64.supergithub.data.repositories.GitHubRepo
4 | import com.theapache64.supergithub.utils.PathUtils
5 | import com.theapache64.supergithub.utils.TimeUtils
6 | import kotlinx.browser.document
7 | import kotlinx.browser.localStorage
8 | import kotlinx.browser.window
9 | import kotlin.js.Date
10 |
11 | class RepoCreatedAt : BaseFeature {
12 |
13 | override suspend fun onGitHubPageLoaded() {
14 | println("Finding repo created date")
15 |
16 |
17 | val repoPath = PathUtils.getRepoPath(window.location.href)
18 | if (repoPath != null) {
19 | val createdAtKey = "sg_$repoPath"
20 | val repoCreatedAt = localStorage.getItem(createdAtKey) ?: GitHubRepo.getRepo(repoPath).created_at
21 |
22 | if (repoCreatedAt != null) {
23 |
24 | localStorage.setItem(createdAtKey, repoCreatedAt)
25 |
26 | val repoCreatedDate = Date(repoCreatedAt)
27 | val timeEmoji = getTimeEmoji(repoCreatedDate)
28 |
29 | val timesAgo = TimeUtils.getRelativeTime(Date(), repoCreatedDate)
30 | if (timesAgo != null) {
31 |
32 | val div =
33 | document.querySelector("#repository-container-header > div.d-flex.mb-3.px-3.px-md-4.px-lg-5 > div")
34 | //document.querySelector("body > div.application-main > div > main > div.pagehead.repohead.hx_repohead.readability-menu.bg-gray-light.pb-0.pt-0.pt-lg-3 > div.d-flex.mb-4.p-responsive.d-none.d-lg-flex > div > h1")
35 |
36 | val hasBirthDate = div?.querySelector("span#sg_created_at") != null
37 | if (div != null && !hasBirthDate) {
38 |
39 | println("Added created date")
40 |
41 | div.innerHTML += """
42 | $timeEmoji
43 |
44 | Born $timesAgo
45 |
46 | """.trimIndent()
47 |
48 | } else {
49 | println("Selector algorithm failed")
50 | }
51 | } else {
52 | println("Invalid creation date $repoCreatedDate")
53 | }
54 | } else {
55 | println("Invalid rep $repoPath")
56 | }
57 | } else {
58 | println("It's not a repo")
59 | }
60 | }
61 |
62 | private fun getTimeEmoji(repoCreatedAt: Date): String {
63 |
64 | val time: Pair = when (repoCreatedAt.getHours()) {
65 | in 0..5 -> Pair("Midnight", "1f319") // "🌙"
66 | in 5..10 -> Pair("Morning", "1f31e") // "🌞"
67 | in 10..15 -> Pair("Noon", "2600") // "☀️"
68 | in 15..19 -> Pair("Evening", "1f325") // "🌥"
69 | else -> Pair("Night", "1f313") // "🌓"
70 | }
71 |
72 | return """
73 |
74 | """.trimIndent()
75 | }
76 |
77 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/theapache64/supergithub/features/ReviewComment.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.features
2 |
3 | import kotlinx.browser.document
4 | import kotlinx.browser.window
5 | import org.w3c.dom.HTMLTextAreaElement
6 | import org.w3c.dom.events.Event
7 | import org.w3c.dom.events.EventListener
8 |
9 | class ReviewComment : BaseFeature {
10 |
11 | companion object {
12 | private val PR_REVIEW_PAGE_URL_REGEX =
13 | "https:\\/\\/github\\.com\\/.+?\\/.+?\\/pull\\/\\d+?\\/files.*".toRegex()
14 |
15 | private val reviewActions = mapOf(
16 | "approve" to listOf(
17 | "LGTM 👌. Feel free to merge",
18 | "Wow. Good job mate 👍. Feel free to merge",
19 | "Cool. LGTM. Please merge 🚀",
20 | "Hell of a job 👍. LGTM. Please merge"
21 | ),
22 | "comment" to listOf(
23 | "I am comment value 1",
24 | "I am comment value 2",
25 | )
26 | )
27 |
28 | private val allMessages = reviewActions.values.flatten()
29 |
30 | private const val RADIO_SELECTOR =
31 | "#review-changes-modal > div > div > div > form > div > label > input[type=radio]"
32 | }
33 |
34 | override suspend fun onGitHubPageLoaded() {
35 | val url = window.location.href
36 | val isPrReviewUrl = PR_REVIEW_PAGE_URL_REGEX.matches(url)
37 |
38 | if (isPrReviewUrl) {
39 |
40 | for (action in reviewActions.keys) {
41 |
42 | val messages = reviewActions[action] ?: error("TSH: Couldn't find key $action")
43 |
44 | document
45 | .querySelector("$RADIO_SELECTOR[value=$action]")
46 | ?.let { element ->
47 | element.addEventListener("click", object : EventListener {
48 | override fun handleEvent(event: Event) {
49 | document.querySelector("#pull_request_review_body")?.let { commentTextArea ->
50 | val currentComment = (commentTextArea as HTMLTextAreaElement).value
51 | println("Current comment is '$currentComment'")
52 | // Either comment should be empty or any of the messages should be the content
53 | if (currentComment.isBlank() || allMessages.contains(currentComment)) {
54 | val newMessage = messages.random()
55 | console.log("Changing review message to $newMessage")
56 | commentTextArea.value = newMessage
57 | }
58 | }
59 | }
60 | })
61 | } ?: console.error("Uhh ho! failed to find the element")
62 | }
63 |
64 |
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/theapache64/supergithub/main.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub
2 |
3 | import com.theapache64.supergithub.features.FoldableContent
4 | import com.theapache64.supergithub.features.ReviewComment
5 | import com.theapache64.supergithub.features.ProfileSummary
6 | import com.theapache64.supergithub.features.RepoCreatedAt
7 | import com.theapache64.supergithub.utils.StringUtils
8 | import kotlinx.coroutines.GlobalScope
9 | import kotlinx.coroutines.delay
10 | import kotlinx.coroutines.launch
11 | import org.w3c.dom.HTMLSpanElement
12 | import kotlinx.browser.document
13 | import kotlinx.browser.window
14 |
15 |
16 | var prevUrl = window.location.toString()
17 |
18 | suspend fun main() {
19 |
20 | runSuperGithub()
21 |
22 | watchForUrlChange {
23 | println("URL changed, runSuperGithub")
24 | runSuperGithub()
25 | }
26 | }
27 |
28 | suspend fun runSuperGithub() {
29 |
30 | val features = listOf(
31 | RepoCreatedAt(),
32 | ProfileSummary(),
33 | ReviewComment(),
34 | FoldableContent()
35 | )
36 |
37 | for (feature in features) {
38 | println("--------------------------")
39 | println("Executing feature -> ${feature::class.simpleName}...")
40 | feature.onGitHubPageLoaded()
41 | }
42 | }
43 |
44 | private fun watchForUrlChange(callback: suspend () -> Unit) {
45 | window.setInterval({
46 | val newUrl = window.location.toString()
47 | if (newUrl != prevUrl) {
48 | prevUrl = newUrl
49 | println("URL changed!!")
50 |
51 | // Waiting for loading to be finished
52 | val progressBar =
53 | document.querySelector("body > div.position-relative.js-header-wrapper > span > span") as HTMLSpanElement
54 |
55 | GlobalScope.launch {
56 | var curProgress = 0
57 | while (curProgress < 100) {
58 | val progress = StringUtils.parseInt(progressBar.style.width)
59 | if (progress != null) {
60 | curProgress = progress
61 | } else {
62 | break
63 | }
64 | delay(200)
65 | }
66 | println("Hehe, progress is $curProgress")
67 | if (curProgress >= 100) {
68 | callback()
69 | } else {
70 | println("Page not loaded fully")
71 | }
72 | }
73 | }
74 | }, 500)
75 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/theapache64/supergithub/utils/PathUtils.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.utils
2 |
3 | object PathUtils {
4 |
5 | private val PROFILE_REGEX = "^https:\\/\\/github\\.com\\/[\\w-]+\\/?\$".toRegex()
6 |
7 | fun getRepoPath(fullUrl: String): String? {
8 | val slashSplit = fullUrl.split("/")
9 | if (slashSplit.size >= 5) {
10 | return "${slashSplit[3]}/${slashSplit[4]}".trim()
11 | }
12 | return null
13 | }
14 |
15 | fun isProfilePage(url: String): Boolean {
16 | return url.matches(PROFILE_REGEX)
17 | }
18 |
19 | fun getUsername(fullUrl: String): String? {
20 | val slashSplit = fullUrl.split("/")
21 | if (slashSplit.size >= 4) {
22 | return slashSplit[3].trim()
23 | }
24 | return null
25 | }
26 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/theapache64/supergithub/utils/StringUtils.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.utils
2 |
3 | object StringUtils {
4 | private val nonDigits = "\\D+".toRegex()
5 | fun parseInt(input: String): Int? {
6 | return try {
7 | input.replace(nonDigits, "").trim().toInt()
8 | }catch (e: NumberFormatException){
9 | null
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/theapache64/supergithub/utils/Svgs.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.utils
2 |
3 | val foldableSvg by lazy {
4 | """
5 |
57 | """.trimIndent()
58 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/theapache64/supergithub/utils/TimeUtils.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.utils
2 |
3 | import kotlin.js.Date
4 | import kotlin.math.floor
5 | import kotlin.math.round
6 |
7 | object TimeUtils {
8 |
9 | private const val SECOND_MILLIS = 1000L
10 | private const val MINUTE_MILLIS = 60 * SECOND_MILLIS
11 | private const val HOUR_MILLIS = 60 * MINUTE_MILLIS
12 | private const val DAY_MILLIS = 24 * HOUR_MILLIS
13 | private const val MONTH_MILLIS = 30 * DAY_MILLIS
14 | private const val YEAR_MILLIS = 12 * MONTH_MILLIS
15 |
16 |
17 | fun getRelativeTime(presentDate: Date, pastDate: Date): String? {
18 |
19 | val past = pastDate.getTime()
20 | val present = presentDate.getTime()
21 | val diff = present - past
22 |
23 | if (diff < 0) {
24 | return null
25 | }
26 |
27 | return when {
28 |
29 | diff < MINUTE_MILLIS -> {
30 | "just now";
31 | }
32 |
33 | diff < 2 * MINUTE_MILLIS -> {
34 | "a minute ago";
35 | }
36 |
37 | diff < 60 * MINUTE_MILLIS -> {
38 | "${floor(diff / MINUTE_MILLIS)} minutes ago";
39 | }
40 |
41 | diff < 120 * MINUTE_MILLIS -> {
42 | "an hour ago";
43 | }
44 |
45 | diff < 24 * HOUR_MILLIS -> {
46 | "${floor(diff / HOUR_MILLIS)} hours ago";
47 | }
48 |
49 | diff < 48 * HOUR_MILLIS -> {
50 | "yesterday";
51 | }
52 |
53 | diff < 30 * DAY_MILLIS -> {
54 | "${floor(diff / DAY_MILLIS)} days ago"
55 | }
56 |
57 | diff < 2 * MONTH_MILLIS -> {
58 | "a month ago"
59 | }
60 |
61 |
62 | diff < 12 * MONTH_MILLIS -> {
63 | "${floor(diff / MONTH_MILLIS)} months ago"
64 | }
65 |
66 | diff < 2 * YEAR_MILLIS -> {
67 | "a year ago"
68 | }
69 |
70 | else -> {
71 | "${round(diff / YEAR_MILLIS)} years ago";
72 | }
73 | }
74 | }
75 |
76 | }
--------------------------------------------------------------------------------
/src/main/resources/icons/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theapache64/super-github/3460987708952b27eb38df1aed829f65547b4824/src/main/resources/icons/icon128.png
--------------------------------------------------------------------------------
/src/main/resources/icons/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theapache64/super-github/3460987708952b27eb38df1aed829f65547b4824/src/main/resources/icons/icon16.png
--------------------------------------------------------------------------------
/src/main/resources/icons/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theapache64/super-github/3460987708952b27eb38df1aed829f65547b4824/src/main/resources/icons/icon48.png
--------------------------------------------------------------------------------
/src/main/resources/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Super Github",
3 | "version": "1.0.4",
4 | "description": "Enhance your GitHub experience",
5 | "manifest_version": 2,
6 | "icons": {
7 | "16": "icons/icon16.png",
8 | "48": "icons/icon48.png",
9 | "128": "icons/icon128.png"
10 | },
11 | "permissions": [
12 | ],
13 | "content_scripts": [
14 | {
15 | "matches": [
16 | "https://github.com/*"
17 | ],
18 | "js": [
19 | "super-github.js"
20 | ],
21 | "runat": "document_end"
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/src/test/kotlin/com/theapache64/supergithub/data/repositories/GitHubRepoTest.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.data.repositories
2 |
3 | import kotlinx.coroutines.GlobalScope
4 | import kotlinx.coroutines.promise
5 | import kotlin.test.Test
6 | import kotlin.test.assertEquals
7 | import kotlin.test.assertNull
8 |
9 | fun runTest(block: suspend () -> Unit): dynamic = GlobalScope.promise { block() }
10 |
11 | class GitHubRepoTest {
12 |
13 | @Test
14 | fun getCreateDateForValidRepo() = runTest {
15 | val repoPath = "theapache64/faded"
16 | val repo = GitHubRepo.getRepo(repoPath)
17 | assertEquals("2019-02-11T04:03:06Z", repo.created_at)
18 | }
19 |
20 | @Test
21 | fun getCreateDateForInvalidRepo() = runTest {
22 | val repoPath = ""
23 | val repo = GitHubRepo.getRepo(repoPath)
24 | assertNull(repo.created_at)
25 | }
26 | }
--------------------------------------------------------------------------------
/src/test/kotlin/com/theapache64/supergithub/utils/PathUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.utils
2 |
3 | import kotlin.test.Test
4 | import kotlin.test.assertEquals
5 | import kotlin.test.assertNull
6 |
7 |
8 | internal class PathUtilsTest {
9 | @Test
10 | fun getRepoPathValid() {
11 | val url = "https://github.com/theapache64/faded"
12 | val repoName = PathUtils.getRepoPath(url)
13 | assertEquals("theapache64/faded", repoName)
14 | }
15 |
16 | @Test
17 | fun getRepoPathInvalid() {
18 | val invalidUrl = "theapache64/faded"
19 | val repoName = PathUtils.getRepoPath(invalidUrl)
20 | assertEquals(null, repoName)
21 | }
22 |
23 |
24 | @Test
25 | fun getUsernameFromValidUrl() {
26 | val url = "https://github.com/theapache64"
27 | val userName = PathUtils.getUsername(url)
28 | assertEquals("theapache64", userName)
29 | }
30 |
31 | @Test
32 | fun getUsernameFromInvalidUrl() {
33 | val url = "https://github.com"
34 | val userName = PathUtils.getUsername(url)
35 | assertNull(userName)
36 | }
37 | }
--------------------------------------------------------------------------------
/src/test/kotlin/com/theapache64/supergithub/utils/StringUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.utils
2 |
3 | import kotlin.test.Test
4 | import kotlin.test.assertEquals
5 |
6 | class StringUtilsTest {
7 | @Test
8 | fun parseInt() {
9 | assertEquals(100, StringUtils.parseInt("100%"))
10 | assertEquals(0, StringUtils.parseInt("0%"))
11 | assertEquals(null, StringUtils.parseInt(""))
12 | }
13 | }
--------------------------------------------------------------------------------
/src/test/kotlin/com/theapache64/supergithub/utils/TimeUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package com.theapache64.supergithub.utils
2 |
3 | import kotlin.js.Date
4 | import kotlin.test.assertEquals
5 |
6 | class TimeUtilsTest {
7 |
8 |
9 | @kotlin.test.Test
10 | fun parseGood() {
11 | val today = Date("2020-01-20T16:00:00Z") // 04:00:00 PM, Jan 20, 2020
12 | TimeUtils.apply {
13 |
14 | val dateMap = mapOf(
15 | "2020-01-20T15:59:58Z" to "just now", // 03:59:58 PM, Jan 20, 2020
16 | "2020-01-20T15:59:00Z" to "a minute ago", // 03:59:00 PM, Jan 20, 2020
17 | "2020-01-20T15:30:00Z" to "30 minutes ago", // 03:30:00 PM, Jan 20, 2020
18 | "2020-01-20T15:01:00Z" to "59 minutes ago", // 03:01:00 PM, Jan 20, 2020
19 | "2020-01-20T15:00:00Z" to "an hour ago", // 03:00:00 PM, Jan 20, 2020
20 | "2020-01-20T14:00:00Z" to "2 hours ago", // 02:00:00 PM, Jan 20, 2020
21 | "2020-01-20T04:00:00Z" to "12 hours ago", // 04:00:00 AM, Jan 20, 2020
22 | "2020-01-20T01:00:00Z" to "15 hours ago", // 01:00:00 AM, Jan 20, 2020
23 | "2020-01-19T17:00:00Z" to "23 hours ago", // 05:00:00 PM, Jan 19, 2020
24 | "2020-01-19T16:01:00Z" to "23 hours ago", // 04:01:00 PM, Jan 19, 2020
25 | "2020-01-19T16:00:00Z" to "yesterday", // 04:00:00 PM, Jan 19, 2020
26 | "2020-01-18T00:00:00Z" to "2 days ago", // 00:00:00 PM, Jan 18, 2020
27 | "2020-01-01T00:00:00Z" to "19 days ago", // 00:00:00 PM, Jan 1, 2020
28 | "2019-12-20T00:00:00Z" to "a month ago", // 00:00:00 PM, Dec 20, 2019
29 | "2019-11-20T00:00:00Z" to "2 months ago", // 00:00:00 PM, Nov 20, 2019
30 | "2019-02-20T00:00:00Z" to "11 months ago", // 00:00:00 PM, Feb 20, 2019
31 | "2019-01-01T00:00:00Z" to "a year ago", // 00:00:00 PM, Jan 01, 2019
32 | "2018-01-01T00:00:00Z" to "2 years ago", // 00:00:00 PM, Jan 01, 2018
33 | "2010-01-01T00:00:00Z" to "10 years ago" // 00:00:00 PM, Jan 01, 2010
34 | )
35 |
36 | for ((input, output) in dateMap) {
37 | val inputDate = Date(input)
38 | assertEquals(output, getRelativeTime(today, inputDate))
39 | }
40 |
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------