├── .gitattributes
├── .gitignore
├── Dockerfile
├── Dockerfile-webhook
├── Instructions.md
├── README.md
├── build.gradle
├── css
├── tooltipster-shadow.css
└── tooltipster.css
├── docker-compose.yml
├── docs
└── privacy.html
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── embed_logo.png
├── embed_logo.svg
├── favicons
│ ├── android-chrome-192x192.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── manifest.json
│ ├── mstile-144x144.png
│ ├── mstile-150x150.png
│ ├── mstile-310x150.png
│ ├── mstile-310x310.png
│ ├── mstile-70x70.png
│ └── safari-pinned-tab.svg
├── logo.svg
└── logo_docs.svg
├── js
├── curse-ads.js
├── docs.js
├── files.js
├── google-ads.js
├── sidebar.js
├── theme-switch-toggle.js
└── theme-switch.js
├── python
├── generators.py
├── mc_version.py
├── metadata.py
├── page_generator.py
├── page_generator_service.py
└── templates.py
├── requirements.txt
├── runTestPageGen.bat
├── runTestPageGen.sh
├── sass
├── _ads.scss
├── _base.scss
├── _docs.scss
├── _downloads.scss
├── _forum_screens.scss
├── _forums.scss
├── _highlighting_dark.scss
├── _highlighting_light.scss
├── _layout.scss
├── _layout_screens.scss
├── _normalize.scss
├── _privacy.scss
├── _reboot.scss
├── _scrollpane.scss
├── _sidebars.scss
├── _styles.scss
├── _styles_dark.scss
├── _styles_light.scss
├── _theme_dark.scss
├── _theme_light.scss
├── documentation_dark.scss
├── documentation_light.scss
├── forums_dark.scss
├── forums_light.scss
├── website_dark.scss
└── website_light.scss
├── settings.gradle
└── templates
├── base_page.html
├── page_body.html
├── page_directory_body.html
├── page_footer.html
├── page_header.html
└── project_index.html
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | *.bat text eol=crlf
3 | *.patch text eol=lf
4 | *.java text eol=lf
5 | *.gradle text eol=crlf
6 | *.png binary
7 | *.gif binary
8 | *.exe binary
9 | *.dll binary
10 | *.jar binary
11 | *.lzma binary
12 | *.zip binary
13 | *.pyd binary
14 | *.cfg text eol=lf
15 | *.py text eol=lf
16 | *.jks binary
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /__pycache__/
2 | /output_web/
3 | *.bat
4 | /cache/
5 | /output_meta/
6 | /.venv/
7 | /.idea/
8 | /build
9 | /.gradle
10 | /tstin/
11 | /tstout/
12 | /out/
13 | /python/__pycache__/
14 | /test.sh
15 | /test/*
16 | !/test/Help.txt
17 | /repo/
18 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.9-alpine
2 |
3 | VOLUME /in
4 | VOLUME /out
5 |
6 | WORKDIR /app
7 |
8 | COPY requirements.txt ./
9 | RUN pip install --no-cache-dir -r requirements.txt
10 |
11 | COPY python python
12 | COPY templates templates
13 |
14 | COPY build/distributions/static-bundle.zip .
15 | RUN unzip static-bundle.zip -d static && rm static-bundle.zip
16 |
17 | ENTRYPOINT [ "python", "-u", "python/page_generator.py" ]
18 |
--------------------------------------------------------------------------------
/Dockerfile-webhook:
--------------------------------------------------------------------------------
1 | FROM python:3.9-alpine
2 |
3 | VOLUME /in
4 | VOLUME /out
5 |
6 | WORKDIR /app
7 |
8 | COPY requirements.txt ./
9 | RUN pip install --no-cache-dir -r requirements.txt
10 |
11 | COPY python python
12 | COPY templates templates
13 |
14 | COPY build/distributions/static-bundle.zip .
15 | RUN unzip static-bundle.zip -d static && rm static-bundle.zip
16 |
17 | ENTRYPOINT [ "python", "-u", "python/page_generator_service.py" ]
18 |
--------------------------------------------------------------------------------
/Instructions.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | There's two main parts to the build system and testing procedure:
4 | 1. Python pagegen for generated content (html templates, files in the `./test/out` folder)
5 | 2. Gradle for static content (js, sass -> css and images)
6 |
7 | ## Folder structure
8 |
9 | Here's a brief description on what each of the folders are for that we understand:
10 |
11 | - `gradle`
12 | - contains the gradle wrapper jar
13 | - `images`
14 | - contains png and svg images
15 | - `js`
16 | - contains JavaScript files that are put on various places of Forge's websites
17 | - `python`
18 | - the PageGen code
19 | - `test`
20 | - this is where you can test your changes
21 | - `maven`
22 | - contains dummy data used by pagegen, generated by the `setupTest` Gradle task
23 | - you can make your own by running the `publish` command on most Forge projects and copy the literal folder called `repo` from there over to here
24 | - poms and the like don't seem to be used by the pagegen, so feel free to make folders, add files inside and change up the json as you see fit to aid testing.
25 | - As long as it follows the expected format similar to the dummy data, it should work
26 | - `out`
27 | - the generated output by pagegen
28 | - `static`
29 | - contains compiled static content, such as the css, js and images, made by the `setupTest`
30 | - `sass`
31 | - poorly named, actually has scss files inside which are used for styling
32 | - they get compiled to css by the gradle part of the build system
33 | - `static`
34 | - contains compiled static content, such as the css, js and images
35 | - during testing, you probably want to compile to this folder and tell pagegen to use this with the `--static` arg
36 | - this folder also contains a copy of the images folder
37 | - `templates`
38 | - the HTML templates used by the Python pagegen side of the build system
39 |
40 | # Instructions
41 |
42 | Here's some instructions on how to setup the environment and work with it.
43 |
44 | ## Step 1: Setting up the test environment
45 |
46 | Firstly, we need to setup the Python side:
47 | 1. Make sure Python is installed. At the time of writing, the latest Python 3.10.4 seems to work
48 |
49 | Now, let's setup the Gradle side:
50 | 1. Import the `build.gradle` into your IDE
51 | 2. Run `gradlew setupTest`
52 |
53 | ## Step 2: Run PageGen
54 |
55 | 1. Run `gradlew runTestPageGen`
56 | 2. Open the pages you want to test, they can be found in the `./test/out` folder
57 |
58 | ## Step 3: Making changes
59 |
60 | For HTML:
61 | 1. Edit the appropriate file(s) in the `./templates` folder
62 | 2. Run `gradlew runTestPageGen`
63 | 3. Open the pages you want to test in the `./test/out` folder
64 |
65 | For CSS:
66 | 1. Edit the appropriate file(s) in the `./css` folder
67 | 2. Run `gradlew setupTest`
68 | 3. Run `gradlew runTestPageGen`
69 | 4. Open the pages you want to test in the `./test/out` folder
70 |
71 | For SCSS:
72 | 1. Edit the appropriate file(s) in the `./sass` folder
73 | 2. Run `gradlew setupTest`
74 | 3. Run `gradlew runTestPageGen`
75 | 4. Open the pages you want to test in the `./test/out` folder
76 |
77 | For JS:
78 | 1. Edit the appropriate file(s) in the `./js` folder
79 | 2. Run `gradlew setupTest`
80 | 3. Run `gradlew runTestPageGen`
81 | 4. Open the pages you want to test in the `./test/out` folder
82 |
83 | For images:
84 | 1. Edit the appropriate file(s) in the `./images` folder
85 | 2. Run `gradlew setupTest`
86 | 3. Run `gradlew runTestPageGen`
87 | 4. Open the pages you want to test in the `./test/out` folder
88 |
89 | For the Python side of the build system:
90 | 1. Edit the appropriate file(s) in the `./python` folder and the `requirements.txt`
91 | 2. Run `setupPageGen` if you changed the `requirements.txt`
92 | 3. Test PageGen with `runTestPageGen` and opening the pages you want to test in the `./test/out` folder
93 |
94 | For the Gradle side of the build system:
95 | 1. Edit the appropriate Gradle file(s), such as `build.gradle`, `gradlew`, `gradlew.bat` and the `./gradle` folder
96 | 2. Refresh Gradle as usual
97 |
98 | ## Step 4: Committing
99 |
100 | In your PR, commit any changed files inside the `./css`, `./sass`, `./js`, `./templates`, `./images` and `./python` folders
101 |
102 | Note: The test html files generated in Step 2 are not intended to be published to a live site, as they contain `file://` references to your machine for some static resources, which won't work for anyone else.
103 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Forge Web
2 | =========
3 |
4 | This repository serves as central hub for all things web and [Forge](https://github.com/MinecraftForge/MinecraftForge/). This includes stylesheets, images and scripts for Forge sites such as the docs and the [forums](https://minecraftforge.net/).
5 |
6 | SASS Structure
7 | --------------
8 | To ensure that the look and feel across all sites is consistent, we heavily rely on the [SASS preprocessor](http://sass-lang.com/) to modularize and automatically generate our stylesheets.
9 |
10 | Hence, you will find a specific structure when looking at the stylesheets:
11 |
12 | - Only files without an underscore prefix will get deployed to the sites and are the place where all other components come together. These files should stay as they are right now unless new modules are added or old ones removed.
13 | - Files with an underscore make up discrete modules of the stylesheets, their name specifies each module's purpose.
14 | - Files with a 'theme' prefix serve as mere variable references to allow for flexible and easy switching of color themes.
15 | - Files with a 'styles' prefix serve as combinations of multiple modules along with the respective themes, while the simple 'styles.scss' file is the location where generic styles are brought together.
16 | - Files with a 'screens' suffix generally contain nothing but media queries that specify adjustments for different screen and device sizes.
17 |
18 | Contribution Guidelines
19 | -----------------------
20 | You can find instructions on how to setup, develop and make contributions to this repo [here](Instructions.md).
21 |
22 | Contributions such as fixes or additions to the stylesheets are welcome, but they should adhere to the following rules:
23 |
24 | - The sites make heavy use of the flexbox model. Its capabilities should be used to the fullest rather than relying on hacks simply to work in old browsers.
25 | - Colors mustn't be hardcoded unless they work well across all themes. Either stick to the current color palette and use the theme variables or, if no other way is possible, introduce a new theme variable.
26 | - All dimensional measurements must be specified in `rem` or as a percentage. This ensures a consistent look and will make potential font sizes changes less of a hassle.
27 | - Unless absolutely necessary, new modules or edits to the deployment files should be avoided. Changes should go into an appropriate file, but one shouldn't need to look for a single change in too many files.
28 | - Use of all capabilities of SASS is helpful. Try to nest things where a hierarchy is given and comment the topmost element accordingly.
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'maven-publish'
3 | id "io.freefair.jsass-base" version "1.0.2"
4 | id "com.magnetichq.gradle.js" version "3.0.2"
5 | id 'net.minecraftforge.gradleutils' version '[2.3,2.4)'
6 | }
7 |
8 | group = 'net.minecraftforge'
9 | version = gradleutils.tagOffsetVersion
10 | println "Version: $version"
11 |
12 | tasks.register("css", io.freefair.gradle.plugins.jsass.SassCompile) {
13 | sassPath = "${projectDir}/sass"
14 | cssPath = "${buildDir}/css"
15 | sourceMapEnabled = false
16 | //outputStyle = io.bit3.jsass.OutputStyle.NESTED
17 | outputStyle = io.bit3.jsass.OutputStyle.COMPRESSED
18 | }
19 | def minifyJsFiles = tasks.register("minifyJsFiles")
20 | void createJsTask(final String taskName, final String fileName) {
21 | def tmp = tasks.register("minify${taskName.capitalize()}DotJs", com.eriwen.gradle.js.tasks.MinifyJsTask) {
22 | source = file("${projectDir}/js/${fileName}.js")
23 | dest = file("${buildDir}/js/${fileName}.min.js")
24 | }
25 | minifyJsFiles.dependsOn(tmp)
26 | }
27 | createJsTask('themeSwitch', "theme-switch")
28 | createJsTask('themeSwitchToggle', "theme-switch-toggle")
29 | createJsTask('curseAds', "curse-ads")
30 | createJsTask('googleAds', "google-ads")
31 | createJsTask('sidebar', "sidebar")
32 | createJsTask('files', "files")
33 | tasks.register('bundleStatic', Zip) {
34 | dependsOn minifyJsFiles
35 | archiveBaseName = 'static-bundle'
36 | destinationDirectory = file("${buildDir}/distributions")
37 | from(file("${buildDir}/js")) {
38 | include '*.js'
39 | into 'js'
40 | }
41 | from(css) {
42 | include 'website_*.css'
43 | into 'css'
44 | rename ~/website_([A-Za-z]+)/, 'styles_$1'
45 | }
46 | from("${projectDir}/css") {
47 | include '*.css'
48 | into 'css'
49 | }
50 | from("${projectDir}/images") {
51 | include '**'
52 | into 'images'
53 | }
54 | from("${projectDir}/docs") {
55 | include '**/*.html'
56 | }
57 | }
58 | tasks.register('bundleFiles', Zip) {
59 | dependsOn bundleStatic
60 | archiveBaseName = 'full-bundle'
61 | destinationDirectory = file("${buildDir}/distributions")
62 | from(zipTree(bundleStatic.archiveFile)) {
63 | into 'static'
64 | }
65 | from(file("${projectDir}/python")) {
66 | exclude '__pycache__/'
67 | into 'python'
68 | }
69 | from(file("${projectDir}/templates")) {
70 | into 'templates'
71 | }
72 | from(file("${projectDir}/requirements.txt"))
73 | }
74 |
75 | @groovy.transform.CompileStatic
76 | static String digest(String data, String hash) {
77 | return java.security.MessageDigest.getInstance(hash).digest(data.bytes).encodeHex().toString()
78 | }
79 | void writeArtifact(group, name, version, classifier = null, String ext = 'jar') {
80 | String path = "${group}/${name}/${version}/${name}-${version}"
81 | if (classifier != null)
82 | path += '-' + classifier
83 | path += '.' + ext
84 | file("./test/maven/${path}").parentFile.mkdirs()
85 | file("./test/maven/${path}").text = path
86 | file("./test/maven/${path}.md5").text = digest(path, 'MD5')
87 | file("./test/maven/${path}.sha1").text = digest(path, 'SHA-1')
88 | file("./test/maven/${path}.sha256").text = digest(path, 'SHA-256')
89 | }
90 | void writeXml(String group, String name, String displayName, List vers, boolean writePromos) {
91 | final writer = new StringWriter()
92 | def xml = new groovy.xml.MarkupBuilder(writer)
93 | String path = "./test/maven/${group}/${name}"
94 |
95 | xml.metadata() {
96 | groupId(group.replace('/', '.'))
97 | artifactId(name)
98 | versioning() {
99 | release(vers[-1])
100 | latest(vers[-1])
101 | lastUpdated(20220101010000)
102 | versions() {
103 | vers.each { ver -> xml.version(ver) }
104 | }
105 | }
106 | }
107 | String data = writer.toString()
108 | file("${path}/").mkdirs()
109 | file("${path}/maven-metadata.xml").text = data
110 | file("${path}/maven-metadata.xml.md5").text = digest(data, 'MD5')
111 | file("${path}/maven-metadata.xml.sha1").text = digest(data, 'SHA-1')
112 | file("${path}/maven-metadata.xml.sha256").text = digest(data, 'SHA-256')
113 | file("${path}/page_config.json").text = new groovy.json.JsonBuilder([
114 | name: displayName,
115 | comments: false,
116 | downloads: true,
117 | shrink_dls: false,
118 | adfocus: "271228"
119 | ]).toPrettyString()
120 | if (writePromos) {
121 | file("${path}/promotions_slim.json").text = new groovy.json.JsonBuilder([
122 | homepage: 'https://not_real.invalid/',
123 | promos: [
124 | '1.16.5-latest': '36.1.2',
125 | '1.16.5-recommended': '36.1.2',
126 | '1.18.1-latest': '39.1.2',
127 | '1.18.1-recommended': '39.1.2',
128 | '1.18.2-latest': '40.0.8'
129 | ]
130 | ]).toPrettyString()
131 | }
132 | }
133 | tasks.register("deleteTestData", Delete) {
134 | delete './test/'
135 | }
136 | tasks.register('generateMaven') {
137 | mustRunAfter deleteTestData
138 | doLast {
139 | List versions = []
140 | ['1.16.5-36.0', '1.16.5-36.1', '1.18.1-39.0', '1.18.1-39.1', '1.18.2-40.0', '1.18.2-40.1'].each { ver ->
141 | def range = 0..8 // make eight builds for "beta" release period
142 | if (ver.endsWith('.1'))
143 | range = 0..2 // make three builds for "recommended" release period
144 |
145 | range.each { build ->
146 | versions.add("${ver}.${build}")
147 | writeArtifact('net/minecraftforge', 'forge', "${ver}.${build}")
148 | writeArtifact('net/minecraftforge', 'forge', "${ver}.${build}", 'userdev')
149 | writeArtifact('net/minecraftforge', 'forge', "${ver}.${build}", 'changelog', 'txt')
150 | writeArtifact('net/minecraftforge', 'forge', "${ver}.${build}", 'mdk', 'zip')
151 | writeArtifact('net/minecraftforge', 'forge', "${ver}.${build}", 'installer')
152 | }
153 | }
154 | writeXml('net/minecraftforge', 'forge', "Minecraft Forge", versions, true)
155 |
156 | versions = []
157 | ['23w13a_or_b-april.2023.13.b', '23w17a-2023.17.a', '1.20.1-pre1-46.0', '1.19.4-45.0', '1.20-rc1-46.1'].each { ver ->
158 | def range = 0..8 // make eight builds
159 |
160 | range.each { build ->
161 | versions.add("${ver}.${build}")
162 | writeArtifact('net/minecraftforge', 'froge', "${ver}.${build}")
163 | writeArtifact('net/minecraftforge', 'froge', "${ver}.${build}", 'userdev')
164 | writeArtifact('net/minecraftforge', 'froge', "${ver}.${build}", 'changelog', 'txt')
165 | writeArtifact('net/minecraftforge', 'froge', "${ver}.${build}", 'mdk', 'zip')
166 | writeArtifact('net/minecraftforge', 'froge', "${ver}.${build}", 'installer')
167 | }
168 | }
169 | writeXml('net/minecraftforge', 'froge', "Minecraft Froge", versions, false)
170 | }
171 | }
172 | tasks.register('generateGlobalConfig') {
173 | mustRunAfter deleteTestData
174 | doLast {
175 | def file = file('./test/config/global_overrides.json')
176 | file.parentFile.mkdirs()
177 | file.text = new groovy.json.JsonBuilder([
178 | ad_left: "
",
179 | ad_middle: "
",
180 | ad_right: "
"
181 | ]).toPrettyString()
182 | }
183 | }
184 | tasks.register('generateTestUsers') {
185 | mustRunAfter deleteTestData
186 | doLast {
187 | def file = file('./test/config/users.json')
188 | file.parentFile.mkdirs()
189 | file.text = new groovy.json.JsonBuilder([
190 | test_name: [
191 | password: 'test_password',
192 | access: [
193 | [group: '', artifact: '']
194 | ]
195 | ]
196 | ]).toPrettyString()
197 | }
198 | }
199 | tasks.register('generateTestConfig') {
200 | dependsOn generateGlobalConfig, generateTestUsers
201 | mustRunAfter deleteTestData
202 | doLast {
203 | def cfg = file('./test/config/config.json')
204 | cfg.parentFile.mkdirs()
205 | cfg.text = new groovy.json.JsonBuilder([
206 | promote: [
207 | args: [ '--folder', '/in', '--local-data', '--config', '/config/global_overrides.json', '--static', 'file://' + file('./test/static/').absolutePath]
208 | ]
209 | ]).toPrettyString()
210 | }
211 | }
212 | tasks.register('extractBundledFiles', Copy) {
213 | dependsOn bundleStatic
214 | mustRunAfter deleteTestData
215 | from zipTree(bundleStatic.archiveFile)
216 | into file('./test/static/')
217 | }
218 | tasks.register('copyTestTemplates', Copy) {
219 | mustRunAfter deleteTestData
220 | from file('./templates/')
221 | into file('./test/templates/')
222 | }
223 | tasks.register('copyTestPython', Copy) {
224 | mustRunAfter deleteTestData
225 | from file('./python/')
226 | into file('./test/python/')
227 | }
228 | tasks.register('runTestPageGen', Exec) {
229 | dependsOn = [copyTestPython, copyTestTemplates]
230 | workingDir 'test'
231 | String os = System.getProperty('os.name').toLowerCase(Locale.ROOT)
232 | if (os.contains('win'))
233 | commandLine 'cmd', '/c', file('runTestPageGen.bat').absolutePath
234 | else // assume *nix
235 | commandLine 'sh', '../runTestPageGen.sh'
236 | }
237 | tasks.register('setupPageGen', Exec) {
238 | String os = System.getProperty('os.name').toLowerCase(Locale.ROOT)
239 | if (os.contains('win'))
240 | commandLine 'cmd', '/c', 'pip install -r requirements.txt'
241 | else // assume *nix
242 | commandLine 'sh', '-c', 'pip install -r requirements.txt'
243 | }
244 | tasks.register('setupTest') {
245 | dependsOn = [deleteTestData, extractBundledFiles, generateMaven, setupPageGen, generateTestConfig, copyTestTemplates]
246 | doLast { file('./test/out').mkdirs() }
247 | }
248 |
249 |
250 | tasks.register('publishDocker', Exec) {
251 | dependsOn bundleStatic
252 | commandLine 'docker-compose', 'build'
253 | doLast {
254 | exec { commandLine 'docker', 'tag', 'pagegen:latest', 'containers.minecraftforge.net/pagegen:latest' }
255 | exec { commandLine 'docker', 'tag', 'pagegen:latest', "containers.minecraftforge.net/pagegen:$version" }
256 | exec { commandLine 'docker', 'push', 'containers.minecraftforge.net/pagegen:latest' }
257 | exec { commandLine 'docker', 'push', "containers.minecraftforge.net/pagegen:$version" }
258 |
259 | exec { commandLine 'docker', 'tag', 'pagegen-webhook:latest', 'containers.minecraftforge.net/pagegen-webhook:latest' }
260 | exec { commandLine 'docker', 'tag', 'pagegen-webhook:latest', "containers.minecraftforge.net/pagegen-webhook:$version" }
261 | exec { commandLine 'docker', 'push', 'containers.minecraftforge.net/pagegen-webhook:latest' }
262 | exec { commandLine 'docker', 'push', "containers.minecraftforge.net/pagegen-webhook:$version" }
263 | }
264 | }
265 | tasks.register('publishDockerTest', Exec) {
266 | dependsOn bundleStatic
267 | commandLine 'docker-compose', 'build'
268 | doLast {
269 | exec { commandLine 'docker', 'tag', 'pagegen:latest', 'containers.minecraftforge.net/pagegen-test:latest' }
270 | exec { commandLine 'docker', 'tag', 'pagegen:latest', "containers.minecraftforge.net/pagegen-test:$version" }
271 | exec { commandLine 'docker', 'push', 'containers.minecraftforge.net/pagegen-test:latest' }
272 | exec { commandLine 'docker', 'push', "containers.minecraftforge.net/pagegen-test:$version" }
273 |
274 |
275 | exec { commandLine 'docker', 'tag', 'pagegen-webhook:latest', 'containers.minecraftforge.net/pagegen-webhook-test:latest' }
276 | exec { commandLine 'docker', 'tag', 'pagegen-webhook:latest', "containers.minecraftforge.net/pagegen-webhook-test:$version" }
277 | exec { commandLine 'docker', 'push', 'containers.minecraftforge.net/pagegen-webhook-test:latest' }
278 | exec { commandLine 'docker', 'push', "containers.minecraftforge.net/pagegen-webhook-test:$version" }
279 | }
280 | }
281 |
282 | publishing {
283 | publications.register('forgeMaven', MavenPublication) {
284 | artifact source: bundleFiles, extension: 'zip'
285 |
286 | artifactId = 'web'
287 |
288 | pom {
289 | name = 'Forge Page Generator'
290 | description = 'Python code to generate a download frontend for maven repository'
291 | url = 'https://github.com/MinecraftForge/Web'
292 |
293 | gradleutils.pom.setGitHubDetails(pom, 'Web')
294 |
295 | license gradleutils.pom.Licenses.LGPLv2_1
296 |
297 | developers {
298 | developer gradleutils.pom.Developers.LexManos
299 | }
300 | }
301 | }
302 |
303 | repositories {
304 | maven gradleutils.publishingForgeMaven
305 | }
306 | }
--------------------------------------------------------------------------------
/css/tooltipster-shadow.css:
--------------------------------------------------------------------------------
1 | .tooltipster-shadow {
2 | border-radius: 5px;
3 | background: #fff;
4 | box-shadow: 0px 0px 14px rgba(0,0,0,0.3);
5 | color: #2c2c2c;
6 | }
7 | .tooltipster-shadow .tooltipster-content {
8 | font-family: 'Arial', sans-serif;
9 | font-size: 14px;
10 | line-height: 16px;
11 | padding: 8px 10px;
12 | }
--------------------------------------------------------------------------------
/css/tooltipster.css:
--------------------------------------------------------------------------------
1 | /* This is the default Tooltipster theme (feel free to modify or duplicate and create multiple themes!): */
2 | .tooltipster-default {
3 | border-radius: 5px;
4 | border: 2px solid #000;
5 | background: #4c4c4c;
6 | color: #fff;
7 | }
8 |
9 | /* Use this next selector to style things like font-size and line-height: */
10 | .tooltipster-default .tooltipster-content {
11 | font-family: Arial, sans-serif;
12 | font-size: 14px;
13 | line-height: 16px;
14 | padding: 8px 10px;
15 | overflow: hidden;
16 | }
17 |
18 | /* This next selector defines the color of the border on the outside of the arrow. This will automatically match the color and size of the border set on the main tooltip styles. Set display: none; if you would like a border around the tooltip but no border around the arrow */
19 | .tooltipster-default .tooltipster-arrow .tooltipster-arrow-border {
20 | /* border-color: ... !important; */
21 | }
22 |
23 |
24 | /* If you're using the icon option, use this next selector to style them */
25 | .tooltipster-icon {
26 | cursor: help;
27 | margin-left: 4px;
28 | }
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | /* This is the base styling required to make all Tooltipsters work */
38 | .tooltipster-base {
39 | padding: 0;
40 | font-size: 0;
41 | line-height: 0;
42 | position: absolute;
43 | left: 0;
44 | top: 0;
45 | z-index: 9999999;
46 | pointer-events: none;
47 | width: auto;
48 | overflow: visible;
49 | }
50 | .tooltipster-base .tooltipster-content {
51 | overflow: hidden;
52 | }
53 |
54 |
55 | /* These next classes handle the styles for the little arrow attached to the tooltip. By default, the arrow will inherit the same colors and border as what is set on the main tooltip itself. */
56 | .tooltipster-arrow {
57 | display: block;
58 | text-align: center;
59 | width: 100%;
60 | height: 100%;
61 | position: absolute;
62 | top: 0;
63 | left: 0;
64 | z-index: -1;
65 | }
66 | .tooltipster-arrow span, .tooltipster-arrow-border {
67 | display: block;
68 | width: 0;
69 | height: 0;
70 | position: absolute;
71 | }
72 | .tooltipster-arrow-top span, .tooltipster-arrow-top-right span, .tooltipster-arrow-top-left span {
73 | border-left: 8px solid transparent !important;
74 | border-right: 8px solid transparent !important;
75 | border-top: 8px solid;
76 | bottom: -7px;
77 | }
78 | .tooltipster-arrow-top .tooltipster-arrow-border, .tooltipster-arrow-top-right .tooltipster-arrow-border, .tooltipster-arrow-top-left .tooltipster-arrow-border {
79 | border-left: 9px solid transparent !important;
80 | border-right: 9px solid transparent !important;
81 | border-top: 9px solid;
82 | bottom: -7px;
83 | }
84 |
85 | .tooltipster-arrow-bottom span, .tooltipster-arrow-bottom-right span, .tooltipster-arrow-bottom-left span {
86 | border-left: 8px solid transparent !important;
87 | border-right: 8px solid transparent !important;
88 | border-bottom: 8px solid;
89 | top: -7px;
90 | }
91 | .tooltipster-arrow-bottom .tooltipster-arrow-border, .tooltipster-arrow-bottom-right .tooltipster-arrow-border, .tooltipster-arrow-bottom-left .tooltipster-arrow-border {
92 | border-left: 9px solid transparent !important;
93 | border-right: 9px solid transparent !important;
94 | border-bottom: 9px solid;
95 | top: -7px;
96 | }
97 | .tooltipster-arrow-top span, .tooltipster-arrow-top .tooltipster-arrow-border, .tooltipster-arrow-bottom span, .tooltipster-arrow-bottom .tooltipster-arrow-border {
98 | left: 0;
99 | right: 0;
100 | margin: 0 auto;
101 | }
102 | .tooltipster-arrow-top-left span, .tooltipster-arrow-bottom-left span {
103 | left: 6px;
104 | }
105 | .tooltipster-arrow-top-left .tooltipster-arrow-border, .tooltipster-arrow-bottom-left .tooltipster-arrow-border {
106 | left: 5px;
107 | }
108 | .tooltipster-arrow-top-right span, .tooltipster-arrow-bottom-right span {
109 | right: 6px;
110 | }
111 | .tooltipster-arrow-top-right .tooltipster-arrow-border, .tooltipster-arrow-bottom-right .tooltipster-arrow-border {
112 | right: 5px;
113 | }
114 | .tooltipster-arrow-left span, .tooltipster-arrow-left .tooltipster-arrow-border {
115 | border-top: 8px solid transparent !important;
116 | border-bottom: 8px solid transparent !important;
117 | border-left: 8px solid;
118 | top: 50%;
119 | margin-top: -7px;
120 | right: -7px;
121 | }
122 | .tooltipster-arrow-left .tooltipster-arrow-border {
123 | border-top: 9px solid transparent !important;
124 | border-bottom: 9px solid transparent !important;
125 | border-left: 9px solid;
126 | margin-top: -8px;
127 | }
128 | .tooltipster-arrow-right span, .tooltipster-arrow-right .tooltipster-arrow-border {
129 | border-top: 8px solid transparent !important;
130 | border-bottom: 8px solid transparent !important;
131 | border-right: 8px solid;
132 | top: 50%;
133 | margin-top: -7px;
134 | left: -7px;
135 | }
136 | .tooltipster-arrow-right .tooltipster-arrow-border {
137 | border-top: 9px solid transparent !important;
138 | border-bottom: 9px solid transparent !important;
139 | border-right: 9px solid;
140 | margin-top: -8px;
141 | }
142 |
143 |
144 | /* Some CSS magic for the awesome animations - feel free to make your own custom animations and reference it in your Tooltipster settings! */
145 |
146 | .tooltipster-fade {
147 | opacity: 0;
148 | -webkit-transition-property: opacity;
149 | -moz-transition-property: opacity;
150 | -o-transition-property: opacity;
151 | -ms-transition-property: opacity;
152 | transition-property: opacity;
153 | }
154 | .tooltipster-fade-show {
155 | opacity: 1;
156 | }
157 |
158 | .tooltipster-grow {
159 | -webkit-transform: scale(0,0);
160 | -moz-transform: scale(0,0);
161 | -o-transform: scale(0,0);
162 | -ms-transform: scale(0,0);
163 | transform: scale(0,0);
164 | -webkit-transition-property: -webkit-transform;
165 | -moz-transition-property: -moz-transform;
166 | -o-transition-property: -o-transform;
167 | -ms-transition-property: -ms-transform;
168 | transition-property: transform;
169 | -webkit-backface-visibility: hidden;
170 | }
171 | .tooltipster-grow-show {
172 | -webkit-transform: scale(1,1);
173 | -moz-transform: scale(1,1);
174 | -o-transform: scale(1,1);
175 | -ms-transform: scale(1,1);
176 | transform: scale(1,1);
177 | -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
178 | -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
179 | -moz-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
180 | -ms-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
181 | -o-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
182 | transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
183 | }
184 |
185 | .tooltipster-swing {
186 | opacity: 0;
187 | -webkit-transform: rotateZ(4deg);
188 | -moz-transform: rotateZ(4deg);
189 | -o-transform: rotateZ(4deg);
190 | -ms-transform: rotateZ(4deg);
191 | transform: rotateZ(4deg);
192 | -webkit-transition-property: -webkit-transform, opacity;
193 | -moz-transition-property: -moz-transform;
194 | -o-transition-property: -o-transform;
195 | -ms-transition-property: -ms-transform;
196 | transition-property: transform;
197 | }
198 | .tooltipster-swing-show {
199 | opacity: 1;
200 | -webkit-transform: rotateZ(0deg);
201 | -moz-transform: rotateZ(0deg);
202 | -o-transform: rotateZ(0deg);
203 | -ms-transform: rotateZ(0deg);
204 | transform: rotateZ(0deg);
205 | -webkit-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 1);
206 | -webkit-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
207 | -moz-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
208 | -ms-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
209 | -o-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
210 | transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
211 | }
212 |
213 | .tooltipster-fall {
214 | top: 0;
215 | -webkit-transition-property: top;
216 | -moz-transition-property: top;
217 | -o-transition-property: top;
218 | -ms-transition-property: top;
219 | transition-property: top;
220 | -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
221 | -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
222 | -moz-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
223 | -ms-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
224 | -o-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
225 | transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
226 | }
227 | .tooltipster-fall-show {
228 | }
229 | .tooltipster-fall.tooltipster-dying {
230 | -webkit-transition-property: all;
231 | -moz-transition-property: all;
232 | -o-transition-property: all;
233 | -ms-transition-property: all;
234 | transition-property: all;
235 | top: 0px !important;
236 | opacity: 0;
237 | }
238 |
239 | .tooltipster-slide {
240 | left: -40px;
241 | -webkit-transition-property: left;
242 | -moz-transition-property: left;
243 | -o-transition-property: left;
244 | -ms-transition-property: left;
245 | transition-property: left;
246 | -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
247 | -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
248 | -moz-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
249 | -ms-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
250 | -o-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
251 | transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
252 | }
253 | .tooltipster-slide.tooltipster-slide-show {
254 | }
255 | .tooltipster-slide.tooltipster-dying {
256 | -webkit-transition-property: all;
257 | -moz-transition-property: all;
258 | -o-transition-property: all;
259 | -ms-transition-property: all;
260 | transition-property: all;
261 | left: 0px !important;
262 | opacity: 0;
263 | }
264 |
265 |
266 | /* CSS transition for when contenting is changing in a tooltip that is still open. The only properties that will NOT transition are: width, height, top, and left */
267 | .tooltipster-content-changing {
268 | opacity: 0.5;
269 | -webkit-transform: scale(1.1, 1.1);
270 | -moz-transform: scale(1.1, 1.1);
271 | -o-transform: scale(1.1, 1.1);
272 | -ms-transform: scale(1.1, 1.1);
273 | transform: scale(1.1, 1.1);
274 | }
275 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 | services:
3 | pagegen-webhook:
4 | image: pagegen-webhook:latest
5 | build:
6 | dockerfile: Dockerfile-webhook
7 | context: .
8 | container_name: pagegen-webhook
9 | restart: 'always'
10 | environment:
11 | - TZ=America/Los_Angeles
12 | ports:
13 | - 5000:5000
14 | volumes:
15 | - ./test/maven:/in
16 | - ./test/out:/out
17 | - ./test/config:/config
18 | labels:
19 | traefik.enable: 'true'
20 | traefik.http.routers.pagegen.entrypoints: 'websecure'
21 | traefik.http.routers.pagegen.middlewares: 'pagegen-prefix'
22 | traefik.http.routers.pagegen.rule: 'Host(`webhooks.minecraftforge.net`) && PathPrefix(`/pagegen/`)'
23 | traefik.http.services.pagegen.loadbalancer.server.port: 5000
24 | traefik.http.middlewares.pagegen-prefix.stripprefix.prefixes: '/pagegen'
25 | traefik.http.middlewares.pagegen-prefix.stripprefix.forceSlash: true
26 | pagegen:
27 | image: pagegen:latest
28 | build:
29 | context: .
30 | container_name: pagegen
31 | restart: 'always'
32 | environment:
33 | - TZ=America/Los_Angeles
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftForge/Web/000a9238c05e9f4bcb079260f41be59e0f8d59ae/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
88 |
89 | # Use the maximum available, or set MAX_FD != -1 to use that value.
90 | MAX_FD=maximum
91 |
92 | warn () {
93 | echo "$*"
94 | } >&2
95 |
96 | die () {
97 | echo
98 | echo "$*"
99 | echo
100 | exit 1
101 | } >&2
102 |
103 | # OS specific support (must be 'true' or 'false').
104 | cygwin=false
105 | msys=false
106 | darwin=false
107 | nonstop=false
108 | case "$( uname )" in #(
109 | CYGWIN* ) cygwin=true ;; #(
110 | Darwin* ) darwin=true ;; #(
111 | MSYS* | MINGW* ) msys=true ;; #(
112 | NONSTOP* ) nonstop=true ;;
113 | esac
114 |
115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
116 |
117 |
118 | # Determine the Java command to use to start the JVM.
119 | if [ -n "$JAVA_HOME" ] ; then
120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
121 | # IBM's JDK on AIX uses strange locations for the executables
122 | JAVACMD=$JAVA_HOME/jre/sh/java
123 | else
124 | JAVACMD=$JAVA_HOME/bin/java
125 | fi
126 | if [ ! -x "$JAVACMD" ] ; then
127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
128 |
129 | Please set the JAVA_HOME variable in your environment to match the
130 | location of your Java installation."
131 | fi
132 | else
133 | JAVACMD=java
134 | if ! command -v java >/dev/null 2>&1
135 | then
136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 | fi
142 |
143 | # Increase the maximum file descriptors if we can.
144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145 | case $MAX_FD in #(
146 | max*)
147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148 | # shellcheck disable=SC2039,SC3045
149 | MAX_FD=$( ulimit -H -n ) ||
150 | warn "Could not query maximum file descriptor limit"
151 | esac
152 | case $MAX_FD in #(
153 | '' | soft) :;; #(
154 | *)
155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156 | # shellcheck disable=SC2039,SC3045
157 | ulimit -n "$MAX_FD" ||
158 | warn "Could not set maximum file descriptor limit to $MAX_FD"
159 | esac
160 | fi
161 |
162 | # Collect all arguments for the java command, stacking in reverse order:
163 | # * args from the command line
164 | # * the main class name
165 | # * -classpath
166 | # * -D...appname settings
167 | # * --module-path (only if needed)
168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
169 |
170 | # For Cygwin or MSYS, switch paths to Windows format before running java
171 | if "$cygwin" || "$msys" ; then
172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -classpath "$CLASSPATH" \
214 | org.gradle.wrapper.GradleWrapperMain \
215 | "$@"
216 |
217 | # Stop when "xargs" is not available.
218 | if ! command -v xargs >/dev/null 2>&1
219 | then
220 | die "xargs is not available"
221 | fi
222 |
223 | # Use "xargs" to parse quoted args.
224 | #
225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
226 | #
227 | # In Bash we could simply go:
228 | #
229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
230 | # set -- "${ARGS[@]}" "$@"
231 | #
232 | # but POSIX shell has neither arrays nor command substitution, so instead we
233 | # post-process each arg (as a line of input to sed) to backslash-escape any
234 | # character that might be a shell metacharacter, then use eval to reverse
235 | # that process (while maintaining the separation between arguments), and wrap
236 | # the whole thing up as a single "set" statement.
237 | #
238 | # This will of course break if any of these variables contains a newline or
239 | # an unmatched quote.
240 | #
241 |
242 | eval "set -- $(
243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
244 | xargs -n1 |
245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
246 | tr '\n' ' '
247 | )" '"$@"'
248 |
249 | exec "$JAVACMD" "$@"
250 |
--------------------------------------------------------------------------------
/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 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/images/embed_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftForge/Web/000a9238c05e9f4bcb079260f41be59e0f8d59ae/images/embed_logo.png
--------------------------------------------------------------------------------
/images/embed_logo.svg:
--------------------------------------------------------------------------------
1 |
2 | image/svg+xml
27 |
28 |
29 |
55 |
61 |
64 |
68 |
72 |
76 |
80 |
84 |
89 |
90 |
--------------------------------------------------------------------------------
/images/favicons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftForge/Web/000a9238c05e9f4bcb079260f41be59e0f8d59ae/images/favicons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/images/favicons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftForge/Web/000a9238c05e9f4bcb079260f41be59e0f8d59ae/images/favicons/apple-touch-icon.png
--------------------------------------------------------------------------------
/images/favicons/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | #ff0000
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/images/favicons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftForge/Web/000a9238c05e9f4bcb079260f41be59e0f8d59ae/images/favicons/favicon-16x16.png
--------------------------------------------------------------------------------
/images/favicons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftForge/Web/000a9238c05e9f4bcb079260f41be59e0f8d59ae/images/favicons/favicon-32x32.png
--------------------------------------------------------------------------------
/images/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftForge/Web/000a9238c05e9f4bcb079260f41be59e0f8d59ae/images/favicons/favicon.ico
--------------------------------------------------------------------------------
/images/favicons/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Forge",
3 | "icons": [
4 | {
5 | "src": "\/android-chrome-192x192.png",
6 | "sizes": "192x192",
7 | "type": "image\/png"
8 | }
9 | ],
10 | "theme_color": "#ffffff",
11 | "display": "standalone"
12 | }
13 |
--------------------------------------------------------------------------------
/images/favicons/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftForge/Web/000a9238c05e9f4bcb079260f41be59e0f8d59ae/images/favicons/mstile-144x144.png
--------------------------------------------------------------------------------
/images/favicons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftForge/Web/000a9238c05e9f4bcb079260f41be59e0f8d59ae/images/favicons/mstile-150x150.png
--------------------------------------------------------------------------------
/images/favicons/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftForge/Web/000a9238c05e9f4bcb079260f41be59e0f8d59ae/images/favicons/mstile-310x150.png
--------------------------------------------------------------------------------
/images/favicons/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftForge/Web/000a9238c05e9f4bcb079260f41be59e0f8d59ae/images/favicons/mstile-310x310.png
--------------------------------------------------------------------------------
/images/favicons/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinecraftForge/Web/000a9238c05e9f4bcb079260f41be59e0f8d59ae/images/favicons/mstile-70x70.png
--------------------------------------------------------------------------------
/images/favicons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
9 |
10 |
11 |
13 |
20 |
24 |
30 |
32 |
33 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/images/logo_docs.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
13 |
14 |
15 |
17 |
24 |
28 |
34 |
36 |
37 |
42 |
43 |
44 |
45 |
47 |
54 |
58 |
64 |
66 |
67 |
72 |
73 |
75 |
77 |
79 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/js/curse-ads.js:
--------------------------------------------------------------------------------
1 | // Ad blocking note
2 |
3 | var target = document.querySelector('.promo-container');
4 |
5 | function removeNonClassAttributes(element) {
6 | var attributes = element.attributes;
7 | var i = attributes.length;
8 | while (i--) {
9 | var attr = attributes.item(i);
10 | if (attr.name !== 'class')
11 | element.removeAttributeNode(attr);
12 | }
13 | }
14 |
15 | function showBlockNote() {
16 | var blockNote = document.querySelector('.promo-container .block-note');
17 | removeNonClassAttributes(target);
18 | removeNonClassAttributes(blockNote);
19 | blockNote.classList.add('block-note-show');
20 | blockNote.setAttribute('style', 'display: flex !important');
21 | target.setAttribute('style', 'display: block !important');
22 | }
23 |
24 | function handleAttributeMutation(target, attribute) {
25 | var classes = target.classList;
26 | if (!classes.contains('promo-container') && !classes.contains('promo-content')) {
27 | return;
28 | }
29 | if (attribute === 'hidden') {
30 | showBlockNote();
31 | }
32 | }
33 |
34 | function handleChildRemoval(children) {
35 | children.forEach(
36 | function (child) {
37 | if (child.classList.contains('promo-content')) {
38 | showBlockNote();
39 | }
40 | }
41 | );
42 | }
43 |
44 | // create an observer instance
45 | var observer = new MutationObserver(function (mutations) {
46 | mutations.forEach(function (mutation) {
47 | if (mutation.type === 'attributes') {
48 | handleAttributeMutation(mutation.target, mutation.attributeName);
49 | } else if (mutation.type === 'childList') {
50 | handleChildRemoval(mutation.removedNodes);
51 | }
52 | });
53 | });
54 |
55 | var observerConfig = {attributes: true, childList: true, characterData: true, subtree: true};
56 | if (target)
57 | observer.observe(target, observerConfig);
58 |
59 | window.isDoneLoading = false;
60 | (window.attachEvent || window.addEventListener)('message', function (e) {
61 | document.querySelector('body').classList.add('loading-done');
62 | window.isDoneLoading = true;
63 | });
64 |
65 | setTimeout(
66 | function () {
67 | if (!window.isDoneLoading) {
68 | document.querySelector('body').classList.add('loading-done');
69 | }
70 | },
71 | 1000
72 | );
73 |
74 | $(document).ready(function() {
75 | window.themeSwitchToggle();
76 | });
77 |
--------------------------------------------------------------------------------
/js/docs.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function () {
2 | window.themeSwitchToggle();
3 |
4 | $('pre.highlight code[class*=\'language-\']').each(function () {
5 | var className = this.className.match(/language-([A-Za-z0-9+-]+)/);
6 | if (className) {
7 | $(this).removeClass(className[0]);
8 | $(this).addClass(className[1].toLowerCase());
9 | }
10 | });
11 | hljs.initHighlighting();
12 | });
13 |
--------------------------------------------------------------------------------
/js/files.js:
--------------------------------------------------------------------------------
1 | /*
2 | Implementation for various utilities on the files site.
3 | */
4 |
5 | $(document).ready(function () {
6 | $('[data-toggle=tooltip]').tooltipster({
7 | theme: 'tooltipster-shadow',
8 | position: 'bottom',
9 | animation: 'grow'
10 | });
11 | $('[data-toggle=popup]').tooltipster({
12 | theme: 'tooltipster-shadow',
13 | position: 'bottom',
14 | animation: 'grow',
15 | contentAsHTML: true,
16 | interactive: true
17 | });
18 | $('.download-list .download-links li').each(function () {
19 | if ($(this).find('.info-tooltip').length > 0) {
20 | var info = $(this).children('.info-tooltip');
21 | $(this).children('.info-link').tooltipster('content', info.html());
22 | }
23 | });
24 | $('.info-container').each(function () {
25 | if ($(this).find('.info-tooltip').length > 0) {
26 | var info = $(this).children('.info-tooltip');
27 | var link = $(this).children('.info-link');
28 | link.tooltipster('content', info.html());
29 | ['delay', 'position', 'animation', 'speed'].forEach(function (key) {
30 | if (link.attr('tooltipster-' + key) !== undefined)
31 | link.tooltipster('option', key, link.attr('tooltipster-' + key))
32 | })
33 | }
34 | });
35 |
36 | });
37 |
38 | window.onload = function () {
39 | if (location.hostname != 'files.minecraftforge.net') {
40 | var find = 'files.minecraftforge.net'
41 | var replace = location.hostname
42 |
43 | if (location.protocol == 'file:') {
44 | find = /https?:\/\/files\.minecraftforge\.net/i
45 | replace = location.pathname.substring(0, location.pathname.indexOf('test/out/') + 'test/out'.length)
46 | }
47 | //console.log('Converting hostname from ' + find.toString() + ' to ' + replace)
48 |
49 | var elems = document.getElementsByTagName('a')
50 | for (var i = 0; i < elems.length; i++)
51 | elems[i]['href'] = elems[i]['href'].replace(find, replace)
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/js/google-ads.js:
--------------------------------------------------------------------------------
1 | // Deal with Google ad consent and ad blockers
2 |
3 | function giveConsent() {
4 | localStorage.consent = 'accepted';
5 | window.themeSwitchToggle();
6 | $('.theme-switch-wrapper').removeClass('hidden');
7 | $('.privacy-disclaimer').remove();
8 | (adsbygoogle = window.adsbygoogle || []).requestNonPersonalizedAds = 0;
9 | (adsbygoogle = window.adsbygoogle || []).pauseAdRequests = 0;
10 | }
11 |
12 | function revokeConsent() {
13 | localStorage.consent = 'declined';
14 | $('.privacy-disclaimer').remove();
15 | (adsbygoogle = window.adsbygoogle || []).requestNonPersonalizedAds = 1;
16 | (adsbygoogle = window.adsbygoogle || []).pauseAdRequests = 0;
17 | }
18 |
19 | (adsbygoogle = window.adsbygoogle || []).pauseAdRequests = 1;
20 | $('.adsbygoogle').each(function () {
21 | window.adsbygoogle.push({});
22 | });
23 |
24 | $(document).ready(function() {
25 | $('body').addClass('loading-done');
26 | var consent = localStorage.consent;
27 |
28 | if (consent === 'accepted') {
29 | giveConsent();
30 | } else if (consent === 'declined') {
31 | //$('.theme-switch-wrapper').remove();
32 | revokeConsent();
33 | } else if (localStorage.consent === undefined) {
34 | // Display the disclaimer by default, in case the request fails completely
35 | $('.privacy-disclaimer').css('display', 'block');
36 | $('.theme-switch-wrapper').addClass('hidden');
37 | $.get({url: 'https://ssl.geoplugin.net/json.gp?k=7a35ee7cc6f992e4', dataType: 'json'})
38 | .done(function (data) {
39 | // If we're not in the EU, simply give consent, otherwise keep asking
40 | if (data.geoplugin_inEU !== 1) {
41 | giveConsent();
42 | }
43 | });
44 | }
45 |
46 | $('.btn-privacy-disclaimer-accept').click(function (e) {
47 | e.preventDefault();
48 | giveConsent();
49 | });
50 |
51 | $('.btn-privacy-disclaimer-decline').click(function (e) {
52 | e.preventDefault();
53 | revokeConsent();
54 | });
55 |
56 | function updatePrivacyStatus() {
57 | if (localStorage.consent === 'accepted') {
58 | $('.privacy-status').html('You have accepted personalized ads and usage of local storage.');
59 | } else if (localStorage.consent === 'declined') {
60 | $('.privacy-status').html('You do not allow personalized ads or usage of local storage.');
61 | } else {
62 | $('.privacy-status').text('You have yet to choose one of the options.')
63 | }
64 | }
65 |
66 | updatePrivacyStatus();
67 |
68 | $('.btn-privacy-settings-accept').click(function (e) {
69 | e.preventDefault();
70 | giveConsent();
71 | updatePrivacyStatus();
72 | });
73 |
74 | $('.btn-privacy-settings-decline').click(function (e) {
75 | e.preventDefault();
76 | revokeConsent();
77 | updatePrivacyStatus();
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/js/sidebar.js:
--------------------------------------------------------------------------------
1 | /*
2 | Implementation for sidebars used on the Forge sites.
3 | */
4 | $(document).ready(function () {
5 | $('.scroll-pane').jScrollPane({
6 | autoReinitialise: true,
7 | verticalGutter: 0,
8 | hideFocus: true
9 | });
10 | $('.open-sidebar').click(function (e) {
11 | $('.sidebar-sticky').addClass('active-sidebar');
12 | $('body').addClass('sidebar-active');
13 | e.preventDefault();
14 | });
15 | $('.close-sidebar').click(function (e) {
16 | $('.sidebar-sticky').removeClass('active-sidebar');
17 | $('body').removeClass('sidebar-active');
18 | e.preventDefault();
19 | });
20 | // Collapsible elements implementation
21 | $('.collapsible,.nav-collapsible').each(function () {
22 | var item = $(this);
23 | var toggle = item.parent().find('> .toggle-collapsible');
24 | var showText = item.data('show-text') ? item.data('show-text') : 'Show';
25 | var hideText = item.data('hide-text') ? item.data('hide-text') : 'Hide';
26 | var toggleStyle = item.data('toggle-style') ? item.data('toggle-style') : 'display';
27 | var isCollapsed = function () {
28 | return toggleStyle == 'class' ? item.hasClass('collapsed') : item.css('display') == 'none';
29 | };
30 | var changeState = function (state) {
31 | if (toggleStyle == 'class') {
32 | if (state) {
33 | item.removeClass('collapsed');
34 | item.addClass('expanded');
35 | } else {
36 | item.removeClass('expanded');
37 | item.addClass('collapsed');
38 | }
39 | } else {
40 | item.css('display', state ? 'block' : 'none');
41 | }
42 | };
43 | if (!item.hasClass('nav-collapsible-open')) {
44 | changeState(false);
45 | } else {
46 | changeState(true);
47 | var icons = toggle.find('> .collapsible-icon');
48 | var texts = toggle.find('> .collapsible-text');
49 | icons.removeClass('fa-plus');
50 | icons.addClass('fa-minus');
51 | texts.html(showText);
52 | }
53 | toggle.click(function (e) {
54 | var icon = toggle.find('> .collapsible-icon');
55 | var text = toggle.find('> .collapsible-text');
56 | if (isCollapsed()) {
57 | changeState(true);
58 | icon.addClass('fa-minus');
59 | icon.removeClass('fa-plus');
60 | text.html(hideText);
61 | } else {
62 | changeState(false);
63 | icon.addClass('fa-plus');
64 | icon.removeClass('fa-minus');
65 | text.html(showText);
66 | }
67 | e.preventDefault();
68 | });
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/js/theme-switch-toggle.js:
--------------------------------------------------------------------------------
1 | /*
2 | Enables switching themes on the fly.
3 | Initialize by calling window.themeSwitchToggle() from $(document).ready
4 | */
5 |
6 | window.themeSwitchToggle = function () {
7 | var toggle = $('.theme-switch input');
8 | toggle.prop('checked', localStorage.theme === window.forge.THEME_DARK);
9 | toggle.change(function () {
10 | localStorage.theme = $(this).is(':checked') ? window.forge.THEME_DARK : window.forge.THEME_LIGHT;
11 | window.forge.swapThemeCSS(localStorage.theme);
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/js/theme-switch.js:
--------------------------------------------------------------------------------
1 | /*
2 | Initializes theme switching
3 | Either call before enabling toggler or in the document head
4 | */
5 |
6 | window.forge = {
7 | THEME_LIGHT: 'light',
8 | THEME_DARK: 'dark',
9 | swapThemeCSS: function (activeTheme) {
10 | var stylesheets = document.styleSheets;
11 | var length = stylesheets.length;
12 | var i;
13 |
14 | for(i = 0; i < length; i++) {
15 | var ss = stylesheets[i];
16 | if (!ss.ownerNode.dataset.theme) {
17 | continue;
18 | }
19 | ss.disabled = ss.ownerNode.dataset.theme !== activeTheme;
20 | }
21 |
22 | }
23 | };
24 |
25 | if (!localStorage.theme) {
26 | localStorage.theme = window.forge.THEME_LIGHT;
27 | }
28 |
29 | window.forge.swapThemeCSS(localStorage.theme);
30 |
--------------------------------------------------------------------------------
/python/generators.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import json
3 | import hashlib
4 | import gzip
5 | import zlib
6 | import os
7 | from abc import abstractmethod
8 |
9 | from markdown import markdown
10 |
11 | import metadata
12 | import templates
13 | from mc_version import MCVer
14 |
15 | # Writes the specified data as utf8, only if the associated md5 file is out of date
16 | # This prevents small file churn when generating indexes. Namely version specific meta.json
17 | # files will almost never change. So we don't need to rewrite them every run.
18 | # This helps disk io, as well as web IO as the metadata doesn't change and invalidate caches.
19 | def writeHashed(out, name: str, data, gzip: bool = False, print_path: bool = True):
20 | data_utf8 = data.encode('utf-8')
21 | md5_expected = hashlib.md5(data_utf8).hexdigest()
22 | file = out.joinpath(f'{name}.md5')
23 | md5_actual = None if not file.exists() else file.read_bytes().decode('utf-8')
24 | if not file.exists() or not md5_expected == md5_actual:
25 | if print_path:
26 | print(f' Writing {name} at {out}')
27 | else:
28 | print(f' Writing {name}')
29 | out.mkdir(parents=True, exist_ok=True)
30 | out.joinpath(name).write_bytes(data_utf8)
31 | file.write_text(md5_expected, 'utf-8')
32 | if gzip:
33 | write_gzip(out, name, data_utf8, print_path)
34 |
35 |
36 | def write_gzip(out, name: str, data, print_path: bool):
37 | file = out.joinpath(name)
38 | file_gz = out.joinpath(f'{name}.gz')
39 | if print_path:
40 | print(f' Gzip\'ing {name} at {out}')
41 | else:
42 | print(f' Gzip\'ing {name}')
43 |
44 | with gzip.open(file_gz, 'wb', compresslevel=9) as dst:
45 | dst.write(data)
46 |
47 |
48 | class Generator:
49 | @abstractmethod
50 | def generate(self, md: metadata.Metadata, artifact: metadata.Artifact, tpl: templates.Templates, args: argparse.Namespace):
51 | pass
52 |
53 |
54 | class MetaJsonGenerator(Generator):
55 | def generate(self, md: metadata.Metadata, artifact: metadata.Artifact, tpl: templates.Templates, args: argparse.Namespace):
56 | print(f'Writing meta.jsons')
57 | for version in artifact.all_versions:
58 | meta = {'classifiers': {cls or "null": {item.ext: item.md5} for cls, item in version.item_by_cls.items()}}
59 | out = version.path(root='output_meta')
60 | writeHashed(out, 'meta.json', json.dumps(meta, indent=2), args.gzip, True)
61 |
62 |
63 | class MavenJsonGenerator(Generator):
64 | def generate(self, md: metadata.Metadata, artifact: metadata.Artifact, tpl: templates.Templates, args: argparse.Namespace):
65 | print('Generating Maven Index')
66 | meta = {mc_vers: [value.raw_version for value in vers.values()] for mc_vers, vers in artifact.versions.items()}
67 | out = artifact.path(root='output_meta')
68 | writeHashed(out, 'maven-metadata.json', json.dumps(meta, indent=2), args.gzip)
69 |
70 |
71 | class IndexGenerator(Generator):
72 | def generate(self, md: metadata.Metadata, artifact: metadata.Artifact, tpl: templates.Templates, args: argparse.Namespace):
73 | description = markdown('\n'.join(artifact.config.get('description', [])))
74 |
75 | config_filters = artifact.config.get('filters', {})
76 | filter_list = [{'mc': MCVer(mc_ver), 'filter': config_filters[mc_ver]} for mc_ver in sorted(config_filters, key=lambda v: MCVer(v))]
77 | out = artifact.path(root='output_web')
78 | out.mkdir(parents=True, exist_ok=True)
79 | print(f'Writing index files at {out}')
80 | render_context = {'md': md, 'artifact': artifact, 'description': description, 'filters': filter_list}
81 | template = tpl.env.get_template('base_page.html')
82 | for (file, context) in artifact.parts(render_context):
83 | writeHashed(out, file, template.render(**context), args.gzip, False)
84 |
85 |
86 | class PromoteGenerator(Generator):
87 | def generate(self, md: metadata.Metadata, artifact: metadata.Artifact, tpl: templates.Templates, args: argparse.Namespace):
88 | print(f'Promoting {artifact.name} version {args.version} to {args.type}')
89 | artifact.promote(args.version, args.type)
90 |
91 | slimpromos = {
92 | "homepage": f'{md.web_root}{artifact.mvnpath()}/',
93 | "promos": {}
94 | }
95 | for mcv, vers in artifact.promotions.items():
96 | if mcv == 'default':
97 | for tag, v in vers.items():
98 | slimpromos['promos'][tag] = v.lower()
99 | else:
100 | for tag, v in vers.items():
101 | slimpromos['promos'][f'{mcv}-{tag}'] = v.lower()
102 |
103 | out = artifact.path(root='output_meta')
104 | out.mkdir(parents=True, exist_ok=True)
105 | print(f'Writing promotion files at {out}')
106 | writeHashed(out, 'promotions_slim.json', json.dumps(slimpromos, indent=2), args.gzip, False)
107 |
108 |
109 | class TrackingGenerator(Generator):
110 | def generate(self, md: metadata.Metadata, artifact: metadata.Artifact, tpl: templates.Templates, args: argparse.Namespace):
111 | out = md.path(root='output_meta')
112 | output = out.joinpath('tracked_promotions.json')
113 | print(f'Adding {artifact.name} to tracked list at {output}')
114 | tracked_promos = json.loads(output.read_text('utf-8')) if output.exists() else {}
115 | meta = {
116 | "name": artifact.fullname(),
117 | "last": {
118 | "version": artifact.all_versions[-1].version,
119 | "mc": artifact.all_versions[-1].minecraft_version,
120 | "timestamp": artifact.all_versions[-1].timestamp.timestamp()
121 | }
122 | }
123 | tracked_promos[artifact.mavenname()] = meta
124 | writeHashed(out, os.path.basename(output), json.dumps(tracked_promos, indent=2), args.gzip, False)
125 |
126 |
127 | class PromotionIndexGenerator(Generator):
128 | def generate(self, md: metadata.Metadata, artifact: metadata.Artifact, tpl: templates.Templates, args: argparse.Namespace):
129 | out = md.path(root='output_web')
130 | output = out.joinpath('project_index.html')
131 | print(f'Generating project index at {output}')
132 | promos = md.path(root='output_meta').joinpath('tracked_promotions.json')
133 | tracked_promos = json.loads(promos.read_text('utf-8')) if promos.exists() else {}
134 | template = tpl.env.get_template('project_index.html')
135 | writeHashed(out, os.path.basename(output), template.render(md=md, promos=tracked_promos), args.gzip, True)
136 |
137 |
138 | class RegenGenerator(Generator):
139 | def generate(self, md: metadata.Metadata, artifact: metadata.Artifact, tpl: templates.Templates, args: argparse.Namespace):
140 | tracked = md.path(root='output_meta').joinpath('tracked_promotions.json')
141 | print(f'Re-Generating all projects')
142 | if (not tracked.exists()):
143 | print(f'No tracked projects at {tracked}')
144 | return
145 | for key in json.loads(tracked.read_text('utf-8')):
146 | art = metadata.Artifact.load_maven_xml(md, key)
147 | for gen in Generators['gen']:
148 | gen.generate(md, art, tpl, args)
149 | for gen in Generators['index']:
150 | gen.generate(md, None, tpl, args)
151 |
152 | basegens = [MetaJsonGenerator(), MavenJsonGenerator(), IndexGenerator()]
153 | Generators: dict[str, list[Generator]] = {
154 | 'gen': basegens,
155 | 'promote': [PromoteGenerator(), TrackingGenerator(), PromotionIndexGenerator(), *basegens],
156 | 'index': [PromotionIndexGenerator()],
157 | 'regen': [RegenGenerator()]
158 | }
159 |
--------------------------------------------------------------------------------
/python/mc_version.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class MCVer:
5 | type = 0
6 | week = 0
7 | year = 0
8 | pre = 0
9 | rev = None
10 | near = []
11 | full = None
12 | special = None
13 |
14 | def __init__(self, version):
15 | self.full = version
16 | lower = version.lower().replace('-', '_')
17 | if 'default' == lower: # Not a MC Version, sort bottom
18 | pass
19 | elif '15w14a' == lower: # 2015 April Fools
20 | self.week = 14
21 | self.year = 15
22 | self.type = 3
23 | self.rev = 'a'
24 | self.near = [1, 10]
25 | self.special = Special.APRIL
26 | elif '1.rv_pre1' == lower: # 2016 April Fools
27 | self.week = 14
28 | self.year = 16
29 | self.type = 3
30 | self.rev = chr(ord('a') - 1)
31 | self.near = [1, 9, 3]
32 | self.special = Special.APRIL
33 | elif '3d shareware v1.34' == lower: # 2019 April Fools
34 | self.week = 14
35 | self.year = 19
36 | self.type = 3
37 | self.rev = chr(ord('a') - 1)
38 | self.near = [1, 14]
39 | self.special = Special.APRIL
40 | elif '20w14infinite' == lower: # 2020 April Fools
41 | self.week = 14
42 | self.year = 20
43 | self.type = 3
44 | self.rev = chr(ord('a') - 1)
45 | self.near = [1, 16]
46 | self.special = Special.APRIL
47 | elif '22w13oneblockatatime' == lower: # 2022 April Fools
48 | self.week = 13
49 | self.year = 22
50 | self.type = 3
51 | self.rev = 'b'
52 | self.near = [1, 19]
53 | self.special = Special.APRIL
54 | elif '23w13a_or_b' == lower: # 2023 April Fools
55 | self.week = 13
56 | self.year = 23
57 | self.type = 3
58 | self.rev = 'b'
59 | self.near = [1, 20]
60 | self.special = Special.APRIL
61 | elif 'inf_20100618' == lower:
62 | self.week = 25
63 | self.year = 10
64 | self.type = 1
65 | self.rev = 'a'
66 | self.near = [1, 0, 4]
67 | elif 'c0.0.13a_03' == lower:
68 | self.week = -1
69 | self.year = -1
70 | self.type = 1
71 | self.rev = chr(ord('a') - 1)
72 | self.near = [0, 0, 13]
73 | elif lower.startswith('rd_'):
74 | self.week = 20
75 | self.year = 9
76 | self.type = 1
77 |
78 | if 'rd_132211' == lower: self.rev = 'a'
79 | if 'rd_132328' == lower: self.rev = 'b'
80 | if 'rd_20090515' == lower: self.rev = 'c'
81 | if 'rd_160052' == lower: self.rev = 'd'
82 | if 'rd_161348' == lower: self.rev = 'e'
83 |
84 | self.near = [0, 0, 1]
85 | elif version[0] == 'a' or version[0] == 'b' or version[0] == 'c':
86 | clean = version[1:].replace('_', '.')
87 | self.type = 2 if version[0] == 'b' else 1
88 | if clean[-1] < '0' or clean[-1] > '9':
89 | self.rev = clean[-1]
90 | self.near = self.splitDots(clean[0:-1])
91 | else:
92 | self.near = self.splitDots(clean)
93 | elif len(version) == 6 and version[2] == 'w':
94 | self.year = int(version[0:2])
95 | self.week = int(version[3:5])
96 | self.type = 3
97 | self.rev = version[5:]
98 | self.near = self.splitDots(self.fromSnapshot(self.year, self.week))
99 | else:
100 | self.type = 4
101 | for suffix in ['_pre_release_', ' pre_release ', ' Pre-Release ', '_pre', '-pre', '-rc']:
102 | if suffix in self.full:
103 | pts = self.full.split(suffix)
104 | self.pre = int(pts[1])
105 | if suffix == '-rc':
106 | self.pre *= -1
107 | self.near = self.splitDots(pts[0])
108 | break
109 |
110 | if self.near == []:
111 | self.near = self.splitDots(self.full)
112 |
113 | def splitDots(self, ver):
114 | return [int(i) for i in ver.split('.')]
115 |
116 | class RangedDict(dict):
117 | def __getitem__(self, item):
118 | if not isinstance(item, range):
119 | for key in self:
120 | if item in key:
121 | return self[key]
122 | raise KeyError(item)
123 | else:
124 | return super().__getitem__(item)
125 |
126 | SNAPSHOT_RANGES = RangedDict({
127 | range(1147, 1202): '1.1',
128 | range(1203, 1209): '1.2',
129 | range(1215, 1231): '1.3',
130 | range(1232, 1243): '1.4',
131 | range(1249, 1251): '1.4.6',
132 | range(1301, 1311): '1.5',
133 | range(1311, 1313): '1.5.1',
134 | range(1316, 1327): '1.6',
135 | range(1336, 1344): '1.7',
136 | range(1347, 1350): '1.7.4',
137 | range(1402, 1435): '1.8',
138 | range(1531, 1608): '1.9',
139 | range(1614, 1616): '1.9.3',
140 | range(1620, 1622): '1.10',
141 | range(1632, 1645): '1.11',
142 | range(1650, 1651): '1.11.1',
143 | range(1706, 1719): '1.12',
144 | range(1731, 1732): '1.12.1',
145 | range(1743, 1823): '1.13',
146 | range(1830, 1834): '1.13.1',
147 | range(1843, 1915): '1.14',
148 | range(1934, 1947): '1.15',
149 | range(2006, 2023): '1.16',
150 | range(2027, 2031): '1.16.2',
151 | range(2045, 2121): '1.17',
152 | range(2137, 2145): '1.18',
153 | range(2203, 2208): '1.18.2',
154 | range(2211, 2220): '1.19',
155 | range(2224, 2225): '1.19.1',
156 | range(2242, 2247): '1.19.3',
157 | range(2303, 2308): '1.19.4',
158 | range(2312, 9999): '1.20'
159 | })
160 |
161 | def fromSnapshot(self, year, week):
162 | value = (year * 100) + week
163 | if ver := self.SNAPSHOT_RANGES[value]:
164 | return ver
165 | raise Exception(f'Invalid snapshot date: {value}')
166 |
167 | def compareStr(self, s1, s2):
168 | return (s1 > s2) - (s1 < s2)
169 |
170 | def compareFull(self, o):
171 | for i in range(len(self.near)):
172 | if i >= len(o.near): return 1
173 | if self.near[i] != o.near[i]: return self.near[i] - o.near[i]
174 | return 0 if len(o.near) == len(self.near) else -1
175 |
176 | def compare(self, o):
177 | if self.type != o.type:
178 | if self.type <= 2 or o.type <= 2: return self.type - o.type
179 | if self.type == 3: return -1 if self.compareFull(o) == 0 else self.compareFull(o)
180 | return 1 if self.compareFull(o) == 0 else self.compareFull(o)
181 |
182 | if self.type == 1 or self.type == 2:
183 | ret = self.compareFull(o)
184 | if ret != 0: return ret
185 | if self.rev == None and o.rev != None: return 1
186 | if self.rev != None and o.rev == None: return -1
187 | return self.compareStr(self.rev, o.rev)
188 |
189 | elif self.type == 3:
190 | if self.year != o.year: return self.year - o.year
191 | if self.week != o.week: return self.week - o.week
192 | return self.compareStr(self.rev, o.rev)
193 |
194 | ret = self.compareFull(o)
195 | if ret != 0: return ret
196 | self_pre_type = min(1, max(-1, self.pre))
197 | o_pre_type = min(1, max(-1, o.pre))
198 | if self_pre_type == o_pre_type: return abs(self.pre) - abs(o.pre)
199 | if self_pre_type == 0: return 1
200 | if o_pre_type == 0: return -1
201 | if self_pre_type == 1: return -1
202 | return 1
203 |
204 | def getFullRelease(self):
205 | return '.'.join(map(str, self.near)) if len(self.near) > 0 else self.full
206 |
207 | def __lt__(self, other):
208 | return self.compare(other) < 0
209 |
210 | def __gt__(self, other):
211 | return self.compare(other) > 0
212 |
213 | def __eq__(self, other):
214 | return self.compare(other) == 0
215 |
216 | def __le__(self, other):
217 | return self.compare(other) <= 0
218 |
219 | def __ge__(self, other):
220 | return self.compare(other) >= 0
221 |
222 | def __ne__(self, other):
223 | return self.compare(other) != 0
224 |
225 | def __repr__(self):
226 | return self.full
227 |
228 | class Special(Enum):
229 | APRIL = "April Fools"
--------------------------------------------------------------------------------
/python/metadata.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import datetime
4 | import json
5 | import pathlib
6 | import re
7 | from collections import defaultdict
8 | from dataclasses import dataclass, field, InitVar
9 |
10 | import requests
11 |
12 | from mc_version import MCVer
13 | from pprint import pprint
14 | import xml.etree.ElementTree as elementtree
15 |
16 | MINECRAFT_FORMAT = '(?P1(?:\.\d+){1,2}?(?:[_\-](?:pre|rc)\d+)?|\d\dw\d\d\w+)'
17 | PROMOTION_REG = re.compile(r'^' + MINECRAFT_FORMAT + '-(?P[\w]+)$')
18 | VERSION_REG = re.compile(r'^(?:' + MINECRAFT_FORMAT + '-)?(?P(?:\w+(?:\.|\+))*[\d]+)-?(?P[\w\.\-]+)?$')
19 |
20 | def parse_version(version):
21 | return (versmatch.groupdict().get('mcversion') or 'default', versmatch.group('version'), versmatch.group('branch')) if (versmatch := VERSION_REG.match(version)) else ('default', version, None)
22 |
23 |
24 | def parse_artifact(artifact: str):
25 | return artifact.rsplit(":")
26 |
27 |
28 | SKIP_SUFFIXES = {'.pom', '.sha1', '.md5', '.url', '.asc', '.sha256', '.sha512', '.module'}
29 |
30 |
31 | @dataclass
32 | class Metadata:
33 | path_root: pathlib.Path
34 | output_meta: pathlib.Path
35 | output_web: pathlib.Path
36 | web_root: str
37 | dl_root: str
38 | static_root: str
39 | config_path: InitVar[pathlib.Path] = None
40 | local_data: bool = False
41 | global_config: dict = field(default_factory=dict)
42 | empty_root: pathlib.Path = pathlib.Path('/')
43 |
44 | def __post_init__(self, config_path):
45 | self.global_config = json.loads(config_path.read_bytes())
46 |
47 | def path(self, root='path_root', *path):
48 | return getattr(self, root).joinpath(*path)
49 |
50 |
51 | @dataclass
52 | class ArtifactItem:
53 | """Artifact item"""
54 | version: ArtifactVersion
55 | filepath: pathlib.Path
56 | name: str
57 | classifier: str
58 | ext: str
59 | relative_path: str
60 | timestamp: datetime.datetime = field(init=False)
61 | sha1: str = ''
62 | md5: str = ''
63 |
64 | def __post_init__(self):
65 | self.timestamp = datetime.datetime.fromtimestamp(self.filepath.stat().st_mtime)
66 | self.sha1 = f.read_text('utf-8') if (f := self.filepath.parent.joinpath(self.name+'.sha1')).exists() else None
67 | self.md5 = f.read_text('utf-8') if (f := self.filepath.parent.joinpath(self.name+'.md5')).exists() else None
68 |
69 | @classmethod
70 | def load(cls, artifact_version, file):
71 | if file.suffix in SKIP_SUFFIXES:
72 | return None
73 | if not file.stem.startswith(artifact_version.full_name()):
74 | return None
75 | return ArtifactItem(artifact_version, file, file.name, pfx[1:] if len(pfx := file.stem.removeprefix(artifact_version.full_name())) > 0 else '', file.suffix[1:], str(file.relative_to(artifact_version.artifact.metadata.path_root)))
76 |
77 |
78 | @dataclass
79 | class ArtifactVersion:
80 | """Artifact Version"""
81 | artifact: Artifact
82 | raw_version: str
83 | version: str
84 | minecraft_version: str or None
85 | branch: str or None
86 | promotion_tags: list[str] = field(init=False, default_factory=list)
87 | items: list[ArtifactItem] = field(init=False)
88 | item_by_cls: dict[str, ArtifactItem] = field(init=False)
89 | timestamp: datetime.datetime = None
90 |
91 | @classmethod
92 | def load(cls, artifact, version):
93 | (mcvers, vers, branch) = parse_version(version)
94 | if vers.endswith('-SNAPSHOT') or branch == 'SNAPSHOT':
95 | return
96 | if not (d := artifact.path().joinpath(version)).exists():
97 | return
98 | av = ArtifactVersion(artifact, version, vers, mcvers, branch)
99 | av.items = [ai for file in d.iterdir() if (ai := ArtifactItem.load(av, file)) is not None]
100 | if not av.items:
101 | return
102 | av.item_by_cls = {ai.classifier: ai for ai in av.items}
103 | av.timestamp = min(ai.timestamp for ai in av.items)
104 | return av
105 |
106 | def path(self, root='path_root') -> pathlib.Path:
107 | return self.artifact.path(root).joinpath(self.raw_version)
108 |
109 | def full_name(self):
110 | return f'{self.artifact.name}-{self.raw_version}'
111 |
112 | def has_promotions(self):
113 | return len(self.promotion_tags) > 0
114 |
115 |
116 | @dataclass
117 | class Artifact:
118 | """Artifact"""
119 | metadata: Metadata
120 | group: str
121 | name: str
122 | versions: dict[str, dict[str, ArtifactVersion]] = field(init=False)
123 | all_versions: list[ArtifactVersion] = field(init=False)
124 | promotions: dict = field(init=False, default_factory=lambda: defaultdict(lambda: defaultdict(str)))
125 | config: dict = field(init=False, default_factory=dict)
126 |
127 | def fullname(self):
128 | return self.config.get('name', self.name)
129 |
130 | def mavenname(self):
131 | return f'{self.group}:{self.name}'
132 |
133 | def path(self, root='path_root') -> pathlib.Path:
134 | return self.metadata.path(root, self.group.replace('.', '/'), self.name)
135 |
136 | def mvnpath(self):
137 | return self.group.replace('.', '/') + '/' + self.name
138 |
139 | def attach_promotions(self, promotions):
140 | for key, value in promotions.get('promos', {}).items():
141 | mc_ver = 'default'
142 | tag = key.lower()
143 |
144 | if mc_match := PROMOTION_REG.match(key):
145 | mc_ver = mc_match.group('mcversion')
146 | tag = mc_match.group('tag').lower()
147 |
148 | if mc_ver in self.versions:
149 | if v := self.versions[mc_ver].get(value):
150 | if not mc_ver in self.promotions:
151 | self.promotions[mc_ver] = {}
152 | self.promotions[mc_ver][tag] = value
153 | v.promotion_tags.append(tag)
154 |
155 | def find_version(self, version: str) -> ArtifactVersion:
156 | v = next((v for v in self.all_versions if v.version == version), None)
157 | if not v:
158 | print(f'Failed to find {version} in {self.name}')
159 | print('Known Versions:')
160 | for kv in self.all_versions:
161 | print(f' {kv.version}')
162 | raise ValueError(f'Failed to find {version} in {self.name}')
163 | return v
164 |
165 | def promote(self, version, tag):
166 | (mc_vers, ver, branch) = parse_version(version)
167 | tag = tag.lower()
168 | v = self.find_version(ver)
169 |
170 | if not v.minecraft_version in self.promotions:
171 | self.promotions[v.minecraft_version] = {}
172 |
173 | if existing := self.promotions[v.minecraft_version].get(tag):
174 | self.find_version(existing).promotion_tags.remove(tag)
175 |
176 | self.promotions[v.minecraft_version][tag] = ver
177 | v.promotion_tags.append(tag)
178 |
179 | def attach_config(self, config):
180 | self.config.update(config)
181 |
182 | def parts(self, global_context: dict):
183 | if len(self.versions) > 1:
184 | sorted_mc_versions = sorted(self.versions.keys(), reverse=True, key=lambda a: MCVer(a))
185 | release_mc_versions = dict()
186 | for mc_version in sorted_mc_versions:
187 | release_mc_version = self.get_release_milestone(MCVer(mc_version))
188 | if release_mc_version not in release_mc_versions:
189 | release_mc_versions[release_mc_version] = []
190 | release_mc_versions[release_mc_version].append(mc_version)
191 | first_idx = next((mc for mc in sorted_mc_versions if 'latest' in self.promotions.get(mc, [])), sorted_mc_versions[0])
192 | yield 'index.html', global_context | {'mc_version': first_idx, 'release_mc_version': self.get_release_milestone(MCVer(first_idx)), 'mcversions': sorted_mc_versions, 'release_mcversions': release_mc_versions}
193 | for mc_version in sorted_mc_versions:
194 | yield f'index_{mc_version}.html', global_context | {'mc_version': mc_version, 'release_mc_version': self.get_release_milestone(MCVer(mc_version)), 'mcversions': sorted_mc_versions, 'release_mcversions': release_mc_versions, 'canonical_url': '' if mc_version == first_idx else f'index_{mc_version}.html'}
195 | elif len(self.versions) == 1 and not 'default' in self.versions:
196 | yield 'index.html', global_context | {'mc_version': list(self.versions.keys())[0]}
197 | else:
198 | yield 'index.html', global_context
199 |
200 | def get_release_milestone(self, release_version):
201 | if release_version.special: return release_version.special.value
202 | release_mc_version = release_version.getFullRelease()
203 | split = release_mc_version.split('.')
204 | return split[0] + '.' + split[1] if len(split) > 1 else split[0]
205 |
206 | @classmethod
207 | def load_maven_xml(cls, metadata: Metadata, artifact):
208 | (group, name) = parse_artifact(artifact)
209 | artifact = Artifact(metadata, group, name)
210 | try:
211 | # Reposilite doesn't flus the maven-metadata.xml file to disk fast enough for us to see it after a new
212 | # build is coreated. So we hackily gather the data over the web. Which is stupid. But I'll fix this
213 | # after I rebuild the reposilite stack
214 | if (metadata.local_data and (meta_file := artifact.path().joinpath('maven-metadata.xml')).exists()):
215 | versions = [v.text for v in elementtree.parse(meta_file).findall("./versioning/versions/version")]
216 | else:
217 | if (xmlresponse := requests.get(f'{metadata.dl_root}{artifact.mvnpath()}/maven-metadata.xml')).status_code == 200:
218 | versions = [v.text for v in elementtree.fromstring(xmlresponse.content).findall("./versioning/versions/version")]
219 | else:
220 | raise RuntimeError(f'Failed to read maven-metadata from {metadata.dl_root}{artifact.mvnpath()}/maven-metadata.xml')
221 |
222 | artifact.all_versions = sorted((av for v in versions if (av := ArtifactVersion.load(artifact, v)) is not None), key=lambda v: v.timestamp)
223 | mc_versions = (av.minecraft_version for av in artifact.all_versions)
224 | artifact.versions = {mc_version: {av.version: av for av in artifact.all_versions if av.minecraft_version == mc_version} for mc_version in mc_versions}
225 | promotions = {}
226 | if (promotions_file := artifact.path().joinpath('promotions_slim.json')).exists():
227 | promotions |= json.loads(promotions_file.read_bytes())
228 | if (promotions_file := artifact.path(root='output_meta').joinpath('promotions_slim.json')).exists():
229 | promotions |= json.loads(promotions_file.read_bytes())
230 | artifact.attach_promotions(promotions)
231 | # load local config
232 | if (config_file := artifact.path().joinpath('page_config.json')).exists():
233 | artifact.attach_config(json.loads(config_file.read_bytes()))
234 | # overwrite global config values
235 | artifact.attach_config(metadata.global_config)
236 | except Exception as e:
237 | raise RuntimeError('Failed to load and parse maven-metadata.xml', e)
238 | return artifact
239 |
--------------------------------------------------------------------------------
/python/page_generator.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import pathlib
3 | import sys
4 |
5 | from metadata import Metadata, Artifact
6 | from templates import Templates
7 | from generators import Generators
8 |
9 | def parse_path(f):
10 | if (p := pathlib.Path(f).absolute()).exists():
11 | return p
12 | else:
13 | raise ValueError('The path is missing')
14 |
15 | def main(raw_args=None):
16 | parser = argparse.ArgumentParser(description='Maven based download index generator')
17 | parser.add_argument('--webout', dest='output_web', default='/out', help='Base directory to output generated index pages. Will generate in sub-directories based on the maven path', type=parse_path)
18 | parser.add_argument('--metaout', dest='output_meta', default='/out', help='Base directory to output generated metadata. Will generate in sub-directories based on the maven path', type=parse_path)
19 | parser.add_argument('--downloadroot', dest='dlroot', default='https://maven.minecraftforge.net/', help='Root URL for downloading artifacts')
20 | parser.add_argument('--webroot', dest='webroot', default='https://files.minecraftforge.net/', help='Root URL for artifact pages')
21 | parser.add_argument('--static', dest='static', default='https://files.minecraftforge.net/static/', help='Root URL for static assets used by the templates')
22 |
23 | parser.add_argument('--folder', dest='folder', default='/in/repositories/releases/', help='Root directory for the maven structure to read metadata from files', type=parse_path)
24 | parser.add_argument('--config', dest='config', default='/in/global_overrides.json', help="Location of global_overrides.json file", type=parse_path)
25 | parser.add_argument('--templates', dest='templates', default='templates', type=parse_path, help="Path to templates")
26 | parser.add_argument('--local-data', dest='localdata', default=False, action=argparse.BooleanOptionalAction)
27 | parser.add_argument('--gzip', dest='gzip', default=True, action=argparse.BooleanOptionalAction, help='Whether or not to create gzip archives of generated files')
28 |
29 | commands = parser.add_subparsers(help='Command to perform', dest='command', required=True)
30 |
31 | gen_command = commands.add_parser('gen', help='Indexes generator subcommand')
32 | gen_command.add_argument('artifact', help='Maven Artifact - net.minecraftforge:forge')
33 |
34 | index_command = commands.add_parser('index', help='Generate tracked project index')
35 | regen_command = commands.add_parser('regen', help='ReGenerate all tracked projects')
36 |
37 | promote_command = commands.add_parser('promote', help='Promote subcommand')
38 | promote_command.add_argument('artifact', help='Maven Artifact - net.minecraftforge:forge')
39 | promote_command.add_argument('version', help='Maven Version')
40 | promote_command.add_argument('type', choices=['latest', 'recommended'], help='Type of promotion')
41 |
42 | if raw_args == None:
43 | raw_args = sys.argv[1:]
44 | args = parser.parse_args(raw_args)
45 |
46 | if (args.webroot[-1] != '/'): args.webroot += '/'
47 | if (args.dlroot[-1] != '/'): args.dlroot += '/'
48 | if (args.static[-1] != '/'): args.static += '/'
49 |
50 | print('Page Generator:')
51 | print(f'PyVer: {sys.version}')
52 | print(f'Folder: {args.folder}')
53 | print(f'Config: {args.config}')
54 | print(f'Web Out: {args.output_web}')
55 | print(f'Meta Out: {args.output_meta}')
56 | print(f'WebRoot: {args.webroot}')
57 | print(f'LocalData:{args.localdata}')
58 | print(f'Gzip: {args.gzip}')
59 | print(f'DLRoot: {args.dlroot}')
60 | print(f'Static: {args.static}')
61 | print(f'Templates:{args.templates}')
62 | print(f'Command: {args.command}')
63 | print(f'Artifact: {args.artifact if "artifact" in args else None}')
64 | print(f'Version: {args.version if "version" in args else None}')
65 | print(f'Type: {args.type if "type" in args else None}')
66 |
67 | metadata = Metadata(args.folder, args.output_meta, args.output_web, args.webroot, args.dlroot, args.static, args.config, args.localdata)
68 | artifact = Artifact.load_maven_xml(metadata, args.artifact) if 'artifact' in args else None
69 | templates = Templates(args.templates, args.static, args.webroot, args.dlroot)
70 |
71 | for gen in Generators[args.command]:
72 | gen.generate(metadata, artifact, templates, args)
73 |
74 | print('Finished')
75 |
76 | if __name__ == '__main__':
77 | main()
78 |
--------------------------------------------------------------------------------
/python/page_generator_service.py:
--------------------------------------------------------------------------------
1 | import waitress
2 | import logging
3 | import os
4 | import time
5 | import json
6 | import base64
7 | from threading import Thread
8 | from enum import Enum
9 | from flask import Flask, request
10 | from queue import PriorityQueue
11 | from pprint import pprint
12 | from paste.translogger import TransLogger
13 |
14 | import page_generator
15 |
16 | logging.basicConfig(level = logging.NOTSET)
17 | logging.getLogger('waitress').setLevel(logging.INFO)
18 |
19 | logger = logging.getLogger('pagegen')
20 | logger.setLevel(logging.DEBUG)
21 |
22 | app = Flask(__name__)
23 | app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 # Posts should never need more then 1KB
24 |
25 | tasks = PriorityQueue()
26 | users = {}
27 | config = {}
28 |
29 | TaskTypes = Enum('TaskTypes', ['PROMOTE', 'REGEN_INDEX', 'GEN', 'REGEN'])
30 |
31 | class Task:
32 | def __init__(self, type, data):
33 | self.type = type
34 | self.data = data
35 |
36 | def __repr__(self):
37 | return "Task(type={}, data={})".format(self.type, self.data)
38 |
39 | def __gt__(self, other):
40 | return self.type.value > other.type.value
41 | def __lt__(self, other):
42 | return self.type.value < other.type.value
43 | def __eq__(self, other):
44 | return self.type.value == other.type.value
45 |
46 | @app.route('/promote', methods = ['POST'])
47 | @app.route('/promote////', methods = ['GET'])
48 | def promote(type=None, group=None, artifact=None, version=None):
49 | data = {}
50 | if request.method == 'GET':
51 | data = {
52 | 'type': type,
53 | 'group': group.replace('/', '.'),
54 | 'artifact': artifact,
55 | 'version': version
56 | }
57 | elif request.method == 'POST':
58 | data = request.get_json()
59 | else:
60 | return 'Unsupported Request', 404
61 |
62 | if data.get('type') == None or data.get('group') == None or data.get('artifact') == None or data.get('version') == None:
63 | return "Missing promotion target: {}".format(data), 400
64 |
65 | if not data['type'] in ['latest', 'recommended']:
66 | return "Invalid promotion type {} only latest or recommended supported".format(data["type"]), 400
67 |
68 | loginResponse = checkAccess(data)
69 | if loginResponse:
70 | return loginResponse
71 |
72 | logger.info("Queueing Promotion: {}:{}:{} {}".format(data['group'], data['artifact'], data['version'], data['type']))
73 | tasks.put(Task(TaskTypes.PROMOTE, data))
74 |
75 | return 'Promotion Queued'
76 |
77 | @app.route('/gen', methods = ['POST'])
78 | @app.route('/gen//', methods = ['GET'])
79 | def gen(group=None, artifact=None):
80 | data = {}
81 | if request.method == 'GET':
82 | data = {
83 | 'group': group.replace('/', '.'),
84 | 'artifact': artifact
85 | }
86 | elif request.method == 'POST':
87 | data = request.get_json()
88 | else:
89 | return 'Unsupported Request', 404
90 |
91 | if data.get('group') == None or data.get('artifact') == None:
92 | return "Missing generator target: {}".format(data), 400
93 |
94 | loginResponse = checkAccess(data)
95 | if loginResponse:
96 | return loginResponse
97 |
98 | logger.info("Queueing Gen: {}:{}".format(data['group'], data['artifact']))
99 | tasks.put(Task(TaskTypes.GEN, data))
100 |
101 | return 'Generation Queued'
102 |
103 | @app.route('/regen', methods = ['GET'])
104 | def regen():
105 | loginResponse = checkAccess({'group': '', 'artifact': ''})
106 | if loginResponse:
107 | return loginResponse
108 |
109 | logger.info("Queueing Complete Regen")
110 | tasks.put(Task(TaskTypes.REGEN, {}))
111 |
112 | return 'Complete Generation Queued'
113 |
114 | @app.route('/regen-index', methods = ['GET'])
115 | def regen_index():
116 | loginResponse = checkAccess({'group': '', 'artifact': ''})
117 | if loginResponse:
118 | return loginResponse
119 |
120 | logger.info("Queueing Index Regen")
121 | tasks.put(Task(TaskTypes.REGEN_INDEX, {}))
122 |
123 | return 'Index Generation Queued'
124 |
125 | def process_queue():
126 | while tasks:
127 | task = tasks.get()
128 | logger.info(task)
129 | data = task.data
130 | key = task.type.name.lower()
131 | args = []
132 | if key in config and 'args' in config[key]:
133 | args += config[key]['args']
134 |
135 | if task.type == TaskTypes.PROMOTE:
136 | logger.info('Promoting %s:%s', data['group'], data['artifact'])
137 | args += ['promote', data['group'] + ':' + data['artifact'], data['version'], data['type']]
138 | elif task.type == TaskTypes.REGEN:
139 | logger.info('Regnerating Everything')
140 | args += ['regen']
141 | elif task.type == TaskTypes.REGEN_INDEX:
142 | logger.info('Regnerating Tracked Index')
143 | args += ['index']
144 | elif task.type == TaskTypes.GEN:
145 | logger.info('Generating %s:%s', data['group'], data['artifact'])
146 | args += ['gen', data['group'] + ':' + data['artifact']]
147 |
148 | try:
149 | page_generator.main(args)
150 | except Exception as e:
151 | logger.error('Failed to run generator: %s', e)
152 |
153 | logger.error("Task queue ended")
154 |
155 | def load_users():
156 | global users
157 | logger.info('Loading users...')
158 | if not os.path.isfile('/config/users.json'):
159 | raise 'Missing /config/users.json, No authentication will work'
160 |
161 | with open('/config/users.json', 'r') as f:
162 | users = json.load(f)
163 |
164 | logger.info("Loaded %d users", len(users))
165 |
166 | def load_config():
167 | global config
168 | logger.info('Loading config...')
169 | if not os.path.isfile('/config/config.json'):
170 | logger.warning('Missing /config/config.json, Its non critical for now')
171 | return
172 |
173 | with open('/config/config.json', 'r') as f:
174 | config = json.load(f)
175 |
176 | logger.info('Loaded config file')
177 |
178 | def checkAccess(data):
179 | global users
180 |
181 | if not 'Authorization' in request.headers:
182 | logger.info('Ignoring anonymous user')
183 | return '', 401, {'WWW-Authenticate': 'BASIC realm="Forge Maven"'}
184 |
185 | auth = request.headers.get('Authorization')
186 |
187 | if (auth.startswith('Basic ')):
188 | try:
189 | auth = base64.b64decode(auth[6:]).decode('utf-8')
190 | except TypeError:
191 | return 'Failed to decode base auth', 401, {'WWW-Authenticate': 'BASIC realm="Forge Maven"'}
192 |
193 | user,password = auth.split(':', 1)
194 | if not user in users:
195 | logger.info("Unknown user: %s", user)
196 | return 'Unauthorized', 401, {'WWW-Authenticate': 'BASIC realm="Forge Maven"'}
197 |
198 | if not users[user]['password'] == password:
199 | logger.info('User %s failed to login, invalid password', user)
200 | return 'Unauthorized', 401, {'WWW-Authenticate': 'BASIC realm="Forge Maven"'}
201 |
202 | success = False
203 | for access in users[user]['access']:
204 | if access['group'] == data['group'] or access['group'] == '':
205 | if access['artifact'] == data['artifact'] or access['artifact'] == '':
206 | success = True
207 | break
208 |
209 | if not success:
210 | logger.info('User %s does not have access to %s:%s', user, data['group'], data['artifact'])
211 | return 'Unauthorized', 401, {'WWW-Authenticate': 'BASIC realm="Forge Maven"'}
212 |
213 | logger.info('User %s permitted to access %s:%s', user, data['group'], data['artifact'])
214 | return None
215 |
216 | if __name__ == "__main__":
217 | load_config()
218 | load_users()
219 |
220 | processor = Thread(target=process_queue)
221 | processor.start()
222 | #app = TransLogger(app, setup_console_handler=False)
223 | waitress.serve(app, host='0.0.0.0', port=5000)
--------------------------------------------------------------------------------
/python/templates.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import itertools
3 | import pathlib
4 | import re
5 | import requests
6 | import zlib
7 |
8 | import jinja2
9 | import markupsafe
10 |
11 | from mc_version import MCVer
12 | from metadata import Artifact
13 |
14 | @jinja2.pass_context
15 | def show_classifier(context, mc_version, classifier):
16 | filters = context.parent['filters']
17 | mc_vers = MCVer(mc_version)
18 | filter = next(reversed([f['filter'] for f in filters if mc_vers >= f['mc']]), [])
19 | return classifier not in filter
20 |
21 |
22 | def humanformatdate(dt: datetime.datetime):
23 | return markupsafe.Markup(f'')
24 |
25 |
26 | def get_artifact_description(artifact: Artifact, mc_version):
27 | if mc_version:
28 | hdr = (f'Downloads for {artifact.fullname()} for Minecraft {mc_version}',)
29 | lines = (f'{t.capitalize()}: {v}' for t, v in artifact.promotions[mc_version].items())
30 | else:
31 | hdr = (f'Downloads for {artifact.fullname()}',)
32 | lines = (f'Latest: {artifact.all_versions[-1].version}',)
33 |
34 | return '\n'.join(itertools.chain(hdr, lines))
35 |
36 | def crc32(file_path: str, chunk_size: int = 4096) -> str:
37 | """Computes the CRC32 checksum of the contents of the file at the given file_path"""
38 | checksum = 0
39 | with open('./static/' + file_path, 'rb') as f:
40 | while (chunk := f.read(chunk_size)):
41 | checksum = zlib.crc32(chunk, checksum)
42 |
43 | return "%08X" % (checksum & 0xFFFFFFFF)
44 |
45 | class Templates:
46 | def __init__(self, template_path: pathlib.Path, static_base, web_base, repository_base):
47 | self.env = jinja2.Environment(loader=jinja2.FileSystemLoader(template_path))
48 | self.env.globals['static_root'] = static_base
49 | self.env.globals['web_base'] = web_base
50 | self.env.globals['repository_base'] = repository_base
51 | now = datetime.datetime.utcnow()
52 | self.env.globals['now'] = now
53 | self.env.globals['crc32'] = crc32
54 | self.env.globals['show_classifier'] = show_classifier
55 | self.env.globals['get_artifact_description'] = get_artifact_description
56 |
57 | self.env.filters['humanformatdate'] = humanformatdate
58 | self.env.filters['formatdate'] = lambda dt: f'{dt:%Y-%m-%d %H:%M:%S}'
59 | self.env.filters['formatdatesimple'] = lambda dt: f'{dt:%Y-%m-%d}'
60 | self.env.filters['todatetime'] = lambda f: datetime.datetime.fromtimestamp(f)
61 | self.env.filters['maventopath'] = lambda p: '/'.join(re.split(r"[:.]", p))
62 |
63 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Jinja2~=3.1.2
2 | Markdown~=3.5
3 | MarkupSafe~=2.0
4 | requests~=2.28.1
5 | flask~=3.0.0
6 | waitress~=2.1.2
7 | paste~=3.6.0
--------------------------------------------------------------------------------
/runTestPageGen.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | set WEB_ROOT=https://files.minecraftforge.net
3 | set DOWNLOAD_ROOT=https://maven.minecraftforge.net
4 | set STATIC_DIR=file://%cd:\=/%/static/
5 | set ARGS=./python/page_generator.py --webout "./out/" --metaout "./out" --config "./config/global_overrides.json" --webroot "%WEB_ROOT%" --downloadroot "%DOWNLOAD_ROOT%" --static "%STATIC_DIR%" --folder "./maven" --templates "./templates/" --local-data
6 | python %ARGS% promote net.minecraftforge:forge 1.16.5-36.1.0 recommended
7 | python %ARGS% promote net.minecraftforge:forge 1.16.5-36.1.2 latest
8 | python %ARGS% promote net.minecraftforge:forge 1.18.1-39.1.0 recommended
9 | python %ARGS% promote net.minecraftforge:forge 1.18.1-39.1.2 latest
10 | python %ARGS% promote net.minecraftforge:forge 1.18.2-40.0.8 latest
11 | python %ARGS% promote net.minecraftforge:froge 23w13a_or_b-april.2023.13.b.8 latest
12 | python %ARGS% promote net.minecraftforge:froge 23w17a-2023.17.a.8 latest
13 | python %ARGS% promote net.minecraftforge:froge 1.20.1-pre1-46.0.8 latest
14 | python %ARGS% promote net.minecraftforge:froge 1.19.4-45.0.8 latest
15 | python %ARGS% promote net.minecraftforge:froge 1.20-rc1-46.1.8 latest
--------------------------------------------------------------------------------
/runTestPageGen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | ARGS="./python/page_generator.py --webout ./out/ --metaout ./out --config ./config/global_overrides.json --webroot https://files.minecraftforge.net --downloadroot https://maven.minecraftforge.net --static file://$(pwd)/static/ --folder ./maven --templates ./templates/ --local-data"
3 | python3 $ARGS promote net.minecraftforge:forge 1.16.5-36.1.0 recommended
4 | python3 $ARGS promote net.minecraftforge:forge 1.16.5-36.1.2 latest
5 | python3 $ARGS promote net.minecraftforge:forge 1.18.1-39.1.0 recommended
6 | python3 $ARGS promote net.minecraftforge:forge 1.18.1-39.1.2 latest
7 | python3 $ARGS promote net.minecraftforge:forge 1.18.2-40.0.8 latest
8 | python3 $ARGS promote net.minecraftforge:froge 23w13a_or_b-april.2023.13.b.8 latest
9 | python3 $ARGS promote net.minecraftforge:froge 23w17a-2023.17.a.8 latest
10 | python3 $ARGS promote net.minecraftforge:froge 1.20.1-pre1-46.0.8 latest
11 | python3 $ARGS promote net.minecraftforge:froge 1.19.4-45.0.0 latest
--------------------------------------------------------------------------------
/sass/_ads.scss:
--------------------------------------------------------------------------------
1 | /*
2 | Advertisement stylesheets applicable everywhere.
3 | They respond to the display size and hence support responsive ads from Google.
4 | */
5 | .promo, .ad {
6 | position: relative;
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | background: #fafafa;
11 | overflow: hidden;
12 | }
13 |
14 | .promo-container, .ad-container {
15 | &-framed {
16 | padding: 1rem;
17 | box-sizing: content-box;
18 | background: $section-bg;
19 | border-radius: 3px;
20 | border: 1px solid $section-highlight;
21 |
22 | &:before {
23 | content: 'ADVERTISEMENT';
24 | padding-bottom: 0.25rem;
25 | position: relative;
26 | font-weight: bold;
27 | font-size: 1.1rem;
28 | display: block;
29 | text-align: left;
30 | }
31 |
32 | .promo {
33 | margin-bottom: 0 !important;
34 | }
35 | }
36 | }
37 |
38 | .promo, .ad {
39 | &-desktop-300x600 {
40 | min-width: 300px;
41 | width: 300px;
42 | max-width: 300px;
43 | height: 600px;
44 | }
45 |
46 | &-desktop-160x600 {
47 | min-width: 160px;
48 | width: 160px;
49 | max-width: 160px;
50 | height: 600px;
51 | }
52 |
53 | &-desktop-728x90 {
54 | min-width: 728px;
55 | width: 728px;
56 | max-width: 728px;
57 | height: 90px;
58 | margin-left: auto;
59 | margin-right: auto;
60 | }
61 |
62 | &-dummy {
63 | &:before {
64 | content: 'AD';
65 | position: absolute;
66 | top: 0;
67 | left: 0;
68 | right: 0;
69 | bottom: 0;
70 | font-size: 1.2rem;
71 | line-height: 1.2rem;
72 | padding: 0.4rem;
73 | }
74 | }
75 | }
76 |
77 | // Warning message for enabled ad blockers
78 | .promo-container .block-note, .ad-container .block-note {
79 | font-size: 1.4rem;
80 | display: none;
81 | padding: 0.5rem;
82 | align-items: center;
83 |
84 | .block-note-icon {
85 | font-size: 4rem;
86 | margin-right: 1rem;
87 | color: #f64627;
88 | }
89 |
90 | &.block-note-show {
91 | display: flex !important;
92 | }
93 | }
94 |
95 | /*
96 | // Ad block detection
97 | .loading-done .promo-container {
98 | // This generally detects various methods of ad blocking, such as simply hiding an element, blocking loading and removing it
99 | .block-note:only-child,
100 | .promo-content:not([data-google-query-id]) + .block-note,
101 | .promo-content[hidden] + .block-note,
102 | *:not(.promo-content) + .block-note {
103 | display: flex !important;
104 | }
105 |
106 | .promo-content:not([data-google-query-id]) {
107 | display: none !important;
108 | }
109 | }
110 |
111 | .loading-done .ad-container {
112 | // This generally detects various methods of ad blocking, such as simply hiding an element, blocking loading and removing it
113 | .block-note:only-child,
114 | .adsbygoogle:not([data-adsbygoogle-status]) + .block-note,
115 | .adsbygoogle[hidden] + .block-note,
116 | *:not(.adsbygoogle) + .block-note {
117 | display: flex !important;
118 | }
119 |
120 | .adsbygoogle:not([data-adsbygoogle-status]) {
121 | display: none !important;
122 | }
123 | }
124 | */
125 |
126 | @media screen and (max-width: $mobile-break) {
127 | .promo, .ad {
128 | &-desktop-300x600, &-desktop-160x600 {
129 | min-width: 300px;
130 | width: 300px;
131 | max-width: 300px;
132 | min-height: 250px;
133 | height: 250px;
134 | max-height: 250px;
135 | margin-left: auto;
136 | margin-right: auto;
137 | }
138 | }
139 |
140 | .promo-container .block-note, .ad-container .block-note {
141 | flex-direction: column;
142 |
143 | .block-note-icon {
144 | margin-right: 0;
145 | margin-bottom: 1rem;
146 | }
147 | }
148 | }
149 |
150 | @media screen and (max-width: 767px) {
151 | .promo, .ad {
152 | &-desktop-300x600, &-desktop-160x600 {
153 | min-width: 300px;
154 | width: 300px;
155 | max-width: 300px;
156 | min-height: 250px;
157 | height: 250px;
158 | max-height: 250px;
159 | margin-left: auto;
160 | margin-right: auto;
161 | }
162 |
163 | &-desktop-728x90 {
164 | min-width: 320px;
165 | width: 320px;
166 | max-width: 320px;
167 | min-height: 100px;
168 | height: 100px;
169 | max-height: 100px;
170 | margin-left: auto;
171 | margin-right: auto;
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/sass/_base.scss:
--------------------------------------------------------------------------------
1 | $font-family-base: 'Source Sans Pro', sans-serif !default;
2 | $font-size-root: 14px !default;
3 | $font-size-base: 1rem !default;
4 | $line-height-base: 1.4 !default;
5 | $font-size-code: 0.8rem !default;
6 |
7 | $table-bg: transparent !default;
8 | $table-cell-padding: .75rem !default;
9 |
10 | $abbr-border-color: #818a91 !default;
11 | $dt-font-weight: bold !default;
12 | $cursor-disabled: not-allowed !default;
13 |
14 | $mobile-break: 1020px;
--------------------------------------------------------------------------------
/sass/_docs.scss:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css?family=Roboto+Slab:300,400,700");
2 |
3 | .docs-entry {
4 | background: $section-bg;
5 | box-shadow: 0 1px 2px $section-highlight;
6 | border-radius: 3px;
7 | padding: 1rem;
8 | overflow-x: auto;
9 | width: 100%;
10 | max-width: 100%;
11 |
12 | h1, h2, h3, h4, h5, h6 {
13 | margin: 0 0 0.5rem;
14 | font-family: "Roboto Slab", serif;
15 |
16 | code {
17 | background: none;
18 | border: none;
19 | padding: 0 0.25rem;
20 | font-size: inherit;
21 | font-weight: normal;
22 | color: inherit;
23 | }
24 | }
25 |
26 | h1, h2 {
27 | border-bottom: 1px solid $section-highlight;
28 | padding-bottom: 0.625rem;
29 | }
30 |
31 | h1 {
32 | font-size: 1.6rem;
33 | }
34 |
35 | h2 {
36 | font-size: 1.4rem;
37 | margin: 1rem 0 0.5rem;
38 | }
39 |
40 | h3 {
41 | color: $accent2;
42 | font-weight: normal;
43 | }
44 |
45 | ul, ol, dl {
46 | padding-left: 1.4rem;
47 | }
48 |
49 | p, pre, table {
50 | margin-bottom: 0.625rem;
51 | }
52 |
53 | p, ul, ol, dl {
54 | text-align: justify;
55 |
56 | code {
57 | white-space: pre;
58 | word-wrap: normal;
59 | }
60 | }
61 | }
62 |
63 | .docs-entry-footer-buttons {
64 | margin-top: 1rem;
65 | }
66 |
67 | section.sidebar-nav > ul {
68 | .elem-toc {
69 | background: rgba(0, 0, 0, 0.05);
70 | padding: 0 !important;
71 |
72 | & > .elem-text {
73 | padding: 0.2rem 0.5rem;
74 | }
75 | }
76 |
77 | .table-of-contents {
78 | background: none !important;
79 | font-size: 0.9rem !important;
80 |
81 | .toc-item, .toc-subitem {
82 | padding-left: 1rem;
83 | }
84 | }
85 | }
86 |
87 | .float-right {
88 | float: right;
89 | }
90 |
91 | .float-left {
92 | float: left;
93 | }
94 |
95 | .admonition {
96 | border-radius: 3px;
97 | box-shadow: 0 1px 2px $section-highlight;
98 | margin-bottom: 0.625rem;
99 | overflow-y: auto; // Allow child margin to affect admonition height
100 |
101 | p {
102 | padding: 0 1rem;
103 | }
104 |
105 | pre {
106 | margin-left: 1rem;
107 | margin-right: 1rem;
108 | }
109 |
110 | .admonition-title {
111 | font-weight: bold;
112 | padding: 0.2rem 1rem;
113 | font-size: 1.2rem;
114 | color: $pagination-color;
115 | margin: 0;
116 | border-radius: 3px 3px 0 0;
117 | border-bottom: 1px solid rgba(0, 0, 0, 0.1);
118 | }
119 |
120 | & > *:nth-child(2) {
121 | margin-top: 0.5rem;
122 | }
123 |
124 | // Admonition colors are constructed using the base theme colors
125 | @function admonition-bg($hue) {
126 | @return hsl($hue, saturation($accent5), lightness($scrollbar-track-bg));
127 | }
128 |
129 | @function admonition-title-bg($hue) {
130 | $base: admonition-bg($hue);
131 | @return saturate(hsl(hue($base), saturation($base), lightness($scrollbar-drag-bg)), 5%);
132 | }
133 |
134 | @mixin admonition($hue) {
135 | background: admonition-bg($hue);
136 |
137 | .admonition-title {
138 | background: admonition-title-bg($hue);
139 | }
140 | }
141 |
142 | &, &.note {
143 | @include admonition(206);
144 | }
145 |
146 | &.important {
147 | @include admonition(168);
148 | }
149 |
150 | &.warning, &.attention {
151 | @include admonition(30);
152 | }
153 | }
154 |
155 | .injected {
156 | display: none;
157 | }
158 |
159 | // Highlight JS
160 | .hljs {
161 | display: block;
162 | overflow-x: auto;
163 | -webkit-text-size-adjust: none
164 | }
165 |
166 | .headerlink {
167 | display: inline-block;
168 | margin-left: 0.5rem;
169 | font: normal normal normal 14px/1 FontAwesome;
170 | font-size: inherit;
171 | text-rendering: auto;
172 | -webkit-font-smoothing: antialiased;
173 | -moz-osx-font-smoothing: grayscale;
174 |
175 | &:before {
176 | content: "\f0c1";
177 | }
178 | }
179 |
180 | .sidebar-heading {
181 | display: flex;
182 | flex-direction: row;
183 | align-items: center;
184 | }
185 |
186 | .version-selection {
187 | display: flex;
188 | flex-direction: row;
189 | margin-left: auto;
190 |
191 | label {
192 | margin-bottom: 0;
193 | }
194 |
195 | select.version-selection-dropdown {
196 | color: $pagination-color;
197 | background: $accent1;
198 | border: none;
199 | border-bottom: 2px solid $pagination-color;
200 |
201 | &:active, &:focus {
202 | border-bottom-color: $accent7;
203 | outline: none;
204 | }
205 |
206 | option {
207 | background: $accent1;
208 | }
209 | }
210 | }
211 |
212 | @media screen and (max-width: $mobile-break) {
213 | table {
214 | display: block;
215 | overflow-x: auto;
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/sass/_downloads.scss:
--------------------------------------------------------------------------------
1 | /*
2 | Styles specific to the downloads page
3 | */
4 | .promos-wrapper {
5 | display: flex;
6 | flex-direction: column;
7 | align-items: center;
8 |
9 | .promos-content {
10 | align-self: stretch;
11 | }
12 | }
13 |
14 | .downloads {
15 | position: relative;
16 | display: flex;
17 | justify-content: center;
18 | padding: 0;
19 | align-items: flex-start;
20 | flex-wrap: wrap;
21 | }
22 |
23 | .downloads div.download {
24 | min-width: 350px;
25 | max-width: 100%;
26 | color: #eeeeee;
27 | padding: 0;
28 | position: relative;
29 | text-decoration: none;
30 | transition: background 0.2s ease-in-out;
31 | text-align: center;
32 | font-size: 1.5rem;
33 | box-shadow: 0 1px 2px $section-highlight;
34 | border-radius: 3px;
35 | margin: 0 1rem 1rem;
36 |
37 | small {
38 | font-size: 1rem;
39 | }
40 |
41 | i {
42 | position: relative;
43 | margin-right: 5px;
44 | top: 2px;
45 | }
46 |
47 | .title {
48 | position: relative;
49 | background-color: $accent1;
50 | border-radius: 3px 3px 0 0;
51 | padding: 10px;
52 | line-height: 1.2rem;
53 | }
54 |
55 | .links {
56 | position: relative;
57 | background: $section-highlight;
58 | border-radius: 0 0 3px 3px;
59 | border-top: 1px solid $section-highlight;
60 | margin: 0;
61 | display: grid;
62 | grid-gap: 1px;
63 | grid-auto-flow: column;
64 | grid-template-rows: 1fr 1fr;
65 | grid-auto-columns: 1fr;
66 |
67 | .link {
68 | background: $section-bg;
69 | }
70 |
71 | .link-boosted {
72 | grid-column-end: span 2;
73 | grid-row-end: span 2;
74 |
75 | a {
76 | font-size: 1.5rem;
77 | }
78 | }
79 |
80 | a {
81 | position: relative;
82 | box-sizing: border-box;
83 | font-size: 1rem;
84 | width: 100%;
85 | height: 100%;
86 | text-decoration: none;
87 | display: flex;
88 | flex-direction: column;
89 | align-items: center;
90 | justify-content: center;
91 | padding: 0.75rem;
92 | color: $body-color;
93 | transition: background 0.2s ease-in-out;
94 |
95 | &:hover {
96 | background-color: rgba(0, 0, 0, 0.1);
97 | }
98 |
99 | &:active {
100 | background-color: rgba(0, 0, 0, 0.1);
101 | box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.2);
102 | }
103 |
104 | i {
105 | width: 28px;
106 | margin: 0;
107 | font-size: 1.75em;
108 | }
109 |
110 | .promo-label {
111 | display: block;
112 | margin-top: 5px;
113 | font-size: 0.8em;
114 | }
115 | }
116 |
117 | &-split {
118 | background: $section-bg;
119 | display: flex;
120 | align-items: stretch;
121 |
122 | .link {
123 | flex-grow: 1;
124 | }
125 |
126 | a {
127 | border-right: 1px solid $section-highlight;
128 | }
129 |
130 | .link:first-child a {
131 | border-bottom-left-radius: 3px;
132 | }
133 |
134 | .link:last-child a {
135 | border-bottom-right-radius: 3px;
136 | border-right: 0;
137 | }
138 |
139 | .link-left, .links-right {
140 | flex-grow: 1;
141 | flex-shrink: 0;
142 | flex-basis: 0;
143 | }
144 |
145 | .link-left {
146 | position: relative;
147 |
148 | a {
149 | border-bottom-left-radius: 3px;
150 | font-size: 1.5rem;
151 | position: absolute;
152 | top: 0;
153 | left: 0;
154 | bottom: 0;
155 | right: 0;
156 | }
157 | }
158 |
159 | .links-right {
160 | display: flex;
161 | flex-direction: row;
162 | flex-wrap: wrap;
163 |
164 | .link {
165 | flex-shrink: 0;
166 | flex-basis: 60px;
167 |
168 | a {
169 | padding: 0.7rem;
170 | }
171 | }
172 |
173 | .link:nth-child(2n) a {
174 | border-right: none;
175 | }
176 |
177 | .link:nth-child(n + 3) a {
178 | border-top: 1px solid $section-highlight;
179 | }
180 |
181 | .link:last-child a {
182 | border-bottom-right-radius: 3px;
183 | }
184 | }
185 | }
186 | }
187 | }
188 |
189 | .download-list {
190 | i {
191 | font-size: 0.875rem;
192 | }
193 |
194 | td:first-child {
195 | border-right: 1px solid $section-highlight;
196 | }
197 |
198 | .download-version {
199 | font-weight: bold;
200 | color: $accent2;
201 | }
202 |
203 | .download-links {
204 | margin: 0;
205 | padding: 0;
206 | display: flex;
207 | list-style-type: none;
208 |
209 | li {
210 | padding: 0 0.5rem;
211 | border-right: 1px solid $section-highlight;
212 |
213 | .download-classifier {
214 | margin-right: 0.25rem;
215 | }
216 |
217 | &:first-child {
218 | padding-left: 0;
219 | }
220 |
221 | &:last-child {
222 | padding-right: 0;
223 | border-right: none;
224 | }
225 | }
226 |
227 | a {
228 | color: $body-color;
229 | text-decoration: underline;
230 |
231 | &:hover {
232 | color: lighten($body-color, 5%);
233 | text-decoration: none;
234 | }
235 | }
236 | }
237 | }
238 |
239 | .download-container {
240 | display: flex;
241 | flex-direction: column;
242 | align-items: center;
243 |
244 | .btn {
245 | margin-bottom: 1rem;
246 | }
247 |
248 | .download-list-wrapper {
249 | width: 100%;
250 | }
251 | }
252 |
253 | .download-disclaimer {
254 | width: 100%;
255 | margin-bottom: 0.5rem;
256 |
257 | h2 {
258 | margin: 0;
259 | }
260 | }
261 |
262 | .fa[class*="classifier-"]:before {
263 | content: "\f1c6";
264 | }
265 |
266 | .fa.classifier-installer:before {
267 | content: "\f187";
268 | }
269 |
270 | .fa.classifier-installer-win:before {
271 | content: "\f17a";
272 | }
273 |
274 | .fa.classifier-dev:before {
275 | content: "\f079";
276 | }
277 |
278 | .fa.classifier-sources:before {
279 | content: "\f121";
280 | }
281 |
282 | .fa.classifier-src:before {
283 | content: "\f121";
284 | }
285 |
286 | .fa.classifier-changelog:before {
287 | content: "\f15c";
288 | }
289 |
290 | .fa.classifier-javadoc:before {
291 | content: "\f0f4";
292 | }
293 |
294 | .fa.classifier-universal:before, .fa.classifier-launcher:before {
295 | content: "\f1c6";
296 | }
297 |
298 | .fa.classifier-mdk:before {
299 | content: "\f126";
300 | }
301 |
302 | .fa.classifier-userdev:before {
303 | content: "\f007";
304 | }
305 |
306 | .fa.classifier-link:before {
307 | content: "\f0c1";
308 | }
309 |
310 | .promo-latest:before {
311 | content: "\f123"; // Half Star (outlined): fa-star-half-o
312 | }
313 |
314 | .promo-recommended:before {
315 | content: "\f005"; // Full Star
316 | }
317 |
318 | .marker-branch:before {
319 | content: "\f126"; // Fork
320 | }
321 |
322 | .promo-downloads-top, .ad-downloads-top {
323 | margin-bottom: 2rem !important;
324 | min-height: 121px; // significantly reduce the top ad making the rest of the page jump about during page load
325 | }
326 |
327 | .promo-downloads-bottom, .ad-downloads-bottom {
328 | margin-top: 2rem !important;
329 | margin-bottom: 2rem !important;
330 | }
331 |
332 | .info-link {
333 | cursor: pointer;
334 | }
335 |
336 | .info-tooltip {
337 | display: none;
338 | }
339 |
340 | @media screen and (max-width: 1440px) {
341 | .sidebar-wrapper {
342 | .sidebar-left.sidebar-sticky, .sidebar-sticky aside {
343 | min-width: 160px;
344 | max-width: 160px;
345 | }
346 |
347 | .sidebar-right {
348 | min-width: 190px;
349 | max-width: 190px;
350 | }
351 | }
352 |
353 | .sidebar-wrapper .sidebar-sticky-wrapper-content {
354 | max-width: calc(100% - 160px - 1rem);
355 | }
356 |
357 | .promo-downloads-right .promo, .ad-downloads-right .ad {
358 | min-width: 160px;
359 | width: 160px;
360 | max-width: 160px;
361 | height: 600px;
362 | }
363 | }
364 |
365 | @media screen and (max-width: 1170px) {
366 | .downloads {
367 | flex-direction: column;
368 | align-items: center;
369 |
370 | div.download {
371 | margin: 0 0 1rem;
372 |
373 | &:last-child {
374 | margin-bottom: 0;
375 | }
376 | }
377 | }
378 |
379 | .promo-downloads-top .promo, .promo-downloads-bottom .promo,
380 | .ad-downloads-top .ad, .ad-downloads-bottom .ad {
381 | min-width: 320px;
382 | width: 320px;
383 | max-width: 320px;
384 | height: 100px;
385 | margin-left: auto;
386 | margin-right: auto;
387 | }
388 |
389 | .download-list .download-links {
390 | flex-wrap: wrap;
391 | }
392 | }
393 |
394 | @media screen and (max-width: $mobile-break) {
395 | .promos-wrapper {
396 | flex-direction: column-reverse;
397 |
398 | .promo-downloads-top, .ad-downloads-top {
399 | margin: 3rem auto !important;
400 | }
401 | }
402 |
403 | .sidebar-wrapper {
404 | .sidebar-left.sidebar-sticky, .sidebar-sticky aside {
405 | min-width: 0;
406 | max-width: 100%;
407 | }
408 |
409 | .sidebar-sticky-wrapper-content, .sidebar-wrapper-content {
410 | max-width: 100%;
411 | }
412 | }
413 |
414 | .sidebar-wrapper .sidebar-right {
415 | width: 100%;
416 | display: flex;
417 | align-items: center;
418 | flex-direction: column;
419 | margin-left: 0;
420 | max-width: 100%;
421 | }
422 | }
423 |
424 | @media screen and (max-width: 960px) {
425 | .download-list {
426 | td:nth-child(2), th:nth-child(2) {
427 | display: none;
428 | }
429 |
430 | .download-version {
431 | width: auto;
432 | }
433 |
434 | .download-files {
435 | width: auto;
436 | }
437 | }
438 | }
439 |
440 | @media screen and (max-width: 768px) {
441 | .download-list {
442 | display: flex;
443 | flex-direction: column;
444 | align-items: stretch;
445 |
446 | thead {
447 | display: none;
448 | }
449 |
450 | thead.mobile-only {
451 | display: block !important;
452 | }
453 |
454 | tr {
455 | display: flex;
456 | flex-direction: column;
457 | align-items: stretch;
458 |
459 | &:last-child {
460 | td:first-child {
461 | border-radius: 0;
462 | }
463 |
464 | td:last-child {
465 | border-radius: 0 0 3px 3px;
466 | }
467 | }
468 | }
469 |
470 | td, th {
471 | display: flex;
472 | justify-content: center;
473 | align-items: center;
474 | }
475 |
476 | td:first-child {
477 | padding-bottom: 0;
478 | border-right: none;
479 | }
480 | }
481 | }
482 |
483 | @media screen and (max-width: 640px) {
484 | .download-list .download-links {
485 | flex-direction: column;
486 | align-items: center;
487 |
488 | li {
489 | border-right: none;
490 | }
491 | }
492 | }
493 |
494 | @media screen and (max-width: 767px) {
495 | .promo-downloads-right .promo, .ad-downloads-right .ad {
496 | min-width: 300px;
497 | width: 300px;
498 | max-width: 300px;
499 | height: 250px;
500 | margin-left: auto;
501 | margin-right: auto;
502 | }
503 | }
504 |
--------------------------------------------------------------------------------
/sass/_forum_screens.scss:
--------------------------------------------------------------------------------
1 | /*
2 | Style adjustments required to make the forums look good on any system.
3 | */
4 | @media screen and (max-width: 960px) {
5 | .forum-row {
6 | .forum-icon-wrapper {
7 | padding: 0.5rem;
8 | }
9 |
10 | .forum-statistics {
11 | display: none;
12 | }
13 | }
14 |
15 | .heading-statistics {
16 | display: none;
17 | }
18 |
19 | .thread-row {
20 | .thread-statistics {
21 | display: none;
22 | }
23 | }
24 |
25 | html[dir="ltr"] .ipsDataItem_modCheck {
26 | display: flex;
27 | left: initial;
28 | position: relative;
29 | top: initial;
30 | padding: 12px 10px;
31 | justify-content: center;
32 | }
33 | }
34 |
35 | @media screen and (max-width: 767px) {
36 | .thread-reply-body-metadata {
37 | flex-direction: column;
38 | }
39 |
40 | .reply-actions {
41 | flex-wrap: wrap;
42 | margin-left: 0;
43 | }
44 |
45 | html[dir="ltr"] .cForumQuestions .cForumQuestion .ipsDataItem_modCheck, html[dir="ltr"] .cForumQuestions .cForumQuestion .ipsDataItem_main + .cForumQuestion_stat {
46 | margin-left: 0;
47 | }
48 |
49 | .ipsStream {
50 | margin-top: 0;
51 | }
52 | }
53 |
54 | @media screen and (max-width: 679px) {
55 | .thread-list-actions {
56 | flex-direction: column;
57 | justify-content: center;
58 | align-items: stretch;
59 |
60 | .thread-list-buttons {
61 | flex-direction: column;
62 | align-items: stretch;
63 | margin-left: 0;
64 |
65 | li {
66 | margin-left: 0;
67 | margin-bottom: 0.5rem;
68 |
69 | &:last-child {
70 | margin-bottom: 0;
71 | }
72 | }
73 |
74 | a {
75 | padding: 0.5rem 1rem;
76 | text-align: center;
77 | }
78 | }
79 | }
80 |
81 | section.thread-replies {
82 | article.thread-reply {
83 | flex-direction: column;
84 | align-items: center;
85 | padding: 1rem;
86 |
87 | .thread-reply-author {
88 | flex-direction: row;
89 | width: auto;
90 | margin-bottom: 1rem;
91 |
92 | .profile-picture {
93 | margin-bottom: 0;
94 | width: 90px;
95 | height: 90px;
96 | }
97 |
98 | .user-details {
99 | margin-left: 1rem;
100 | }
101 | }
102 |
103 | .thread-reply-body {
104 | margin-left: 0;
105 | }
106 | }
107 | }
108 |
109 | html[dir="ltr"] .ipsDataItem_modCheck {
110 | padding: 0.5rem;
111 | justify-content: flex-end;
112 | background: none;
113 | border-left: none;
114 | border-top: 1px solid $section-highlight;
115 | }
116 |
117 | .forum-row {
118 | flex-direction: column;
119 | align-items: stretch;
120 | margin-bottom: 0.5rem;
121 |
122 | .forum-icon-wrapper {
123 | display: none;
124 | }
125 |
126 | .forum-icon-mobile {
127 | display: inline-block;
128 | }
129 |
130 | &:first-child {
131 | .forum-info {
132 | border-top: none;
133 | }
134 | }
135 |
136 | &:last-child {
137 | margin-bottom: 0;
138 | }
139 |
140 | .forum-info, .forum-statistics, .forum-last-post {
141 | flex-basis: auto;
142 | border-left: none;
143 | }
144 |
145 | .forum-info {
146 | align-items: stretch;
147 | border-top: 1px solid $section-highlight;
148 |
149 | .forum-details {
150 | align-items: center;
151 | }
152 |
153 | .forum-children {
154 | display: none;
155 | }
156 | }
157 |
158 | .forum-last-post {
159 | align-items: stretch;
160 | border-top: 1px solid $section-highlight;
161 | }
162 |
163 | .forum-last-post-fact {
164 | align-items: flex-start;
165 | }
166 | }
167 |
168 | section.thread-list {
169 | .heading {
170 | display: none;
171 | }
172 | }
173 |
174 | .thread-row {
175 | flex-direction: column;
176 | align-items: stretch;
177 | margin-bottom: 0.5rem;
178 |
179 | .thread-icon-wrapper {
180 | display: none;
181 | }
182 |
183 | &:first-child {
184 | border-top-left-radius: 3px;
185 | border-top-right-radius: 3px;
186 |
187 | .thread-info {
188 | border-top: none;
189 | }
190 | }
191 |
192 | &:last-child {
193 | margin-bottom: 0;
194 | }
195 |
196 | .thread-info, .thread-statistics, .thread-last-post {
197 | flex-basis: auto;
198 | border-left: none;
199 | }
200 |
201 | .thread-info {
202 | align-items: stretch;
203 | border-top: 1px solid $section-highlight;
204 |
205 | .thread-details {
206 | align-items: center;
207 | }
208 | }
209 |
210 | .thread-last-post {
211 | align-items: stretch;
212 | border-top: 1px solid $section-highlight;
213 | }
214 |
215 | .thread-last-post-fact {
216 | align-items: flex-start;
217 | }
218 | }
219 | }
--------------------------------------------------------------------------------
/sass/_highlighting_dark.scss:
--------------------------------------------------------------------------------
1 | .hljs-strong,
2 | .hljs-emphasis {
3 | color: #a8a8a2;
4 | }
5 |
6 | .hljs-bullet,
7 | .hljs-quote,
8 | .hljs-link,
9 | .hljs-number,
10 | .hljs-regexp,
11 | .hljs-literal {
12 | color: #6896ba;
13 | }
14 |
15 | .hljs-code,
16 | .hljs-selector-class {
17 | color: #a6e22e;
18 | }
19 |
20 | .hljs-emphasis {
21 | font-style: italic;
22 | }
23 |
24 | .hljs-keyword,
25 | .hljs-selector-tag,
26 | .hljs-section,
27 | .hljs-attribute,
28 | .hljs-name,
29 | .hljs-variable {
30 | color: #cb7832;
31 | }
32 |
33 | .hljs-params {
34 | color: #b9b9b9;
35 | }
36 |
37 | .hljs-string {
38 | color: #6a8759;
39 | }
40 |
41 | .hljs-subst,
42 | .hljs-type,
43 | .hljs-built_in,
44 | .hljs-builtin-name,
45 | .hljs-symbol,
46 | .hljs-selector-id,
47 | .hljs-selector-attr,
48 | .hljs-selector-pseudo,
49 | .hljs-template-tag,
50 | .hljs-template-variable,
51 | .hljs-addition {
52 | color: #e0c46c;
53 | }
54 |
55 | .hljs-comment,
56 | .hljs-deletion,
57 | .hljs-meta {
58 | color: #7f7f7f;
59 | }
--------------------------------------------------------------------------------
/sass/_highlighting_light.scss:
--------------------------------------------------------------------------------
1 | .hljs-subst,
2 | .hljs-title {
3 | font-weight: normal;
4 | color: #000;
5 | }
6 |
7 | .hljs-comment,
8 | .hljs-quote {
9 | color: #808080;
10 | font-style: italic;
11 | }
12 |
13 | .hljs-meta {
14 | color: #808000;
15 | }
16 |
17 | .hljs-tag {
18 | background: #efefef;
19 | }
20 |
21 | .hljs-section,
22 | .hljs-name,
23 | .hljs-literal,
24 | .hljs-keyword,
25 | .hljs-selector-tag,
26 | .hljs-type,
27 | .hljs-selector-id,
28 | .hljs-selector-class {
29 | font-weight: bold;
30 | color: #000080;
31 | }
32 |
33 | .hljs-attribute,
34 | .hljs-number,
35 | .hljs-regexp,
36 | .hljs-link {
37 | font-weight: bold;
38 | color: #0000ff;
39 | }
40 |
41 | .hljs-number,
42 | .hljs-regexp,
43 | .hljs-link {
44 | font-weight: normal;
45 | }
46 |
47 | .hljs-string {
48 | color: #008000;
49 | font-weight: bold;
50 | }
51 |
52 | .hljs-symbol,
53 | .hljs-bullet,
54 | .hljs-formula {
55 | color: #000;
56 | background: #d0eded;
57 | font-style: italic;
58 | }
59 |
60 | .hljs-doctag {
61 | text-decoration: underline;
62 | }
63 |
64 | .hljs-variable,
65 | .hljs-template-variable {
66 | color: #660e7a;
67 | }
68 |
69 | .hljs-addition {
70 | background: #baeeba;
71 | }
72 |
73 | .hljs-deletion {
74 | background: #ffc8bd;
75 | }
76 |
77 | .hljs-emphasis {
78 | font-style: italic;
79 | }
80 |
81 | .hljs-strong {
82 | font-weight: bold;
83 | }
--------------------------------------------------------------------------------
/sass/_layout.scss:
--------------------------------------------------------------------------------
1 | /*
2 | Basic layout shared across all Forge sites.
3 | Allows for easy and consistent looks everywhere.
4 | */
5 |
6 | // Basic wrapper around all content areas, specifies a centered, fixed width area on desktops
7 | .wrapper {
8 | position: relative;
9 | width: 1720px;
10 | margin: 0 auto;
11 | box-sizing: border-box;
12 | padding: 0 1rem;
13 | }
14 |
15 | // Simple class to hide any element
16 | .hidden {
17 | display: none !important;
18 | }
19 |
20 | // Header area, contains things such as the logo, links, a search area and the user panel
21 | header {
22 | color: #f9f7f7;
23 | background: $header-bg;
24 | margin-bottom: 1rem;
25 |
26 | .wrapper {
27 | display: flex;
28 | align-items: center;
29 | padding: 1.5rem 1rem;
30 | }
31 |
32 | // Generic search field that stretches the header
33 | .search {
34 | margin-left: 3rem;
35 | background: lighten($header-bg, 10%);
36 | height: 40px;
37 | flex-grow: 2;
38 |
39 | .search-field {
40 | position: relative;
41 | height: 100%;
42 | overflow: hidden;
43 | flex-grow: 2;
44 | }
45 |
46 | form {
47 | height: 100%;
48 | display: flex;
49 | align-items: center;
50 | }
51 |
52 | input[type=search] {
53 | box-sizing: border-box;
54 | color: #f9f7f7;
55 | width: 100%;
56 | height: 100%;
57 | background: none;
58 | outline: none;
59 | border: none;
60 | padding: 0 0 0 1rem;
61 | max-width: 100%;
62 | border-radius: 0;
63 |
64 | &:focus {
65 | border-radius: 0;
66 | box-shadow: none;
67 | outline: none;
68 | background: rgba(255, 255, 255, 0.1);
69 | }
70 | }
71 |
72 | input[type=submit], button[type=submit] {
73 | position: relative;
74 | float: right;
75 | box-sizing: border-box;
76 | padding: 0 1rem;
77 | max-width: 52px;
78 | height: 100%;
79 | background: none;
80 | outline: none;
81 | border: none;
82 | color: $accent2;
83 | display: block;
84 | font-family: FontAwesome, sans-serif;
85 | font-size: 1.45rem;
86 | transition: color 0.1s ease-in-out;
87 |
88 | &:hover, &:active {
89 | color: darken($accent2, 5%);
90 | cursor: pointer;
91 | }
92 | }
93 | }
94 |
95 | // Container for user-related data
96 | .user-panel {
97 | display: flex;
98 | align-items: center;
99 | margin-left: auto;
100 | padding-left: 1rem;
101 |
102 | ul.user-panel-links {
103 | display: flex;
104 | list-style-type: none;
105 | align-items: center;
106 | margin-bottom: 0;
107 |
108 | li {
109 | margin-right: 1rem;
110 |
111 | &:last-child {
112 | margin-right: 0;
113 | }
114 | }
115 | }
116 | }
117 |
118 | a {
119 | color: #f9f7f7;
120 | text-decoration: none;
121 | transition: color 0.1s ease-in-out;
122 |
123 | &:hover, &:active, &:focus {
124 | color: darken(#f9f7f7, 10%);
125 | }
126 | }
127 | }
128 |
129 | // The main navigation uses the semantic 'nav' tag around a list of links in order to use the full potential of HTML5
130 | nav {
131 | font-size: 1.2rem;
132 | margin-left: 1rem;
133 |
134 | // Link list is simple and horizontally aligned
135 | .links {
136 | list-style-type: none;
137 | margin: 0;
138 | padding: 0;
139 |
140 | li {
141 | float: left;
142 | padding: 0 0.4rem;
143 |
144 | &:first-child {
145 | padding-left: 0;
146 | }
147 |
148 | &:last-child {
149 | padding-right: 0;
150 | }
151 | }
152 | }
153 | }
154 |
155 | .hero {
156 | margin-top: -1rem;
157 | color: #f9f7f7;
158 | background: darken($header-bg, 5%);
159 |
160 | .wrapper {
161 | padding: 2rem;
162 | }
163 | }
164 |
165 | // Sections are individual blocks separated from each other and the background
166 | section, table {
167 | background: $section-bg;
168 | border-radius: 3px;
169 | box-shadow: 0 1px 2px $section-highlight;
170 | margin-bottom: 1rem;
171 | width: 100%;
172 |
173 | h2, thead, .heading {
174 | font-size: 1.2rem;
175 | color: $pagination-color;
176 | margin: 0;
177 | border-radius: 3px 3px 0 0;
178 | font-weight: normal;
179 | }
180 |
181 | h2, .heading {
182 | background: $accent1;
183 | padding: 0.5rem 1rem;
184 | }
185 |
186 | .section-content {
187 | padding: 0.5rem 1rem;
188 | border-radius: 0 0 3px 3px;
189 | overflow-wrap: break-word;
190 | word-wrap: break-word;
191 | word-break: break-all;
192 | word-break: break-word;
193 | hyphens: auto;
194 |
195 | & > *:last-child {
196 | margin-bottom: 0;
197 | }
198 | }
199 | }
200 |
201 | footer {
202 | text-align: center;
203 | /* margin-top: 2rem; We already add padding */
204 | border-top: 1px solid rgba(0, 0, 0, 0.1);
205 | padding: 1.5rem 0 0 0;
206 | }
207 |
208 | /*
209 | Several basic controls follow...
210 | */
211 |
212 | // List sections are vertical listings of entries
213 | .list-section {
214 | padding: 0 !important;
215 |
216 | .row {
217 | position: relative;
218 | display: flex;
219 | flex-flow: row;
220 | align-items: stretch;
221 | height: auto;
222 | border-bottom: 1px solid $section-highlight;
223 |
224 | &:last-child {
225 | border-bottom: none;
226 | }
227 |
228 | .row-entry {
229 | padding: 0.5rem 1rem;
230 | }
231 | }
232 | }
233 |
234 | // Generic table styles, should work everywhere
235 | table {
236 | thead {
237 | th {
238 | background: $accent1;
239 | padding: 0.5rem 1rem;
240 | font-weight: normal;
241 |
242 | &:first-child {
243 | border-top-left-radius: 3px;
244 | }
245 |
246 | &:last-child {
247 | border-top-right-radius: 3px;
248 | }
249 | }
250 | }
251 |
252 | tbody {
253 | background: $section-bg;
254 |
255 | tr:nth-child(2n) td {
256 | background: rgba(0, 0, 0, 0.05);
257 | }
258 |
259 | tr:last-child {
260 | td:first-child {
261 | border-bottom-left-radius: 3px;
262 | }
263 |
264 | td:last-child {
265 | border-bottom-right-radius: 3px;
266 | }
267 | }
268 |
269 | td {
270 | padding: 0.5rem 1rem;
271 | font-weight: normal;
272 | }
273 | }
274 | }
275 |
276 | // Paragraphs receive a slight bottom margin to not stretch content areas too much vertically
277 | p {
278 | margin-bottom: 0.1rem;
279 | }
280 |
281 | // Buttons
282 | .btn {
283 | display: inline-block;
284 | border: none;
285 | padding: 0.5rem 0.7rem;
286 | transition: background 0.2s ease-in-out;
287 | outline: none;
288 | border-radius: 0;
289 | background: $accent2;
290 | color: #fafafa;
291 |
292 | &:hover, &:active, &:focus {
293 | border: none;
294 | color: #fafafa;
295 | background: lighten($accent2, 10%);
296 | }
297 | }
298 |
299 | .btn-large {
300 | padding: 0.7rem 0.9rem;
301 | font-size: 1.2rem;
302 | }
303 |
304 | // General purpose pagination control
305 | .pagination {
306 | display: flex;
307 | align-items: center;
308 | padding: 0;
309 | list-style-type: none;
310 | margin: 0 0 1rem;
311 |
312 | &.pagination-center {
313 | justify-content: center;
314 | }
315 |
316 | li {
317 | display: block;
318 | margin-left: 0.25rem;
319 |
320 | a {
321 | display: block;
322 | padding: 0.2rem 0.5rem;
323 | background: $pagination-bg;
324 | color: $pagination-color;
325 | border-radius: 3px;
326 | transition: background 0.2s ease-in-out;
327 |
328 | &:hover, &:focus, &:active {
329 | color: $pagination-color;
330 | background: darken($pagination-bg, 10%) !important;
331 | }
332 | }
333 |
334 | &.pagination-active {
335 | display: block;
336 | padding: 0.2rem 0.5rem;
337 | background: $pagination-color;
338 | border-radius: 3px;
339 | font-weight: bold;
340 | color: $body-color !important;
341 | box-shadow: 0 2px 2px darken($pagination-color, 10%);
342 | }
343 |
344 | &.pagination-ellipsis {
345 | font-size: 0.9em;
346 | }
347 |
348 | &.pagination-disabled {
349 | display: block;
350 | padding: 0.2rem 0.5rem;
351 | border-radius: 3px;
352 | background: $pagination-bg;
353 | color: $pagination-color;
354 | }
355 |
356 | &.pagination-first, &.pagination-last, &.pagination-next, &.pagination-prev {
357 | a {
358 | background: none;
359 | color: $pagination-bg;
360 | transition: color 0.2s ease-in-out;
361 |
362 | &:hover, &:active, &:focus {
363 | background: none !important;
364 | color: darken($pagination-bg, 5%) !important;;
365 | }
366 | }
367 |
368 | &.pagination-disabled {
369 | background: none;
370 | padding: 0.2rem 0.5rem;
371 | color: $pagination-bg;
372 | }
373 | }
374 |
375 | &:first-child {
376 | padding-left: 0;
377 | margin-left: 0;
378 |
379 | a {
380 | padding-left: 0;
381 | }
382 | }
383 |
384 | &:last-child {
385 | padding-right: 0;
386 |
387 | a {
388 | padding-right: 0;
389 | }
390 | }
391 | }
392 | }
393 |
394 | // Generic code styles
395 | .codeheader {
396 | margin-bottom: 0.5rem;
397 | }
398 |
399 | pre, .ipsCode, code {
400 | font-family: "Source Code Pro", Consolas, monospace;
401 | margin: 0;
402 | white-space: pre;
403 | overflow-wrap: normal;
404 | word-wrap: normal;
405 | font-size: $font-size-code;
406 | background: $section-content-bg;
407 | border: 1px solid darken($section-content-bg, 5%) !important;
408 | border-radius: 3px;
409 | max-width: 100%;
410 | }
411 |
412 | code {
413 | color: lighten($accent4, 5%);
414 | padding: 0.02rem 0.2rem;
415 | overflow: auto;
416 | }
417 |
418 | pre, .ipsCode {
419 | padding: 0.5rem;
420 | color: $body-color;
421 |
422 | code {
423 | display: block;
424 | color: $body-color;
425 | background: none;
426 | padding: 0;
427 | border-radius: 0;
428 | border: none !important;
429 | }
430 | }
431 |
432 | a pre, a code {
433 | color: inherit;
434 | }
435 |
436 | // Block quotes for quoting people
437 | blockquote {
438 | border: none;
439 | border-left: 5px solid darken($body-bg, 3%);
440 | background: lighten($body-bg, 3%);
441 | padding: 0.5rem;
442 |
443 | footer {
444 | text-align: left;
445 | padding: 0;
446 | font-size: 0.8rem;
447 |
448 | &:before {
449 | content: '- ';
450 | }
451 | }
452 | }
453 |
454 | // Theme switch
455 | .theme-switch-wrapper {
456 | position: relative;
457 | display: flex;
458 | align-items: center;
459 | justify-content: center;
460 | }
461 |
462 | .theme-switch {
463 | position: relative;
464 | display: inline-block;
465 | width: 3.25rem;
466 | height: 1.5rem;
467 | margin-left: 0.25rem;
468 | margin-bottom: 0;
469 |
470 | input {
471 | display: none;
472 | }
473 |
474 | /* The slider */
475 | .slider {
476 | position: absolute;
477 | cursor: pointer;
478 | top: 0;
479 | left: 0;
480 | right: 0;
481 | bottom: 0;
482 | background-color: $text-muted;
483 | transition: .4s;
484 | border-radius: 20px;
485 | }
486 |
487 | .slider:before {
488 | position: absolute;
489 | content: '';
490 | height: 1rem;
491 | width: 1rem;
492 | left: 0.25rem;
493 | bottom: 0.25rem;
494 | background-color: #fff;
495 | transition: .4s;
496 | border-radius: 20px;
497 | }
498 |
499 | input:checked + .slider {
500 | background-color: $accent2;
501 | }
502 |
503 | input:focus + .slider {
504 | box-shadow: 0 0 1px $accent2;
505 | }
506 |
507 | input:checked + .slider:before {
508 | transform: translateX(1.75rem);
509 | }
510 | }
511 |
512 | // Import adjustments for non-desktop devices
513 | @import "layout_screens";
514 |
--------------------------------------------------------------------------------
/sass/_layout_screens.scss:
--------------------------------------------------------------------------------
1 | /*
2 | Style adjustments required to make the generic layout look good on any system.
3 | */
4 | // Mobile only elements
5 | .mobile-only {
6 | display: none;
7 | }
8 |
9 | @media screen and (max-width: 1720px) {
10 | .wrapper {
11 | width: 100%;
12 | }
13 | }
14 |
15 | @media screen and (max-width: $mobile-break) {
16 | header {
17 | .wrapper {
18 | flex-direction: column;
19 | }
20 |
21 | nav, .search, .user-panel {
22 | margin-left: 0;
23 | }
24 |
25 | .user-panel {
26 | padding-left: 0;
27 | }
28 |
29 | nav {
30 | margin-top: 0.2rem;
31 | }
32 |
33 | .search, .user-panel {
34 | margin-top: 0.7rem;
35 | }
36 |
37 | .search {
38 | flex-grow: 1;
39 | align-self: stretch;
40 | }
41 | }
42 | }
43 |
44 | @media screen and (max-width: 679px) {
45 | .mobile-only {
46 | display: block;
47 | }
48 |
49 | header {
50 | .user-panel {
51 | flex-direction: column;
52 |
53 | .user-panel-links {
54 | margin-left: 0;
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/sass/_normalize.scss:
--------------------------------------------------------------------------------
1 | /*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /**
4 | * 1. Change the default font family in all browsers (opinionated).
5 | * 2. Prevent adjustments of font size after orientation changes in IE and iOS.
6 | */
7 |
8 | html {
9 | font-family: sans-serif; /* 1 */
10 | -ms-text-size-adjust: 100%; /* 2 */
11 | -webkit-text-size-adjust: 100%; /* 2 */
12 | }
13 |
14 | /**
15 | * Remove the margin in all browsers (opinionated).
16 | */
17 |
18 | body {
19 | margin: 0;
20 | }
21 |
22 | /* HTML5 display definitions
23 | ========================================================================== */
24 |
25 | /**
26 | * Add the correct display in IE 9-.
27 | * 1. Add the correct display in Edge, IE, and Firefox.
28 | * 2. Add the correct display in IE.
29 | */
30 |
31 | article,
32 | aside,
33 | details, /* 1 */
34 | figcaption,
35 | figure,
36 | footer,
37 | header,
38 | main, /* 2 */
39 | menu,
40 | nav,
41 | section,
42 | summary { /* 1 */
43 | display: block;
44 | }
45 |
46 | /**
47 | * Add the correct display in IE 9-.
48 | */
49 |
50 | audio,
51 | canvas,
52 | progress,
53 | video {
54 | display: inline-block;
55 | }
56 |
57 | /**
58 | * Add the correct display in iOS 4-7.
59 | */
60 |
61 | audio:not([controls]) {
62 | display: none;
63 | height: 0;
64 | }
65 |
66 | /**
67 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
68 | */
69 |
70 | progress {
71 | vertical-align: baseline;
72 | }
73 |
74 | /**
75 | * Add the correct display in IE 10-.
76 | * 1. Add the correct display in IE.
77 | */
78 |
79 | template, /* 1 */
80 | [hidden] {
81 | display: none;
82 | }
83 |
84 | /* Links
85 | ========================================================================== */
86 |
87 | /**
88 | * 1. Remove the gray background on active links in IE 10.
89 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
90 | */
91 |
92 | a {
93 | background-color: transparent; /* 1 */
94 | -webkit-text-decoration-skip: objects; /* 2 */
95 | }
96 |
97 | /**
98 | * Remove the outline on focused links when they are also active or hovered
99 | * in all browsers (opinionated).
100 | */
101 |
102 | a:active,
103 | a:hover {
104 | outline-width: 0;
105 | }
106 |
107 | /* Text-level semantics
108 | ========================================================================== */
109 |
110 | /**
111 | * 1. Remove the bottom border in Firefox 39-.
112 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
113 | */
114 |
115 | abbr[title] {
116 | border-bottom: none; /* 1 */
117 | text-decoration: underline; /* 2 */
118 | text-decoration: underline dotted; /* 2 */
119 | }
120 |
121 | /**
122 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6.
123 | */
124 |
125 | b,
126 | strong {
127 | font-weight: inherit;
128 | }
129 |
130 | /**
131 | * Add the correct font weight in Chrome, Edge, and Safari.
132 | */
133 |
134 | b,
135 | strong {
136 | font-weight: bolder;
137 | }
138 |
139 | /**
140 | * Add the correct font style in Android 4.3-.
141 | */
142 |
143 | dfn {
144 | font-style: italic;
145 | }
146 |
147 | /**
148 | * Correct the font size and margin on `h1` elements within `section` and
149 | * `article` contexts in Chrome, Firefox, and Safari.
150 | */
151 |
152 | h1 {
153 | font-size: 2em;
154 | margin: 0.67em 0;
155 | }
156 |
157 | /**
158 | * Add the correct background and color in IE 9-.
159 | */
160 |
161 | mark {
162 | background-color: #ff0;
163 | color: #000;
164 | }
165 |
166 | /**
167 | * Add the correct font size in all browsers.
168 | */
169 |
170 | small {
171 | font-size: 80%;
172 | }
173 |
174 | /**
175 | * Prevent `sub` and `sup` elements from affecting the line height in
176 | * all browsers.
177 | */
178 |
179 | sub,
180 | sup {
181 | font-size: 75%;
182 | line-height: 0;
183 | position: relative;
184 | vertical-align: baseline;
185 | }
186 |
187 | sub {
188 | bottom: -0.25em;
189 | }
190 |
191 | sup {
192 | top: -0.5em;
193 | }
194 |
195 | /* Embedded content
196 | ========================================================================== */
197 |
198 | /**
199 | * Remove the border on images inside links in IE 10-.
200 | */
201 |
202 | img {
203 | border-style: none;
204 | }
205 |
206 | /**
207 | * Hide the overflow in IE.
208 | */
209 |
210 | svg:not(:root) {
211 | overflow: hidden;
212 | }
213 |
214 | /* Grouping content
215 | ========================================================================== */
216 |
217 | /**
218 | * 1. Correct the inheritance and scaling of font size in all browsers.
219 | * 2. Correct the odd `em` font sizing in all browsers.
220 | */
221 |
222 | code,
223 | kbd,
224 | pre,
225 | samp {
226 | font-family: monospace, monospace; /* 1 */
227 | font-size: 1em; /* 2 */
228 | }
229 |
230 | /**
231 | * Add the correct margin in IE 8.
232 | */
233 |
234 | figure {
235 | margin: 1em 40px;
236 | }
237 |
238 | /**
239 | * 1. Add the correct box sizing in Firefox.
240 | * 2. Show the overflow in Edge and IE.
241 | */
242 |
243 | hr {
244 | box-sizing: content-box; /* 1 */
245 | height: 0; /* 1 */
246 | overflow: visible; /* 2 */
247 | }
248 |
249 | /* Forms
250 | ========================================================================== */
251 |
252 | /**
253 | * 1. Change font properties to `inherit` in all browsers (opinionated).
254 | * 2. Remove the margin in Firefox and Safari.
255 | */
256 |
257 | button,
258 | input,
259 | select,
260 | textarea {
261 | font: inherit; /* 1 */
262 | margin: 0; /* 2 */
263 | }
264 |
265 | /**
266 | * Restore the font weight unset by the previous rule.
267 | */
268 |
269 | optgroup {
270 | font-weight: bold;
271 | }
272 |
273 | /**
274 | * Show the overflow in IE.
275 | * 1. Show the overflow in Edge.
276 | */
277 |
278 | button,
279 | input { /* 1 */
280 | overflow: visible;
281 | }
282 |
283 | /**
284 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
285 | * 1. Remove the inheritance of text transform in Firefox.
286 | */
287 |
288 | button,
289 | select { /* 1 */
290 | text-transform: none;
291 | }
292 |
293 | /**
294 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
295 | * controls in Android 4.
296 | * 2. Correct the inability to style clickable types in iOS and Safari.
297 | */
298 |
299 | button,
300 | html [type="button"], /* 1 */
301 | [type="reset"],
302 | [type="submit"] {
303 | -webkit-appearance: button; /* 2 */
304 | }
305 |
306 | /**
307 | * Remove the inner border and padding in Firefox.
308 | */
309 |
310 | button::-moz-focus-inner,
311 | [type="button"]::-moz-focus-inner,
312 | [type="reset"]::-moz-focus-inner,
313 | [type="submit"]::-moz-focus-inner {
314 | border-style: none;
315 | padding: 0;
316 | }
317 |
318 | /**
319 | * Restore the focus styles unset by the previous rule.
320 | */
321 |
322 | button:-moz-focusring,
323 | [type="button"]:-moz-focusring,
324 | [type="reset"]:-moz-focusring,
325 | [type="submit"]:-moz-focusring {
326 | outline: 1px dotted ButtonText;
327 | }
328 |
329 | /**
330 | * Change the border, margin, and padding in all browsers (opinionated).
331 | */
332 |
333 | fieldset {
334 | border: 1px solid #c0c0c0;
335 | margin: 0 2px;
336 | padding: 0.35em 0.625em 0.75em;
337 | }
338 |
339 | /**
340 | * 1. Correct the text wrapping in Edge and IE.
341 | * 2. Correct the color inheritance from `fieldset` elements in IE.
342 | * 3. Remove the padding so developers are not caught out when they zero out
343 | * `fieldset` elements in all browsers.
344 | */
345 |
346 | legend {
347 | box-sizing: border-box; /* 1 */
348 | color: inherit; /* 2 */
349 | display: table; /* 1 */
350 | max-width: 100%; /* 1 */
351 | padding: 0; /* 3 */
352 | white-space: normal; /* 1 */
353 | }
354 |
355 | /**
356 | * Remove the default vertical scrollbar in IE.
357 | */
358 |
359 | textarea {
360 | overflow: auto;
361 | }
362 |
363 | /**
364 | * 1. Add the correct box sizing in IE 10-.
365 | * 2. Remove the padding in IE 10-.
366 | */
367 |
368 | [type="checkbox"],
369 | [type="radio"] {
370 | box-sizing: border-box; /* 1 */
371 | padding: 0; /* 2 */
372 | }
373 |
374 | /**
375 | * Correct the cursor style of increment and decrement buttons in Chrome.
376 | */
377 |
378 | [type="number"]::-webkit-inner-spin-button,
379 | [type="number"]::-webkit-outer-spin-button {
380 | height: auto;
381 | }
382 |
383 | /**
384 | * 1. Correct the odd appearance in Chrome and Safari.
385 | * 2. Correct the outline style in Safari.
386 | */
387 |
388 | [type="search"] {
389 | -webkit-appearance: textfield; /* 1 */
390 | outline-offset: -2px; /* 2 */
391 | }
392 |
393 | /**
394 | * Remove the inner padding and cancel buttons in Chrome and Safari on OS X.
395 | */
396 |
397 | [type="search"]::-webkit-search-cancel-button,
398 | [type="search"]::-webkit-search-decoration {
399 | -webkit-appearance: none;
400 | }
401 |
402 | /**
403 | * Correct the text style of placeholders in Chrome, Edge, and Safari.
404 | */
405 |
406 | ::-webkit-input-placeholder {
407 | color: inherit;
408 | opacity: 0.54;
409 | }
410 |
411 | /**
412 | * 1. Correct the inability to style clickable types in iOS and Safari.
413 | * 2. Change font properties to `inherit` in Safari.
414 | */
415 |
416 | ::-webkit-file-upload-button {
417 | -webkit-appearance: button; /* 1 */
418 | font: inherit; /* 2 */
419 | }
--------------------------------------------------------------------------------
/sass/_privacy.scss:
--------------------------------------------------------------------------------
1 | /*
2 | Styles related to privacy-related settings.
3 | */
4 |
5 | // Bar under the navigation for privacy disclaimer
6 | .privacy-disclaimer {
7 | position: -webkit-sticky;
8 | position: sticky;
9 | display: none;
10 | top: 0;
11 | width: 100%;
12 | margin-top: -1rem;
13 | margin-bottom: 1rem;
14 |
15 | z-index: 1000000;
16 |
17 | background: #2f2f2f;
18 | color: #eeeeee;
19 |
20 | .wrapper {
21 | padding: 2rem 1rem;
22 | display: flex;
23 | flex-wrap: wrap;
24 | align-items: center;
25 | }
26 |
27 | &-text a {
28 | color: lighten($accent6, 30%);
29 |
30 | &:hover, &:active, &:focus {
31 | color: lighten($accent6, 35%);
32 | }
33 | }
34 |
35 | &-links {
36 | margin-left: auto;
37 |
38 | a {
39 | margin-left: 0.5rem;
40 | }
41 |
42 | a.btn-cookie-disclaimer-decline {
43 | color: #eeeeee;
44 |
45 | &:hover, &:active, &:focus {
46 | color: #fafafa;
47 | }
48 | }
49 | }
50 | }
51 |
52 | .privacy-policy {
53 | display: flex;
54 | flex-direction: column;
55 |
56 | .privacy-settings {
57 | margin: 1rem 0;
58 |
59 | a.btn-privacy-settings-decline {
60 | margin-left: 0.5rem;
61 | }
62 | }
63 |
64 | .donation-links {
65 | margin: 1rem 0;
66 | list-style-type: none;
67 | padding-left: 0.5rem;
68 |
69 | li {
70 | margin-bottom: 0.25rem;
71 | }
72 |
73 | i {
74 | text-align: center;
75 | width: 1rem;
76 | margin-right: 0.5rem;
77 | font-size: 1.1rem;
78 | }
79 | }
80 |
81 | .privacy-advertisers-list {
82 | padding-left: 2rem;
83 | column-count: 4;
84 | column-gap: 2.5rem;
85 | margin-bottom: 1rem;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/sass/_reboot.scss:
--------------------------------------------------------------------------------
1 | @import "base";
2 |
3 | // scss-lint:disable ImportantRule, QualifyingElement, DuplicateProperty
4 |
5 | // Reboot
6 | //
7 | // Global resets to common HTML elements and more for easier usage by Bootstrap.
8 | // Adds additional rules on top of Normalize.css, including several overrides.
9 |
10 | // Reset the box-sizing
11 | //
12 | // Change from `box-sizing: content-box` to `border-box` so that when you add
13 | // `padding` or `border`s to an element, the overall declared `width` does not
14 | // change. For example, `width: 100px;` will always be `100px` despite the
15 | // `border: 10px solid red;` and `padding: 20px;`.
16 | //
17 | // Heads up! This reset may cause conflicts with some third-party widgets. For
18 | // recommendations on resolving such conflicts, see
19 | // http://getbootstrap.com/getting-started/#third-box-sizing.
20 | //
21 | // Credit: https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/
22 |
23 | html {
24 | box-sizing: border-box;
25 | width: 100%;
26 | max-width: 100%;
27 | }
28 |
29 | body {
30 | width: 100%;
31 | max-width: 100%;
32 | }
33 |
34 | *,
35 | *::before,
36 | *::after {
37 | box-sizing: inherit;
38 | }
39 |
40 | // Make viewport responsive
41 | //
42 | // @viewport is needed because IE 10+ doesn't honor in
43 | // some cases. See http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/.
44 | // Eventually @viewport will replace .
45 | //
46 | // However, `device-width` is broken on IE 10 on Windows (Phone) 8,
47 | // (see http://timkadlec.com/2013/01/windows-phone-8-and-device-width/ and https://github.com/twbs/bootstrap/issues/10497)
48 | // and the fix for that involves a snippet of JavaScript to sniff the user agent
49 | // and apply some conditional CSS.
50 | //
51 | // See http://getbootstrap.com/getting-started/#support-ie10-width for the relevant hack.
52 | //
53 | // Wrap `@viewport` with `@at-root` for when folks do a nested import (e.g.,
54 | // `.class-name { @import "bootstrap"; }`).
55 | @at-root {
56 | @-ms-viewport {
57 | width: device-width;
58 | }
59 | }
60 |
61 | //
62 | // Reset HTML, body, and more
63 | //
64 |
65 | html {
66 | // Sets a specific default `font-size` for user with `rem` type scales.
67 | font-size: $font-size-root;
68 | // As a side-effect of setting the @viewport above,
69 | // IE11 & Edge make the scrollbar overlap the content and automatically hide itself when not in use.
70 | // Unfortunately, the auto-showing of the scrollbar is sometimes too sensitive,
71 | // thus making it hard to click on stuff near the right edge of the page.
72 | // So we add this style to force IE11 & Edge to use a "normal", non-overlapping, non-auto-hiding scrollbar.
73 | // See https://github.com/twbs/bootstrap/issues/18543
74 | -ms-overflow-style: scrollbar;
75 | // Changes the default tap highlight to be completely transparent in iOS.
76 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
77 | }
78 |
79 | body {
80 | // Make the `body` use the `font-size-root`
81 | font-family: $font-family-base;
82 | font-size: $font-size-base;
83 | line-height: $line-height-base;
84 | // Go easy on the eyes and use something other than `#000` for text
85 | color: $body-color;
86 | // By default, `` has no `background-color` so we set one as a best practice.
87 | background-color: $body-bg;
88 | }
89 |
90 | // Suppress the focus outline on elements that cannot be accessed via keyboard.
91 | // This prevents an unwanted focus outline from appearing around elements that
92 | // might still respond to pointer events.
93 | //
94 | // Credit: https://github.com/suitcss/base
95 | [tabindex="-1"]:focus {
96 | outline: none !important;
97 | }
98 |
99 | //
100 | // Typography
101 | //
102 |
103 | // Remove top margins from headings
104 | //
105 | // By default, ``-`` all receive top and bottom margins. We nuke the top
106 | // margin for easier control within type scales as it avoids margin collapsing.
107 | h1, h2, h3, h4, h5, h6 {
108 | margin-top: 0;
109 | margin-bottom: .5rem;
110 | }
111 |
112 | // Reset margins on paragraphs
113 | //
114 | // Similarly, the top margin on ` `s get reset. However, we also reset the
115 | // bottom margin to use `rem` units instead of `em`.
116 | p {
117 | margin-top: 0;
118 | margin-bottom: 1rem;
119 | }
120 |
121 | // Abbreviations and acronyms
122 | abbr[title],
123 | // Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257
124 | abbr[data-original-title] {
125 | cursor: help;
126 | border-bottom: 1px dotted $abbr-border-color;
127 | }
128 |
129 | address {
130 | margin-bottom: 1rem;
131 | font-style: normal;
132 | line-height: inherit;
133 | }
134 |
135 | ol,
136 | ul,
137 | dl {
138 | margin-top: 0;
139 | margin-bottom: 1rem;
140 | }
141 |
142 | ol ol,
143 | ul ul,
144 | ol ul,
145 | ul ol {
146 | margin-bottom: 0;
147 | }
148 |
149 | dt {
150 | font-weight: $dt-font-weight;
151 | }
152 |
153 | dd {
154 | margin-bottom: .5rem;
155 | margin-left: 0; // Undo browser default
156 | }
157 |
158 | blockquote {
159 | margin: 0 0 1rem;
160 | }
161 |
162 | //
163 | // Links
164 | //
165 | a {
166 | color: $link-color;
167 | text-decoration: $link-decoration;
168 |
169 | &:hover, &:focus {
170 | color: $link-hover-color;
171 | text-decoration: $link-hover-decoration;
172 | }
173 |
174 | &:focus {
175 | // Default
176 | outline: thin dotted;
177 | // WebKit
178 | outline: 5px auto -webkit-focus-ring-color;
179 | outline-offset: -2px;
180 | }
181 | }
182 |
183 | // And undo these styles for placeholder links/named anchors (without href)
184 | // which have not been made explicitly keyboard-focusable (without tabindex).
185 | // It would be more straightforward to just use a[href] in previous block, but that
186 | // causes specificity issues in many other styles that are too complex to fix.
187 | // See https://github.com/twbs/bootstrap/issues/19402
188 |
189 | a:not([href]):not([tabindex]) {
190 | color: inherit;
191 | text-decoration: none;
192 |
193 | &:hover, &:focus {
194 | color: inherit;
195 | text-decoration: none;
196 | }
197 |
198 | &:focus {
199 | outline: none;
200 | }
201 | }
202 |
203 | //
204 | // Code
205 | //
206 |
207 | pre {
208 | // Remove browser default top margin
209 | margin-top: 0;
210 | // Reset browser default of `1em` to use `rem`s
211 | margin-bottom: 1rem;
212 | // Normalize v4 removed this property, causing `
` content to break out of wrapping code snippets
213 | //overflow: auto;
214 | }
215 |
216 | //
217 | // Figures
218 | //
219 |
220 | figure {
221 | // Normalize adds `margin` to `figure`s as browsers apply it inconsistently.
222 | // We reset that to create a better flow in-page.
223 | margin: 0 0 1rem;
224 | }
225 |
226 | //
227 | // Images
228 | //
229 |
230 | img {
231 | // By default, ` `s are `inline-block`. This assumes that, and vertically
232 | // centers them. This won't apply should you reset them to `block` level.
233 | vertical-align: middle;
234 | // Note: ` `s are deliberately not made responsive by default.
235 | // For the rationale behind this, see the comments on the `.img-fluid` class.
236 | }
237 |
238 | // iOS "clickable elements" fix for role="button"
239 | //
240 | // Fixes "clickability" issue (and more generally, the firing of events such as focus as well)
241 | // for traditionally non-focusable elements with role="button"
242 | // see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
243 |
244 | [role="button"] {
245 | cursor: pointer;
246 | }
247 |
248 | // Avoid 300ms click delay on touch devices that support the `touch-action` CSS property.
249 | //
250 | // In particular, unlike most other browsers, IE11+Edge on Windows 10 on touch devices and IE Mobile 10-11
251 | // DON'T remove the click delay when ` ` is present.
252 | // However, they DO support removing the click delay via `touch-action: manipulation`.
253 | // See:
254 | // * http://v4-alpha.getbootstrap.com/content/reboot/#click-delay-optimization-for-touch
255 | // * http://caniuse.com/#feat=css-touch-action
256 | // * http://patrickhlauke.github.io/touch/tests/results/#suppressing-300ms-delay
257 |
258 | a,
259 | area,
260 | button,
261 | [role="button"],
262 | input,
263 | label,
264 | select,
265 | summary,
266 | textarea {
267 | touch-action: manipulation;
268 | }
269 |
270 | //
271 | // Tables
272 | //
273 |
274 | table {
275 | // No longer part of Normalize since v4
276 | border-collapse: collapse;
277 | // Reset for nesting within parents with `background-color`.
278 | background-color: $table-bg;
279 | }
280 |
281 | caption {
282 | padding-top: $table-cell-padding;
283 | padding-bottom: $table-cell-padding;
284 | color: $text-muted;
285 | text-align: left;
286 | caption-side: bottom;
287 | }
288 |
289 | th {
290 | // Centered by default, but left-align-ed to match the `td`s below.
291 | text-align: left;
292 | }
293 |
294 | //
295 | // Forms
296 | //
297 |
298 | label {
299 | // Allow labels to use `margin` for spacing.
300 | display: inline-block;
301 | margin-bottom: .5rem;
302 | }
303 |
304 | // Work around a Firefox/IE bug where the transparent `button` background
305 | // results in a loss of the default `button` focus styles.
306 | //
307 | // Credit: https://github.com/suitcss/base/
308 | button:focus {
309 | outline: 1px dotted;
310 | outline: 5px auto -webkit-focus-ring-color;
311 | }
312 |
313 | input,
314 | button,
315 | select,
316 | textarea {
317 | // Remove all `margin`s so our classes don't have to do it themselves.
318 | margin: 0;
319 | // Normalize includes `font: inherit;`, so `font-family`. `font-size`, etc are
320 | // properly inherited. However, `line-height` isn't addressed there. Using this
321 | // ensures we don't need to unnecessarily redeclare the global font stack.
322 | line-height: inherit;
323 | // iOS adds rounded borders by default
324 | border-radius: 0;
325 | }
326 |
327 | input[type="radio"],
328 | input[type="checkbox"] {
329 | // Apply a disabled cursor for radios and checkboxes.
330 | //
331 | // Note: Neither radios nor checkboxes can be readonly.
332 | &:disabled {
333 | cursor: $cursor-disabled;
334 | }
335 | }
336 |
337 | input[type="date"],
338 | input[type="time"],
339 | input[type="datetime-local"],
340 | input[type="month"] {
341 | // Remove the default appearance of temporal inputs to avoid a Mobile Safari
342 | // bug where setting a custom line-height prevents text from being vertically
343 | // centered within the input.
344 | //
345 | // Bug report: https://github.com/twbs/bootstrap/issues/11266
346 | -webkit-appearance: listbox;
347 | }
348 |
349 | textarea {
350 | // Textareas should really only resize vertically so they don't break their (horizontal) containers.
351 | resize: vertical;
352 | }
353 |
354 | fieldset {
355 | // Chrome and Firefox set a `min-width: min-content;` on fieldsets,
356 | // so we reset that to ensure it behaves more like a standard block element.
357 | // See https://github.com/twbs/bootstrap/issues/12359.
358 | min-width: 0;
359 | // Reset the default outline behavior of fieldsets so they don't affect page layout.
360 | padding: 0;
361 | margin: 0;
362 | border: 0;
363 | }
364 |
365 | legend {
366 | // Reset the entire legend element to match the `fieldset`
367 | display: block;
368 | width: 100%;
369 | padding: 0;
370 | margin-bottom: .5rem;
371 | font-size: 1.5rem;
372 | line-height: inherit;
373 | }
374 |
375 | input[type="search"] {
376 | // This overrides the extra rounded corners on search inputs in iOS so that our
377 | // `.form-control` class can properly style them. Note that this cannot simply
378 | // be added to `.form-control` as it's not specific enough. For details, see
379 | // https://github.com/twbs/bootstrap/issues/11586.
380 | -webkit-appearance: none;
381 | }
382 |
383 | // todo: needed?
384 | output {
385 | display: inline-block;
386 | // font-size: $font-size-base;
387 | // line-height: $line-height;
388 | // color: $input-color;
389 | }
390 |
391 | // Always hide an element with the `hidden` HTML attribute (from PureCSS).
392 | [hidden] {
393 | display: none !important;
394 | }
--------------------------------------------------------------------------------
/sass/_scrollpane.scss:
--------------------------------------------------------------------------------
1 | .jspContainer {
2 | overflow: hidden;
3 | position: relative;
4 | }
5 |
6 | .jspPane {
7 | position: absolute;
8 | padding: 0 !important;
9 | }
10 |
11 | .jspVerticalBar {
12 | position: absolute;
13 | top: 0;
14 | right: 0;
15 | width: 8px;
16 | height: 100%;
17 | }
18 |
19 | .jspHorizontalBar {
20 | position: absolute;
21 | bottom: 0;
22 | left: 0;
23 | width: 100%;
24 | height: 8px;
25 | }
26 |
27 | .jspCap {
28 | display: none;
29 | }
30 |
31 | .jspHorizontalBar .jspCap {
32 | float: left;
33 | }
34 |
35 | .jspTrack {
36 | background: $scrollbar-track-bg;
37 | position: relative;
38 | }
39 |
40 | .jspDrag {
41 | background: $scrollbar-drag-bg;
42 | position: relative;
43 | top: 0;
44 | left: 0;
45 | cursor: pointer;
46 | transition: background 0.4s ease-in-out;
47 |
48 | &:hover, &:active, &:focus {
49 | background: $scrollbar-drag-hover-bg;
50 | }
51 | }
52 |
53 | .jspHorizontalBar .jspTrack,
54 | .jspHorizontalBar .jspDrag {
55 | float: left;
56 | height: 100%;
57 | }
58 |
59 | .jspArrow {
60 | background: #50506d;
61 | text-indent: -20000px;
62 | display: block;
63 | cursor: pointer;
64 | padding: 0;
65 | margin: 0;
66 | }
67 |
68 | .jspArrow.jspDisabled {
69 | cursor: default;
70 | background: #80808d;
71 | }
72 |
73 | .jspVerticalBar .jspArrow {
74 | height: 16px;
75 | }
76 |
77 | .jspHorizontalBar .jspArrow {
78 | width: 16px;
79 | float: left;
80 | height: 100%;
81 | }
82 |
83 | .jspVerticalBar .jspArrow:focus {
84 | outline: none;
85 | }
86 |
87 | .jspCorner {
88 | background: #eeeef4;
89 | float: left;
90 | height: 100%;
91 | }
92 |
93 | /* Yuk! CSS Hack for IE6 3 pixel bug :( */
94 | * html .jspCorner {
95 | margin: 0 -3px 0 0;
96 | }
97 |
--------------------------------------------------------------------------------
/sass/_sidebars.scss:
--------------------------------------------------------------------------------
1 | .sidebar-wrapper {
2 | position: relative;
3 | box-sizing: border-box;
4 | display: flex;
5 |
6 | .sidebar-left {
7 | margin-right: 1rem;
8 | }
9 |
10 | .sidebar-right {
11 | margin-left: 1rem;
12 | }
13 |
14 | .sidebar-left, .sidebar-right, aside {
15 | min-width: 330px;
16 | max-width: 330px;
17 | }
18 |
19 | .sidebar-sticky {
20 | position: -webkit-sticky;
21 | position: sticky;
22 | top: 1rem;
23 | }
24 |
25 | .sidebar-sticky, .sidebar-sticky aside {
26 | min-width: 330px;
27 | max-width: 330px;
28 | max-height: calc(100vh - (90px + 2rem));
29 | }
30 |
31 | .sidebar-wrapper-content, .sidebar-sticky-wrapper-content {
32 | flex-grow: 1;
33 | }
34 |
35 | .sidebar-sticky-wrapper-content {
36 | min-height: calc(100vh - 182px); /* 182px, a good size for the footer, Doesn't scroll on anything >= 900 */
37 | max-width: calc(100% - 330px - 1rem);
38 | }
39 | }
40 |
41 | .open-sidebar, .close-sidebar {
42 | display: none;
43 | }
44 |
45 | section.sidebar-nav h2 {
46 | border-bottom: none;
47 | }
48 |
49 | section.sidebar-nav {
50 | background: none;
51 | box-shadow: none;
52 | }
53 |
54 | .collapsible-icon {
55 | font-size: 0.8rem !important;
56 | margin-right: 0.25rem;
57 | }
58 |
59 | section.sidebar-nav > ul {
60 | list-style-type: none;
61 | margin: 0;
62 | padding: 0;
63 | max-height: calc(100vh - (90px + 42px + 2rem));
64 | overflow-y: scroll;
65 |
66 | .elem-text {
67 | display: block;
68 | }
69 |
70 | .elem-active {
71 | font-weight: bold;
72 |
73 | & > * {
74 | font-weight: normal;
75 | }
76 |
77 | & > .elem-text {
78 | font-weight: bold;
79 | }
80 | }
81 |
82 | .jspPane {
83 | background: $section-bg;
84 | border-radius: 0 0 3px 3px;
85 | box-shadow: 0 1px 2px $section-highlight;
86 | }
87 |
88 | & > li, .jspPane > li {
89 | & > .elem-text {
90 | display: block;
91 | line-height: 1rem;
92 | color: $pagination-color;
93 | background: lighten($accent1, 5%);
94 | padding: 0.5rem;
95 | }
96 |
97 | & > a.elem-text {
98 | transition: background 0.2s ease-in-out;
99 |
100 | &:hover, &:active, &:focus {
101 | background: lighten($accent1, 10%);
102 | }
103 | }
104 |
105 | ul {
106 | list-style-type: none;
107 | background: $section-bg;
108 | margin: 0;
109 | padding: 0;
110 |
111 | li {
112 | padding: 0.2rem 0.5rem;
113 | }
114 | }
115 | }
116 |
117 | a {
118 | display: block;
119 | }
120 | }
121 |
122 | @media screen and (max-width: $mobile-break) {
123 | .logo {
124 | width: 100%;
125 | text-align: center;
126 | display: flex;
127 | justify-content: center;
128 | align-items: center;
129 |
130 | .logo-image {
131 | margin: 0 auto;
132 | }
133 | }
134 |
135 | body.sidebar-active {
136 | position: fixed;
137 | width: 100%;
138 | }
139 |
140 | .open-sidebar {
141 | display: block;
142 | font-size: 2rem;
143 | }
144 |
145 | .close-sidebar {
146 | display: inline;
147 | color: $pagination-color;
148 | font-size: 1.5rem;
149 | margin-right: 0.5rem;
150 |
151 | &:hover, &:active {
152 | color: darken($pagination-color, 5%);
153 | }
154 | }
155 |
156 | .sidebar-wrapper {
157 | flex-direction: column;
158 |
159 | &-content, .sidebar-sticky-wrapper-content {
160 | max-width: 100%;
161 | min-height: 0;
162 | }
163 |
164 | .sidebar-left.sidebar-sticky {
165 | transform: translateX(-100%);
166 | }
167 |
168 | .sidebar-right.sidebar-sticky {
169 | transform: translateX(100%);
170 | }
171 |
172 | & > .sidebar-left, & > .sidebar-right, & > aside, & > .sidebar-left > aside, & > .sidebar-right > aside {
173 | width: 100%;
174 | max-width: 100%;
175 | display: flex;
176 | align-items: center;
177 | flex-direction: column;
178 | }
179 |
180 | .sidebar-left {
181 | margin-right: 0;
182 | }
183 |
184 | .sidebar-right {
185 | margin-left: 0;
186 | }
187 |
188 | .sidebar-sticky {
189 | position: fixed;
190 | top: 0;
191 | left: 0;
192 | z-index: 10000;
193 | width: 100%;
194 | height: 100%;
195 | transition: transform 0.75s ease-in-out;
196 | max-width: 100%;
197 | max-height: 100%;
198 |
199 | &.active-sidebar {
200 | transform: translateX(0);
201 | }
202 |
203 | aside, section {
204 | height: 100%;
205 | box-shadow: none;
206 | max-width: 100%;
207 | max-height: 100%;
208 | }
209 |
210 | section.sidebar-nav {
211 | background: $section-bg;
212 | }
213 |
214 | section.sidebar-nav > ul {
215 | max-height: calc(100% - 42px);
216 |
217 | .jspPane {
218 | border-radius: 0;
219 | box-shadow: none;
220 | }
221 | }
222 |
223 | section {
224 | &, h2, .heading {
225 | border-radius: 0;
226 | }
227 |
228 | h2, .heading {
229 | display: flex;
230 | align-items: center;
231 | }
232 | }
233 | }
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/sass/_styles.scss:
--------------------------------------------------------------------------------
1 | /*
2 | Generic stylesheet shared across all platforms
3 | */
4 | @import "base";
5 | @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300);
6 | @import url('https://fonts.googleapis.com/css?family=Source+Code+Pro:300,400,700&subset=latin-ext');
7 |
8 | @import "layout";
9 | // All sites may contain ads
10 | @import "ads";
11 |
12 | .clearfix {
13 | &:after {
14 | content: ".";
15 | clear: both;
16 | display: block;
17 | visibility: hidden;
18 | height: 0;
19 | }
20 | }
--------------------------------------------------------------------------------
/sass/_styles_dark.scss:
--------------------------------------------------------------------------------
1 | @import "theme_dark";
2 | @import "normalize";
3 | @import "reboot";
4 | @import "styles";
--------------------------------------------------------------------------------
/sass/_styles_light.scss:
--------------------------------------------------------------------------------
1 | @import "theme_light";
2 | @import "normalize";
3 | @import "reboot";
4 | @import "styles";
--------------------------------------------------------------------------------
/sass/_theme_dark.scss:
--------------------------------------------------------------------------------
1 | $accent1: #2e4460;
2 | $accent2: #de9e59;
3 | $accent3: #c9d44c;
4 | $accent4: #cc4523;
5 | $accent5: #09687C;
6 | $accent6: #378eda;
7 | $accent7: #DFA86A;
8 |
9 | $body-bg: #2B2B2B !default;
10 | $body-color: #bbbbbb !default;
11 | $section-bg: #3C3F41 !default;
12 | $section-highlight: darken($section-bg, 5%) !default;
13 | $section-content-bg: lighten($section-bg, 3%);
14 | $section-content-highlight: lighten($section-bg, 1%);
15 | $header-bg: #2e4460 !default;
16 |
17 | $text-muted: #909090 !default;
18 |
19 | $pagination-bg: #63676c !default;
20 | $pagination-color: #b4bcc1 !default;
21 |
22 | $thread-sticky: #2d3d49;
23 | $thread-locked: #4b4c4e;
24 |
25 | $link-color: $accent6 !default;
26 | $link-decoration: none !default;
27 | $link-hover-color: #44a7ff !default;
28 | $link-hover-decoration: none !default;
29 |
30 | $scrollbar-track-bg: darken($section-bg, 5%);
31 | $scrollbar-drag-bg: #222425;
32 | $scrollbar-drag-hover-bg: $accent7;
33 |
34 | .prettyprint {
35 | background-color: #2b2b2b;
36 | color: #a9b7c6;
37 |
38 | .lit { // literal
39 | color: #6897bb;
40 | }
41 |
42 | .kwd { // keyword
43 | font-weight: bold;
44 | color: #cc7832;
45 | }
46 |
47 | .typ { // type
48 | color: #4e807d;
49 | }
50 |
51 | .str { // string
52 | color: #6a8759;
53 | }
54 |
55 | .pun { // punctuation
56 | color: #e8bf6a;
57 | }
58 |
59 | .com { // comment
60 | color: #629755;
61 | font-style: italic;
62 | }
63 |
64 | .pln { // plain
65 | color: #a9b7c6;
66 | }
67 |
68 | .dec { // declaration
69 | color: #e8bf6a;
70 | }
71 |
72 | .tag { // html tag
73 | color: #e8bf6a;
74 | }
75 |
76 | .atn { // html attribute name
77 | color: #a9b7c6;
78 | }
79 |
80 | .atv { // html attribute value
81 | color: #a5c261;
82 | }
83 | }
--------------------------------------------------------------------------------
/sass/_theme_light.scss:
--------------------------------------------------------------------------------
1 | $accent1: #26303d;
2 | $accent2: #de9e59;
3 | $accent3: #c9d44c;
4 | $accent4: #cb3d35;
5 | $accent5: #1b4e7e;
6 | $accent6: #185b98;
7 | $accent7: #DFA86A;
8 |
9 | $body-bg: #EFF1F3 !default;
10 | $body-color: #373a3c !default;
11 | $section-bg: #fff !default;
12 | $section-highlight: darken($section-bg, 14%) !default;
13 | $section-content-bg: darken($section-bg, 2%);
14 | $section-content-highlight: darken($section-bg, 4%);
15 | $header-bg: #26303d;
16 |
17 | $text-muted: #8c9195 !default;
18 |
19 | $pagination-bg: #BCC1C5 !default;
20 | $pagination-color: #fbf9f9 !default;
21 |
22 | $thread-sticky: lighten($accent1, 75%);
23 | $thread-locked: darken(#fff, 5%);
24 |
25 | $link-color: $accent6 !default;
26 | $link-decoration: none !default;
27 | $link-hover-color: darken($accent6, 20%) !default;
28 | $link-hover-decoration: none !default;
29 |
30 | $scrollbar-track-bg: darken($section-bg, 15%);
31 | $scrollbar-drag-bg: darken($section-bg, 30%);
32 | $scrollbar-drag-hover-bg: $accent7;
33 |
34 | .prettyprint {
35 | background-color: #ffffff;
36 | color: #000000;
37 |
38 | .lit { // literal
39 | color: #0000ff;
40 | }
41 |
42 | .kwd { // keyword
43 | font-weight: bold;
44 | color: #000080;
45 | }
46 |
47 | .typ { // type
48 | color: #20999d;
49 | }
50 |
51 | .str { // string
52 | color: #008000;
53 | }
54 |
55 | .pun { // punctuation
56 | color: #000000;
57 | }
58 |
59 | .com { // comment
60 | color: #808080;
61 | font-style: italic;
62 | }
63 |
64 | .pln { // plain
65 | color: #000000;
66 | }
67 |
68 | .dec { // declaration
69 | color: #000080;
70 | }
71 |
72 | .tag { // html tag
73 | color: #000080;
74 | }
75 |
76 | .atn { // html attribute name
77 | color: #0000ff;
78 | }
79 |
80 | .atv { // html attribute value
81 | color: #008000;
82 | }
83 | }
--------------------------------------------------------------------------------
/sass/documentation_dark.scss:
--------------------------------------------------------------------------------
1 | $font-size-code: 0.9rem;
2 | @import "styles_dark";
3 | @import "sidebars";
4 | @import "scrollpane";
5 | @import "docs";
6 | @import "highlighting_dark";
--------------------------------------------------------------------------------
/sass/documentation_light.scss:
--------------------------------------------------------------------------------
1 | $font-size-code: 0.9rem;
2 | @import "styles_light";
3 | @import "sidebars";
4 | @import "scrollpane";
5 | @import "docs";
6 | @import "highlighting_light";
--------------------------------------------------------------------------------
/sass/forums_dark.scss:
--------------------------------------------------------------------------------
1 | @import "styles_dark";
2 | @import "sidebars";
3 | @import "forums";
--------------------------------------------------------------------------------
/sass/forums_light.scss:
--------------------------------------------------------------------------------
1 | @import "styles_light";
2 | @import "sidebars";
3 | @import "forums";
--------------------------------------------------------------------------------
/sass/website_dark.scss:
--------------------------------------------------------------------------------
1 | @import "styles_dark";
2 | @import "sidebars";
3 | @import "downloads";
4 | @import "scrollpane";
5 | @import "privacy";
6 |
--------------------------------------------------------------------------------
/sass/website_light.scss:
--------------------------------------------------------------------------------
1 | @import "styles_light";
2 | @import "sidebars";
3 | @import "downloads";
4 | @import "scrollpane";
5 | @import "privacy";
6 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | maven {
5 | name = 'MinecraftForge'
6 | url = 'https://maven.minecraftforge.net/'
7 | }
8 | }
9 | }
10 |
11 | plugins {
12 | id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
13 | }
14 |
15 | rootProject.name = 'web'
--------------------------------------------------------------------------------
/templates/base_page.html:
--------------------------------------------------------------------------------
1 | {% include 'page_header.html' %}
2 | {% include 'page_body.html' %}
3 | {% include 'page_footer.html' %}
--------------------------------------------------------------------------------
/templates/page_directory_body.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Tracked project index
4 |
5 |
6 |
7 | Project
8 | Maven
9 | Time
10 | Last version
11 |
12 |
13 |
14 | {% for mvn, info in promos|dictsort %}
15 |
16 | {{ info.name }}
17 | {{ mvn }}
18 | {{ info.last.timestamp | todatetime | humanformatdate }}
19 | {{ info.last.version }}{% if info.last.mc and info.last.mc != 'default' %} for Minecraft {{ info.last.mc }}{% endif %}
20 |
21 | {% endfor %}
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/templates/page_footer.html:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 | {%- if md.global_config['enable_adsense'] %}
20 |
21 | {%- else %}
22 |
23 | {%- endif %}
24 |
25 |
26 | {% if artifact and artifact.config['analytics'] %}{{ artifact.config['analytics'] }}{% endif %}
27 |
28 | {%- if artifact and artifact.config['body_end'] %}
29 | {%- for line in artifact.config['body_end'] %}
30 | {{ line }}
31 | {%- endfor %}
32 | {%- endif %}
33 |
34 |