Redo version page (#777)

* Redo version page

* More work on editing page

* Make saving work

* Finish version editing

* Version creation (base)

* Add creation buttons

* Add file uploader to versions page

* Add version file parsing

* Finish PR

* Fix version page responsiveness and use more consistent card design

* Whoops that wasn't supposed to be there

* Fixes + allow whole page dragging

* Re-add lost merge data

* Remove debug line

* Move back to list btm

Co-authored-by: Prospector <prospectordev@gmail.com>
This commit is contained in:
Geometrically 2022-12-20 11:15:31 -07:00 committed by GitHub
parent 0de19a09ad
commit 6f58e9e7bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1922 additions and 1513 deletions

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>

After

Width:  |  Height:  |  Size: 320 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="9" x2="20" y2="9"></line><line x1="4" y1="15" x2="20" y2="15"></line><line x1="10" y1="3" x2="8" y2="21"></line><line x1="16" y1="3" x2="14" y2="21"></line></svg>

After

Width:  |  Height:  |  Size: 303 B

View File

@ -381,25 +381,25 @@ tr.button-transparent {
background-color: transparent;
border-radius: var(--size-rounded-sm);
&:focus-visible:not(&:disabled) > *,
&:hover:not(&:disabled) > * {
cursor: pointer;
filter: brightness(0.85);
background-color: var(--color-raised-bg);
}
&:focus-visible:not(&:disabled) > *,
&:hover:not(&:disabled) > * {
cursor: pointer;
filter: brightness(0.85);
background-color: var(--color-raised-bg);
}
&:active:not(&:disabled) > * {
filter: brightness(0.8);
background-color: var(--color-raised-bg);
}
&:active:not(&:disabled) > * {
filter: brightness(0.8);
background-color: var(--color-raised-bg);
}
&:disabled > *,
&[disabled] > * {
cursor: not-allowed;
filter: grayscale(50%);
opacity: 0.5;
box-shadow: none;
}
&:disabled > *,
&[disabled] > * {
cursor: not-allowed;
filter: grayscale(50%);
opacity: 0.5;
box-shadow: none;
}
}
.button-within {
@ -886,6 +886,7 @@ textarea.known-error {
}
.known-errors {
min-height: 0;
color: var(--color-special-red);
ul {
@ -999,6 +1000,7 @@ h1 {
color: var(--color-special-red);
}
}
.label__description {
display: block;
margin-block-end: var(--spacing-card-sm);
@ -1067,11 +1069,14 @@ h1 {
width: 15rem;
}
>,
.extend-styling> {
input + *,
.input-group + * {
margin-block-start: var(--spacing-card-md);
> :where(input + *, .input-group + *) {
margin-block-start: var(--spacing-card-md);
}
.input-group {
.multiselect, input {
width: auto;
flex-basis: 0;
}
}
@ -1146,6 +1151,13 @@ h1 {
margin-right: 0;
}
.full-width-inputs {
.multiselect, input, .iconified-input {
width: 100%;
flex-basis: 100%;
}
}
input,
button {
&:disabled {
@ -1356,3 +1368,10 @@ button {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
}
.sr-only {
position: absolute;
width: 0;
height: 0;
overflow: hidden;
}

View File

@ -72,7 +72,7 @@ html {
--color-banner-side: hsl(357, 78%, 40%);
--color-block-quote: var(--color-tooltip-bg);
--color-header-underline: var(--color-tooltip-text);
--color-header-underline: var(--color-divider-dark);
--color-hr: var(--color-text);
--color-table-border: #dfe2e5;
@ -156,7 +156,7 @@ html {
--color-banner-side: hsl(357, 78%, 40%);
--color-block-quote: var(--color-code-bg);
--color-header-underline: var(--color-tooltip-text);
--color-header-underline: var(--color-divider-dark);
--color-hr: var(--color-text);
--color-table-border: #4f5864;

View File

@ -38,9 +38,26 @@
}
.normal-page {
display: flex;
flex-direction: column;
margin: 0 0.75rem;
display: grid;
padding: 0 0.75rem;
grid-template:
'sidebar'
'content'
'info'
/ 100%;
.normal-page__sidebar {
grid-area: sidebar;
}
.normal-page__info {
grid-area: info;
}
.normal-page__content {
grid-area: content;
}
}
.site-header {
@ -49,13 +66,22 @@
@media (min-width: 1024px) {
.normal-page {
flex-direction: row;
margin: 0 auto;
max-width: 80rem;
column-gap: 0.75rem;
grid-template:
'sidebar content' auto
'info content' auto
'dummy content' 1fr
/ 20rem 1fr;
&.alt-layout {
flex-direction: row-reverse;
grid-template:
'content sidebar' auto
'content info' auto
'content dummy' 1fr
/ 1fr 20rem;
}
}
@ -65,11 +91,12 @@
}
.normal-page__content {
width: 60rem;
max-width: 60rem;
}
.site-header {
margin: 0 auto;
max-width: calc(60rem - .75rem);
}
}
@media (min-width: 80rem) {
.normal-page__content {
width: calc(60rem - .75rem);
}
}

View File

@ -0,0 +1,82 @@
<template>
<div
ref="drop_area"
class="drop-area"
@drop.stop.prevent="
(event) => {
$refs.drop_area.style.visibility = 'hidden'
if (event.dataTransfer && event.dataTransfer.files) {
$emit('change', event.dataTransfer.files)
}
}
"
@dragenter.prevent="allowDrag"
@dragover.prevent="allowDrag"
@dragleave.prevent="$refs.drop_area.style.visibility = 'hidden'"
/>
</template>
<script>
export default {
name: 'DropArea',
props: {
accept: {
type: String,
default: '',
},
},
mounted() {
document.addEventListener('dragenter', () => {
this.$refs.drop_area.style.visibility = 'visible'
})
},
methods: {
allowDrag(event) {
const file = event.dataTransfer?.items[0]
if (
file &&
this.accept
.split(',')
.reduce(
(acc, t) => acc || file.type.startsWith(t) || file.type === t,
false
)
) {
// Adds file dropping indicator
event.dataTransfer.dropEffect = 'copy'
event.preventDefault()
}
},
},
}
</script>
<style lang="scss" scoped>
.drop-area {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
visibility: hidden;
background-color: hsla(0, 0%, 0%, 0.5);
transition: visibility 0.2s ease-in-out, background-color 0.1s ease-in-out;
display: flex;
&::before {
--indent: 4rem;
content: ' ';
position: relative;
top: var(--indent);
left: var(--indent);
width: calc(100% - (2 * var(--indent)));
height: calc(100% - (2 * var(--indent)));
border-radius: 1rem;
border: 0.25rem dashed var(--color-button-bg);
}
}
</style>

View File

@ -1,31 +1,26 @@
<template>
<div class="columns">
<label
class="iconified-button"
@drop.prevent="handleDrop"
@dragover.prevent
>
<UploadIcon v-if="showIcon" />
{{ prompt }}
<input
type="file"
:multiple="multiple"
:accept="accept"
@change="handleChange"
/>
</label>
</div>
<label
:class="{ 'long-style': longStyle }"
@drop.prevent="handleDrop"
@dragover.prevent
>
<slot></slot>
{{ prompt }}
<input
type="file"
:multiple="multiple"
:accept="accept"
@change="handleChange"
/>
</label>
</template>
<script>
import { fileIsValid } from '~/plugins/fileUtils'
import UploadIcon from '~/assets/images/utils/upload.svg?inline'
export default {
name: 'FileInput',
components: {
UploadIcon,
},
components: {},
props: {
prompt: {
type: String,
@ -54,6 +49,10 @@ export default {
type: Boolean,
default: false,
},
longStyle: {
type: Boolean,
default: false,
},
},
data() {
return {
@ -92,22 +91,22 @@ label {
svg {
height: 1rem;
}
}
input {
display: none;
}
.known-error label {
border-color: var(--color-special-red) !important;
background-color: var(--color-warning-bg) !important;
span {
border-color: var(--color-special-red);
input {
display: none;
}
&::placeholder {
color: var(--color-warning-text);
&.long-style {
display: flex;
padding: 1.5rem 2rem;
justify-content: center;
align-items: center;
grid-gap: 0.5rem;
background-color: var(--color-button-bg);
border-radius: var(--size-rounded-sm);
border: dashed 0.3rem var(--color-text);
cursor: pointer;
color: var(--color-text-dark);
}
}
</style>

View File

View File

@ -1,5 +1,9 @@
<template>
<div class="layout" :class="{ 'expanded-mobile-nav': isBrowseMenuOpen }">
<div
ref="main_page"
class="layout"
:class="{ 'expanded-mobile-nav': isBrowseMenuOpen }"
>
<header class="site-header" role="presentation">
<section class="navbar card columns" role="navigation">
<section class="skip column" role="presentation">
@ -581,7 +585,7 @@ export default {
margin-bottom: var(--spacing-card-md);
max-width: 100vw;
@media screen and (min-width: 1024px) {
@media screen and (min-width: 1280px) {
border-radius: var(--size-rounded-sm);
max-width: 1280px;
margin-left: auto;
@ -793,15 +797,6 @@ export default {
&.nuxt-link-exact-active {
color: var(--color-button-text-active);
background-color: var(--color-button-bg);
.profile-link {
.username {
margin-block: 0.7rem;
}
.prompt {
display: none;
}
}
}
}
@ -1024,12 +1019,6 @@ export default {
color: var(--color-brand-inverted);
background-color: var(--color-brand);
.profile-link {
.prompt {
display: none;
}
}
.beta-badge {
background-color: var(--color-brand-inverted);
color: var(--color-text-dark);

179
package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "knossos",
"version": "1.0.0",
"dependencies": {
"@iarna/toml": "^2.2.5",
"@nuxtjs/axios": "^5.13.1",
"@nuxtjs/dayjs": "^1.2.0",
"@nuxtjs/markdownit": "^2.0.0",
@ -16,6 +17,8 @@
"cookie-universal-nuxt": "^2.1.5",
"core-js": "^3.9.1",
"highlight.js": "^10.3.2",
"js-yaml": "^4.1.0",
"jszip": "^3.10.1",
"nuxt": "^2.15.3",
"sass": "^1.32.12",
"v-tooltip": "^2.0.3",
@ -1709,6 +1712,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@ -1734,6 +1750,11 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true
},
"node_modules/@iarna/toml": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz",
@ -5320,6 +5341,18 @@
"node": ">=4"
}
},
"node_modules/cosmiconfig/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/cosmiconfig/node_modules/resolve-from": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
@ -6871,6 +6904,19 @@
"node": ">=8"
}
},
"node_modules/eslint/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/eslint/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -8395,6 +8441,11 @@
"node": ">= 4"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
"node_modules/immutable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
@ -9083,17 +9134,21 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/js-yaml/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@ -9146,6 +9201,17 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/kind-of": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
@ -9205,6 +9271,14 @@
"node": ">= 0.8.0"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@ -13904,6 +13978,18 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/svgo/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/svgo/node_modules/nth-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
@ -17708,6 +17794,16 @@
"requires": {
"type-fest": "^0.20.2"
}
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
}
}
},
@ -17733,6 +17829,11 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true
},
"@iarna/toml": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="
},
"@jridgewell/gen-mapping": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz",
@ -20609,6 +20710,15 @@
"resolve-from": "^3.0.0"
}
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"resolve-from": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
@ -21489,6 +21599,16 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -22925,6 +23045,11 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
"immutable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
@ -23412,12 +23537,18 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
"argparse": "^2.0.1"
},
"dependencies": {
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
}
}
},
"jsesc": {
@ -23460,6 +23591,17 @@
"graceful-fs": "^4.1.6"
}
},
"jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"requires": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"kind-of": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
@ -23510,6 +23652,14 @@
"type-check": "~0.4.0"
}
},
"lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"requires": {
"immediate": "~3.0.5"
}
},
"lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@ -27247,6 +27397,15 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"nth-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",

View File

@ -13,6 +13,7 @@
"fix": "eslint --fix --ext .js,.vue --ignore-path .eslintignore ."
},
"dependencies": {
"@iarna/toml": "^2.2.5",
"@nuxtjs/axios": "^5.13.1",
"@nuxtjs/dayjs": "^1.2.0",
"@nuxtjs/markdownit": "^2.0.0",
@ -21,6 +22,8 @@
"cookie-universal-nuxt": "^2.1.5",
"core-js": "^3.9.1",
"highlight.js": "^10.3.2",
"js-yaml": "^4.1.0",
"jszip": "^3.10.1",
"nuxt": "^2.15.3",
"sass": "^1.32.12",
"v-tooltip": "^2.0.3",

View File

@ -9,6 +9,7 @@
</div>
</Modal>
<ModalReport
v-if="$auth.user"
ref="modal_project_report"
:item-id="project.id"
item-type="project"
@ -286,253 +287,249 @@
</ul>
</div>
</div>
<div class="extra-info-desktop card">
<template
v-if="
project.issues_url ||
project.source_url ||
project.wiki_url ||
project.discord_url ||
project.donation_urls.length > 0
</article>
<div class="card normal-page__info">
<template
v-if="
project.issues_url ||
project.source_url ||
project.wiki_url ||
project.discord_url ||
project.donation_urls.length > 0
"
>
<h3 class="card-header">External resources</h3>
<div class="links">
<a
v-if="project.issues_url"
:href="project.issues_url"
class="title"
:target="$external()"
>
<IssuesIcon aria-hidden="true" />
<span>Issues</span>
</a>
<a
v-if="project.source_url"
:href="project.source_url"
class="title"
:target="$external()"
>
<CodeIcon aria-hidden="true" />
<span>Source</span>
</a>
<a
v-if="project.wiki_url"
:href="project.wiki_url"
class="title"
:target="$external()"
>
<WikiIcon aria-hidden="true" />
<span>Wiki</span>
</a>
<a
v-if="project.discord_url"
:href="project.discord_url"
:target="$external()"
>
<DiscordIcon class="shrink" aria-hidden="true" />
<span>Discord</span>
</a>
<a
v-for="(donation, index) in project.donation_urls"
:key="index"
:href="donation.url"
:target="$external()"
>
<BuyMeACoffeeLogo
v-if="donation.id === 'bmac'"
aria-hidden="true"
/>
<PatreonIcon
v-else-if="donation.id === 'patreon'"
aria-hidden="true"
/>
<KoFiIcon
v-else-if="donation.id === 'ko-fi'"
aria-hidden="true"
/>
<PayPalIcon
v-else-if="donation.id === 'paypal'"
aria-hidden="true"
/>
<OpenCollectiveIcon
v-else-if="donation.id === 'open-collective'"
aria-hidden="true"
/>
<HeartIcon v-else-if="donation.id === 'github'" />
<UnknownIcon v-else />
<span v-if="donation.id === 'bmac'">Buy Me a Coffee</span>
<span v-else-if="donation.id === 'patreon'">Patreon</span>
<span v-else-if="donation.id === 'paypal'">PayPal</span>
<span v-else-if="donation.id === 'ko-fi'">Ko-fi</span>
<span v-else-if="donation.id === 'github'">GitHub Sponsors</span>
<span v-else>Donate</span>
</a>
</div>
<hr class="card-divider" />
</template>
<template v-if="featuredVersions.length > 0">
<div class="featured-header">
<h3 class="card-header">Featured versions</h3>
<nuxt-link
v-if="project.versions.length > 0 || currentMember"
:to="`/${project.project_type}/${
project.slug ? project.slug : project.id
}/versions`"
class="goto-link"
>
See all
<ChevronRightIcon
class="featured-header-chevron"
aria-hidden="true"
/>
</nuxt-link>
</div>
<div
v-for="version in featuredVersions"
:key="version.id"
class="featured-version button-transparent"
@click="
$router.push(
`/${project.project_type}/${
project.slug ? project.slug : project.id
}/version/${encodeURI(version.displayUrlEnding)}`
)
"
>
<h3 class="card-header">External resources</h3>
<div class="links">
<a
v-if="project.issues_url"
:href="project.issues_url"
class="title"
:target="$external()"
>
<IssuesIcon aria-hidden="true" />
<span>Issues</span>
</a>
<a
v-if="project.source_url"
:href="project.source_url"
class="title"
:target="$external()"
>
<CodeIcon aria-hidden="true" />
<span>Source</span>
</a>
<a
v-if="project.wiki_url"
:href="project.wiki_url"
class="title"
:target="$external()"
>
<WikiIcon aria-hidden="true" />
<span>Wiki</span>
</a>
<a
v-if="project.discord_url"
:href="project.discord_url"
:target="$external()"
>
<DiscordIcon class="shrink" aria-hidden="true" />
<span>Discord</span>
</a>
<a
v-for="(donation, index) in project.donation_urls"
:key="index"
:href="donation.url"
:target="$external()"
>
<BuyMeACoffeeLogo
v-if="donation.id === 'bmac'"
aria-hidden="true"
/>
<PatreonIcon
v-else-if="donation.id === 'patreon'"
aria-hidden="true"
/>
<KoFiIcon
v-else-if="donation.id === 'ko-fi'"
aria-hidden="true"
/>
<PayPalIcon
v-else-if="donation.id === 'paypal'"
aria-hidden="true"
/>
<OpenCollectiveIcon
v-else-if="donation.id === 'open-collective'"
aria-hidden="true"
/>
<HeartIcon v-else-if="donation.id === 'github'" />
<UnknownIcon v-else />
<span v-if="donation.id === 'bmac'">Buy Me a Coffee</span>
<span v-else-if="donation.id === 'patreon'">Patreon</span>
<span v-else-if="donation.id === 'paypal'">PayPal</span>
<span v-else-if="donation.id === 'ko-fi'">Ko-fi</span>
<span v-else-if="donation.id === 'github'"
>GitHub Sponsors</span
>
<span v-else>Donate</span>
</a>
</div>
<hr class="card-divider" />
</template>
<template v-if="featuredVersions.length > 0">
<div class="featured-header">
<h3 class="card-header">Featured versions</h3>
<a
v-tooltip="
findPrimary(version).filename +
' (' +
$formatBytes(findPrimary(version).size) +
')'
"
:href="findPrimary(version).url"
class="download download-button square-button brand-button"
:title="`Download ${version.name}`"
@click.stop="(event) => event.stopPropagation()"
>
<DownloadIcon aria-hidden="true" />
</a>
<div class="info">
<nuxt-link
v-if="project.versions.length > 0 || currentMember"
:to="`/${project.project_type}/${
project.slug ? project.slug : project.id
}/versions`"
class="goto-link"
}/version/${encodeURI(version.displayUrlEnding)}`"
class="top"
>
See all
<ChevronRightIcon
class="featured-header-chevron"
aria-hidden="true"
/>
{{ version.name }}
</nuxt-link>
</div>
<div
v-for="version in featuredVersions"
:key="version.id"
class="featured-version button-transparent"
@click="
$router.push(
`/${project.project_type}/${
project.slug ? project.slug : project.id
}/version/${encodeURI(version.displayUrlEnding)}`
)
"
>
<a
v-tooltip="
findPrimary(version).filename +
' (' +
$formatBytes(findPrimary(version).size) +
')'
"
:href="findPrimary(version).url"
class="download square-button brand-button"
:title="`Download ${version.name}`"
@click.stop="(event) => event.stopPropagation()"
<div
v-if="version.game_versions.length > 0"
class="game-version item"
>
<DownloadIcon aria-hidden="true" />
</a>
<div class="info">
<nuxt-link
:to="`/${project.project_type}/${
project.slug ? project.slug : project.id
}/version/${encodeURI(version.displayUrlEnding)}`"
class="top"
>
{{ version.name }}
</nuxt-link>
<div
v-if="version.game_versions.length > 0"
class="game-version item"
>
{{
version.loaders.map((x) => $formatCategory(x)).join(', ')
}}
{{ $formatVersion(version.game_versions) }}
</div>
<Badge
v-if="version.version_type === 'release'"
type="release"
color="green"
/>
<Badge
v-else-if="version.version_type === 'beta'"
type="beta"
color="orange"
/>
<Badge
v-else-if="version.version_type === 'alpha'"
type="alpha"
color="red"
/>
{{ version.loaders.map((x) => $formatCategory(x)).join(', ') }}
{{ $formatVersion(version.game_versions) }}
</div>
</div>
<hr class="card-divider" />
</template>
<h3 class="card-header">Project members</h3>
<div
v-for="member in members"
:key="member.user.id"
class="team-member columns button-transparent"
@click="$router.push('/user/' + member.user.username)"
>
<Avatar
:src="member.avatar_url"
:alt="member.username"
size="sm"
circle
/>
<div class="member-info">
<nuxt-link :to="'/user/' + member.user.username" class="name">
<p>{{ member.name }}</p>
</nuxt-link>
<p class="role">{{ member.role }}</p>
<Badge
v-if="version.version_type === 'release'"
type="release"
color="green"
/>
<Badge
v-else-if="version.version_type === 'beta'"
type="beta"
color="orange"
/>
<Badge
v-else-if="version.version_type === 'alpha'"
type="alpha"
color="red"
/>
</div>
</div>
<hr class="card-divider" />
<h3 class="card-header">Technical information</h3>
<div class="infos">
<div class="info">
<div class="key">License</div>
<div class="value lowercase">
<a
v-if="project.license.url"
class="text-link"
:href="project.license.url"
>
{{ licenseIdDisplay }}
</a>
<a
v-else-if="
project.license.id === 'LicenseRef-All-Rights-Reserved' ||
!project.license.id.includes('LicenseRef')
"
class="text-link"
@click="getLicenseData()"
>
{{ licenseIdDisplay }}
</a>
<span v-else>{{ licenseIdDisplay }}</span>
</div>
</template>
<h3 class="card-header">Project members</h3>
<div
v-for="member in members"
:key="member.user.id"
class="team-member columns button-transparent"
@click="$router.push('/user/' + member.user.username)"
>
<Avatar
:src="member.avatar_url"
:alt="member.username"
size="sm"
circle
/>
<div class="member-info">
<nuxt-link :to="'/user/' + member.user.username" class="name">
<p>{{ member.name }}</p>
</nuxt-link>
<p class="role">{{ member.role }}</p>
</div>
</div>
<hr class="card-divider" />
<h3 class="card-header">Technical information</h3>
<div class="infos">
<div class="info">
<div class="key">License</div>
<div class="value lowercase">
<a
v-if="project.license.url"
class="text-link"
:href="project.license.url"
>
{{ licenseIdDisplay }}
</a>
<a
v-else-if="
project.license.id === 'LicenseRef-All-Rights-Reserved' ||
!project.license.id.includes('LicenseRef')
"
class="text-link"
@click="getLicenseData()"
>
{{ licenseIdDisplay }}
</a>
<span v-else>{{ licenseIdDisplay }}</span>
</div>
<div
v-if="
project.project_type !== 'resourcepack' &&
project.project_type !== 'plugin'
"
class="info"
>
<div class="key">Client side</div>
<div class="value">
{{ project.client_side }}
</div>
</div>
<div
v-if="
project.project_type !== 'resourcepack' &&
project.project_type !== 'plugin'
"
class="info"
>
<div class="key">Client side</div>
<div class="value">
{{ project.client_side }}
</div>
<div
v-if="
project.project_type !== 'resourcepack' &&
project.project_type !== 'plugin'
"
class="info"
>
<div class="key">Server side</div>
<div class="value">
{{ project.server_side }}
</div>
</div>
<div
v-if="
project.project_type !== 'resourcepack' &&
project.project_type !== 'plugin'
"
class="info"
>
<div class="key">Server side</div>
<div class="value">
{{ project.server_side }}
</div>
<div class="info">
<div class="key">Project ID</div>
<div class="value lowercase">
<CopyCode :text="project.id" />
</div>
</div>
<div class="info">
<div class="key">Project ID</div>
<div class="value lowercase">
<CopyCode :text="project.id" />
</div>
</div>
</div>
</article>
</div>
<section class="normal-page__content">
<div
v-if="project.status === 'unlisted'"
@ -637,252 +634,6 @@
:all-members.sync="allMembers"
:dependencies.sync="dependencies"
/>
<div class="extra-info-mobile card">
<template
v-if="
project.issues_url ||
project.source_url ||
project.wiki_url ||
project.discord_url ||
project.donation_urls.length > 0
"
>
<h3 class="card-header">External resources</h3>
<div class="links">
<a
v-if="project.issues_url"
:href="project.issues_url"
class="title"
:target="$external()"
>
<IssuesIcon aria-hidden="true" />
<span>Issues</span>
</a>
<a
v-if="project.source_url"
:href="project.source_url"
class="title"
:target="$external()"
>
<CodeIcon aria-hidden="true" />
<span>Source</span>
</a>
<a
v-if="project.wiki_url"
:href="project.wiki_url"
class="title"
:target="$external()"
>
<WikiIcon aria-hidden="true" />
<span>Wiki</span>
</a>
<a
v-if="project.discord_url"
:href="project.discord_url"
:target="$external()"
>
<DiscordIcon class="shrink" aria-hidden="true" />
<span>Discord</span>
</a>
<a
v-for="(donation, index) in project.donation_urls"
:key="index"
:href="donation.url"
:target="$external()"
>
<BuyMeACoffeeLogo
v-if="donation.id === 'bmac'"
aria-hidden="true"
/>
<PatreonIcon
v-else-if="donation.id === 'patreon'"
aria-hidden="true"
/>
<KoFiIcon
v-else-if="donation.id === 'ko-fi'"
aria-hidden="true"
/>
<PayPalIcon
v-else-if="donation.id === 'paypal'"
aria-hidden="true"
/>
<OpenCollectiveIcon
v-else-if="donation.id === 'open-collective'"
aria-hidden="true"
/>
<HeartIcon v-else-if="donation.id === 'github'" />
<UnknownIcon v-else />
<span v-if="donation.id === 'bmac'">Buy Me a Coffee</span>
<span v-else-if="donation.id === 'patreon'">Patreon</span>
<span v-else-if="donation.id === 'paypal'">PayPal</span>
<span v-else-if="donation.id === 'ko-fi'">Ko-fi</span>
<span v-else-if="donation.id === 'github'"
>GitHub Sponsors</span
>
<span v-else>Donate</span>
</a>
</div>
<hr class="card-divider" />
</template>
<template v-if="featuredVersions.length > 0">
<div class="featured-header">
<h3 class="card-header">Featured versions</h3>
<nuxt-link
v-if="project.versions.length > 0 || currentMember"
:to="`/${project.project_type}/${
project.slug ? project.slug : project.id
}/versions`"
class="goto-link"
>
See all
<ChevronRightIcon
class="featured-header-chevron"
aria-hidden="true"
/>
</nuxt-link>
</div>
<div
v-for="version in featuredVersions"
:key="version.id"
class="featured-version button-transparent"
@click="
$router.push(
`/${project.project_type}/${
project.slug ? project.slug : project.id
}/version/${encodeURI(version.displayUrlEnding)}`
)
"
>
<a
v-tooltip="
findPrimary(version).filename +
' (' +
$formatBytes(findPrimary(version).size) +
')'
"
:href="findPrimary(version).url"
class="download square-button brand-button"
:title="`Download ${version.name}`"
@click.stop="(event) => event.stopPropagation()"
>
<DownloadIcon aria-hidden="true" />
</a>
<div class="info">
<nuxt-link
:to="`/${project.project_type}/${
project.slug ? project.slug : project.id
}/version/${encodeURI(version.displayUrlEnding)}`"
class="top"
>
{{ version.name }}
</nuxt-link>
<div
v-if="version.game_versions.length > 0"
class="game-version item"
>
{{
version.loaders.map((x) => $formatCategory(x)).join(', ')
}}
{{ $formatVersion(version.game_versions) }}
</div>
<Badge
v-if="version.version_type === 'release'"
type="release"
color="green"
/>
<Badge
v-else-if="version.version_type === 'beta'"
type="beta"
color="orange"
/>
<Badge
v-else-if="version.version_type === 'alpha'"
type="alpha"
color="red"
/>
</div>
</div>
<hr class="card-divider" />
</template>
<h3 class="card-header">Project members</h3>
<div
v-for="member in members"
:key="member.user.id"
class="team-member columns button-transparent"
@click="$router.push('/user/' + member.user.username)"
>
<Avatar
:src="member.avatar_url"
:alt="member.username"
size="sm"
circle
/>
<div class="member-info">
<nuxt-link :to="'/user/' + member.user.username" class="name">
<p>{{ member.name }}</p>
</nuxt-link>
<p class="role">{{ member.role }}</p>
</div>
</div>
<hr class="card-divider" />
<h3 class="card-header">Technical information</h3>
<div class="infos">
<div class="info">
<div class="key">License</div>
<div class="value lowercase">
<a
v-if="project.license.url"
class="text-link"
:href="project.license.url"
>
{{ licenseIdDisplay }}
</a>
<a
v-else-if="
project.license.id === 'LicenseRef-All-Rights-Reserved' ||
!project.license.id.includes('LicenseRef')
"
class="text-link"
@click="getLicenseData()"
>
{{ licenseIdDisplay }}
</a>
<span v-else>{{ licenseIdDisplay }}</span>
</div>
</div>
<div
v-if="
project.project_type !== 'resourcepack' &&
project.project_type !== 'plugin'
"
class="info"
>
<div class="key">Client side</div>
<div class="value">
{{ project.client_side }}
</div>
</div>
<div
v-if="
project.project_type !== 'resourcepack' &&
project.project_type !== 'plugin'
"
class="info"
>
<div class="key">Server side</div>
<div class="value">
{{ project.server_side }}
</div>
</div>
<div class="info">
<div class="key">Project ID</div>
<div class="value lowercase">
<CopyCode :text="project.id" />
</div>
</div>
</div>
</div>
</section>
</div>
</div>
@ -1205,7 +956,7 @@ export default {
async submitForReview() {
if (
this.project.body === '' ||
this.project.versions.length < 1 ||
this.versions.length < 1 ||
this.project.client_side === 'unknown' ||
this.project.server_side === 'unknown'
) {
@ -1475,16 +1226,6 @@ export default {
.content {
max-width: calc(1280px - 21rem);
}
.extra-info-mobile {
display: none;
}
}
@media screen and (max-width: 1024px) {
.extra-info-desktop {
display: none;
}
}
.status-buttons {

View File

@ -203,11 +203,13 @@
:max-size="262144"
:show-icon="true"
accept="image/png,image/jpeg,image/gif,image/webp"
class="choose-image"
class="choose-image iconified-button"
prompt="Choose image"
:disabled="(currentMember.permissions & EDIT_DETAILS) !== EDIT_DETAILS"
@change="showPreviewImage"
/>
>
<UploadIcon />
</FileInput>
<button
class="iconified-button"
:disabled="(currentMember.permissions & EDIT_DETAILS) !== EDIT_DETAILS"
@ -551,6 +553,7 @@ import PlusIcon from '~/assets/images/utils/plus.svg?inline'
import SaveIcon from '~/assets/images/utils/save.svg?inline'
import TrashIcon from '~/assets/images/utils/trash.svg?inline'
import RevertIcon from '~/assets/images/utils/undo.svg?inline'
import UploadIcon from '~/assets/images/utils/upload.svg?inline'
import Chips from '~/components/ui/Chips'
import FileInput from '~/components/ui/FileInput'
@ -570,6 +573,7 @@ export default {
SaveIcon,
TrashIcon,
RevertIcon,
UploadIcon,
},
props: {
project: {

View File

@ -237,8 +237,11 @@
:max-size="5242880"
accept="image/png,image/jpeg,image/gif,image/webp,.png,.jpeg,.gif,.webp"
prompt="Choose image or drag it here"
class="iconified-button"
@change="(files) => showPreviewImage(files, index)"
/>
>
<UploadIcon />
</FileInput>
<div class="gallery-buttons">
<div class="delete-button-container">
<button
@ -268,6 +271,7 @@ import CheckIcon from '~/assets/images/utils/check.svg?inline'
import ExternalIcon from '~/assets/images/utils/external.svg?inline'
import ExpandIcon from '~/assets/images/utils/expand.svg?inline'
import ContractIcon from '~/assets/images/utils/contract.svg?inline'
import UploadIcon from '~/assets/images/utils/upload.svg?inline'
import FileInput from '~/components/ui/FileInput'
import Checkbox from '~/components/ui/Checkbox'
@ -287,6 +291,7 @@ export default {
ExternalIcon,
ExpandIcon,
ContractIcon,
UploadIcon,
},
auth: false,
beforeRouteLeave(to, from, next) {

View File

@ -20,10 +20,4 @@ export default {
}
</script>
<style lang="scss" scoped>
.markdown-body {
max-width: calc(
60rem - 2 * var(--spacing-card-lg) - 9px
); // $2.50 to anyone who can figure out why the 9px is needed
}
</style>
<style lang="scss" scoped></style>

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,22 @@
<template>
<div class="content">
<div v-if="currentMember" class="card header-buttons">
<nuxt-link to="version/create" class="brand-button iconified-button">
<PlusIcon />
Create a version
</nuxt-link>
<FileInput
:max-size="524288000"
:accept="acceptFileFromProjectType(project.project_type)"
prompt="Upload a version"
class="brand-button iconified-button"
@change="handleFiles"
>
<UploadIcon />
</FileInput>
<span class="indicator">
<InfoIcon /> Click to choose a file or drag one onto this page
</span>
<DropArea
:accept="acceptFileFromProjectType(project.project_type)"
@change="handleFiles"
/>
</div>
<VersionFilterControl
class="card"
@ -45,7 +57,14 @@
>
<DownloadIcon aria-hidden="true" />
</a>
<span class="version__title">{{ version.name }}</span>
<nuxt-link
:to="`/${project.project_type}/${
project.slug ? project.slug : project.id
}/version/${encodeURI(version.displayUrlEnding)}`"
class="version__title"
>
{{ version.name }}
</nuxt-link>
<div class="version__metadata">
<VersionBadge
v-if="version.version_type === 'release'"
@ -88,17 +107,24 @@
</div>
</template>
<script>
import PlusIcon from '~/assets/images/utils/plus.svg?inline'
import { acceptFileFromProjectType } from '~/plugins/fileUtils'
import DownloadIcon from '~/assets/images/utils/download.svg?inline'
import UploadIcon from '~/assets/images/utils/upload.svg?inline'
import InfoIcon from '~/assets/images/utils/info.svg?inline'
import VersionBadge from '~/components/ui/Badge'
import FileInput from '~/components/ui/FileInput'
import VersionFilterControl from '~/components/ui/VersionFilterControl'
import DropArea from '~/components/ui/DropArea.vue'
export default {
components: {
PlusIcon,
DropArea,
DownloadIcon,
UploadIcon,
InfoIcon,
VersionBadge,
VersionFilterControl,
FileInput,
},
auth: false,
props: {
@ -167,9 +193,20 @@ export default {
}
},
methods: {
acceptFileFromProjectType,
updateVersions(updatedVersions) {
this.filteredVersions = updatedVersions
},
handleFiles(files) {
this.$router.push({
name: 'type-id-version-create',
params: {
type: this.project.project_type,
id: this.project.slug ? this.project.slug : this.project.id,
newPrimaryFile: files[0],
},
})
},
},
}
</script>
@ -177,7 +214,15 @@ export default {
<style lang="scss" scoped>
.header-buttons {
display: flex;
justify-content: right;
align-items: center;
gap: 1rem;
.indicator {
display: flex;
gap: 0.5ch;
align-items: center;
color: var(--color-text-inactive);
}
}
.all-versions {
@ -215,7 +260,10 @@ export default {
.version-button {
display: grid;
grid-template: 'download title supports stats' 'download metadata supports stats';
grid-template:
'download title supports stats'
'download metadata supports stats'
'download dummy supports stats';
grid-template-columns: calc(2.25rem + var(--spacing-card-sm)) 1fr 1fr 1fr;
column-gap: var(--spacing-card-sm);
justify-content: left;
@ -287,4 +335,14 @@ export default {
}
}
}
.modal-create {
padding: var(--spacing-card-bg);
.input-group {
width: fit-content;
margin-left: auto;
margin-top: 1.5rem;
}
}
</style>

View File

@ -23,10 +23,12 @@
:max-size="262144"
:show-icon="true"
accept="image/png,image/jpeg,image/gif,image/webp"
class="choose-image"
class="choose-image iconified-button"
prompt="Upload avatar"
@change="showPreviewImage"
/>
>
<UploadIcon />
</FileInput>
<button
v-else-if="$auth.user && $auth.user.id === user.id"
class="iconified-button"
@ -269,6 +271,7 @@ import SaveIcon from '~/assets/images/utils/save.svg?inline'
import GridIcon from '~/assets/images/utils/grid.svg?inline'
import ListIcon from '~/assets/images/utils/list.svg?inline'
import ImageIcon from '~/assets/images/utils/image.svg?inline'
import UploadIcon from '~/assets/images/utils/upload.svg?inline'
import FileInput from '~/components/ui/FileInput'
import ModalReport from '~/components/ui/ModalReport'
import ModalCreation from '~/components/ui/ModalCreation'
@ -302,6 +305,7 @@ export default {
GridIcon,
ListIcon,
ImageIcon,
UploadIcon,
},
async asyncData(data) {
try {
@ -322,52 +326,13 @@ export default {
}
let gitHubUser = {}
let versions = []
try {
const [gitHubUserData, versionsData] = (
await Promise.all([
data.$axios.get(`https://api.github.com/user/` + user.github_id),
data.$axios.get(
`versions?ids=${JSON.stringify(
[].concat.apply(
[],
projects.map((x) => x.versions)
)
)}`
),
])
).map((it) => it.data)
gitHubUser = gitHubUserData
versions = versionsData
gitHubUser = (
await data.$axios.get(`https://api.github.com/user/` + user.github_id)
).data
} catch {}
for (const version of versions) {
const projectIndex = projects.findIndex(
(x) => x.id === version.project_id
)
if (projects[projectIndex].loaders) {
for (const loader of version.loaders) {
if (!projects[projectIndex].loaders.includes(loader)) {
projects[projectIndex].loaders.push(loader)
}
}
} else {
projects[projectIndex].loaders = version.loaders
}
}
for (const project of projects) {
project.categories = project.categories.concat(project.loaders)
project.project_type = data.$getProjectTypeForUrl(
project.project_type,
project.categories
)
}
return {
user,
projects,

View File

@ -1,3 +1,6 @@
import JSZip from 'jszip'
import TOML from '@iarna/toml'
import yaml from 'js-yaml'
import { formatBytes } from '~/plugins/shorthands'
/**
@ -7,7 +10,7 @@ import { formatBytes } from '~/plugins/shorthands'
* @param validationOptions.maxSize the max file size in bytes
* @param validationOptions.alertOnInvalid if an alert should pop up describing
* each validation error
* @returns `true` if the file is valid; `false` otherise
* @returns `true` if the file is valid; `false` otherwise
*/
export const fileIsValid = (file, validationOptions) => {
const { maxSize, alertOnInvalid } = validationOptions
@ -25,3 +28,212 @@ export const fileIsValid = (file, validationOptions) => {
return true
}
export const acceptFileFromProjectType = (projectType) => {
switch (projectType) {
case 'mod':
return '.jar,.zip,.litemod,application/java-archive,application/zip'
case 'plugin':
return '.jar,.zip,application/java-archive,application/zip'
case 'resourcepack':
return '.zip,application/zip'
case 'shader':
return '.zip,application/zip'
case 'modpack':
return '.mrpack,application/x-modrinth-modpack+zip'
default:
return '*'
}
}
export const inferVersionInfo = async function (
rawFile,
project,
gameVersions
) {
function versionType(number) {
if (number.includes('alpha')) {
return 'alpha'
} else if (
number.includes('beta') ||
number.match(/[^A-z](rc)[^A-z]/) || // includes `rc`
number.match(/[^A-z](pre)[^A-z]/) // includes `pre`
) {
return 'beta'
} else {
return 'release'
}
}
// TODO: This func does not handle accurate semver parsing. We should eventually
function gameVersionRange(gameVersionString, gameVersions) {
if (!gameVersionString) {
return []
}
// Truncate characters after `-` & `+`
const gameString = gameVersionString.replace(/-|\+.*$/g, '')
let prefix = ''
if (gameString.includes('~')) {
// Include minor versions
// ~1.2.3 -> 1.2
prefix = gameString.replace('~', '').split('.').slice(0, 2).join('.')
} else if (gameString.includes('>=')) {
// Include minor versions
// >=1.2.3 -> 1.2
prefix = gameString.replace('>=', '').split('.').slice(0, 2).join('.')
} else if (gameString.includes('^')) {
// Include major versions
// ^1.2.3 -> 1
prefix = gameString.replace('^', '').split('.')[0]
} else if (gameString.includes('x')) {
// Include versions that match `x.x.x`
// 1.2.x -> 1.2
prefix = gameString.replace(/\.x$/, '')
} else {
// Include exact version
// 1.2.3 -> 1.2.3
prefix = gameString
}
const simplified = gameVersions
.filter((it) => it.version_type === 'release')
.map((it) => it.version)
return simplified.filter((version) => version.startsWith(prefix))
}
const inferFunctions = {
// Forge 1.13+
'META-INF/mods.toml': (file, zip) => {
const metadata = TOML.parse(file)
console.log(JSON.stringify(metadata))
// TODO: Parse minecraft version ranges, handle if version is set to value from manifest
if (metadata.mods && metadata.mods.length > 0) {
return {
name: `${project.title} ${metadata.mods[0].version}`,
version_number: metadata.mods[0].version,
version_type: versionType(metadata.mods[0].version),
loaders: ['forge'],
}
} else {
return {}
}
},
// Old Forge
'mcmod.info': (file) => {
const metadata = JSON.parse(file)
return {
name: metadata.version ? `${project.title} ${metadata.version}` : '',
version_number: metadata.version,
version_type: versionType(metadata.version),
loaders: ['forge'],
game_versions: gameVersions
.filter(
(x) =>
x.version.startsWith(metadata.mcversion) &&
x.version_type === 'release'
)
.map((x) => x.version),
}
},
// Fabric
'fabric.mod.json': (file) => {
const metadata = JSON.parse(file)
return {
name: `${project.title} ${metadata.version}`,
version_number: metadata.version,
loaders: ['fabric'],
version_type: versionType(metadata.version),
game_versions: metadata.depends
? gameVersionRange(metadata.depends.minecraft, gameVersions)
: [],
}
},
// Quilt
'quilt.mod.json': (file) => {
const metadata = JSON.parse(file)
return {
name: `${project.title} ${metadata.quilt_loader.version}`,
version_number: metadata.quilt_loader.version,
loaders: ['quilt'],
version_type: versionType(metadata.quilt_loader.version),
game_versions: metadata.quilt_loader.depends
? gameVersionRange(
metadata.depends.find((x) => x.id === 'minecraft')
? metadata.depends.find((x) => x.id === 'minecraft').versions
: [],
gameVersions
)
: [],
}
},
// Bukkit + Other Forks
'plugin.yml': (file) => {
const metadata = yaml.load(file)
return {
name: `${project.title} ${metadata.version}`,
version_number: metadata.version,
version_type: versionType(metadata.version),
// We don't know which fork of Bukkit users are using
loaders: [],
game_versions: gameVersions
.filter(
(x) =>
x.version.startsWith(metadata['api-version']) &&
x.version_type === 'release'
)
.map((x) => x.version),
}
},
// Bungeecord + Waterfall
'bungee.yml': (file) => {
const metadata = yaml.load(file)
return {
name: `${project.title} ${metadata.version}`,
version_number: metadata.version,
version_type: versionType(metadata.version),
loaders: ['bungeecord'],
}
},
// Modpacks
'modrinth.index.json': (file) => {
const metadata = JSON.parse(file)
const loaders = []
if ('forge' in metadata.dependencies) loaders.push('forge')
if ('fabric-loader' in metadata.dependencies) loaders.push('fabric')
if ('quilt-loader' in metadata.dependencies) loaders.push('quilt')
return {
name: `${project.title} ${metadata.versionId}`,
version_number: metadata.versionId,
version_type: versionType(metadata.versionId),
loaders,
game_versions: gameVersions
.filter((x) => x.version === metadata.dependencies.minecraft)
.map((x) => x.version),
}
},
}
const zipReader = new JSZip()
const zip = await zipReader.loadAsync(rawFile)
for (const fileName in inferFunctions) {
const file = zip.file(fileName)
if (file !== null) {
const text = await file.async('text')
return inferFunctions[fileName](text, zip)
}
}
}