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
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; background-color: transparent;
border-radius: var(--size-rounded-sm); border-radius: var(--size-rounded-sm);
&:focus-visible:not(&:disabled) > *, &:focus-visible:not(&:disabled) > *,
&:hover:not(&:disabled) > * { &:hover:not(&:disabled) > * {
cursor: pointer; cursor: pointer;
filter: brightness(0.85); filter: brightness(0.85);
background-color: var(--color-raised-bg); background-color: var(--color-raised-bg);
} }
&:active:not(&:disabled) > * { &:active:not(&:disabled) > * {
filter: brightness(0.8); filter: brightness(0.8);
background-color: var(--color-raised-bg); background-color: var(--color-raised-bg);
} }
&:disabled > *, &:disabled > *,
&[disabled] > * { &[disabled] > * {
cursor: not-allowed; cursor: not-allowed;
filter: grayscale(50%); filter: grayscale(50%);
opacity: 0.5; opacity: 0.5;
box-shadow: none; box-shadow: none;
} }
} }
.button-within { .button-within {
@@ -886,6 +886,7 @@ textarea.known-error {
} }
.known-errors { .known-errors {
min-height: 0;
color: var(--color-special-red); color: var(--color-special-red);
ul { ul {
@@ -999,6 +1000,7 @@ h1 {
color: var(--color-special-red); color: var(--color-special-red);
} }
} }
.label__description { .label__description {
display: block; display: block;
margin-block-end: var(--spacing-card-sm); margin-block-end: var(--spacing-card-sm);
@@ -1067,11 +1069,14 @@ h1 {
width: 15rem; width: 15rem;
} }
>, > :where(input + *, .input-group + *) {
.extend-styling> { margin-block-start: var(--spacing-card-md);
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; margin-right: 0;
} }
.full-width-inputs {
.multiselect, input, .iconified-input {
width: 100%;
flex-basis: 100%;
}
}
input, input,
button { button {
&:disabled { &:disabled {
@@ -1356,3 +1368,10 @@ button {
grid-template-columns: repeat(1, minmax(0, 1fr)); 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-banner-side: hsl(357, 78%, 40%);
--color-block-quote: var(--color-tooltip-bg); --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-hr: var(--color-text);
--color-table-border: #dfe2e5; --color-table-border: #dfe2e5;
@@ -156,7 +156,7 @@ html {
--color-banner-side: hsl(357, 78%, 40%); --color-banner-side: hsl(357, 78%, 40%);
--color-block-quote: var(--color-code-bg); --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-hr: var(--color-text);
--color-table-border: #4f5864; --color-table-border: #4f5864;

View File

@@ -38,9 +38,26 @@
} }
.normal-page { .normal-page {
display: flex; display: grid;
flex-direction: column; padding: 0 0.75rem;
margin: 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 { .site-header {
@@ -49,13 +66,22 @@
@media (min-width: 1024px) { @media (min-width: 1024px) {
.normal-page { .normal-page {
flex-direction: row;
margin: 0 auto; margin: 0 auto;
max-width: 80rem; max-width: 80rem;
column-gap: 0.75rem; column-gap: 0.75rem;
grid-template:
'sidebar content' auto
'info content' auto
'dummy content' 1fr
/ 20rem 1fr;
&.alt-layout { &.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 { .normal-page__content {
width: 60rem; max-width: calc(60rem - .75rem);
max-width: 60rem; }
} }
.site-header { @media (min-width: 80rem) {
margin: 0 auto; .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> <template>
<div class="columns"> <label
<label :class="{ 'long-style': longStyle }"
class="iconified-button" @drop.prevent="handleDrop"
@drop.prevent="handleDrop" @dragover.prevent
@dragover.prevent >
> <slot></slot>
<UploadIcon v-if="showIcon" /> {{ prompt }}
{{ prompt }} <input
<input type="file"
type="file" :multiple="multiple"
:multiple="multiple" :accept="accept"
:accept="accept" @change="handleChange"
@change="handleChange" />
/> </label>
</label>
</div>
</template> </template>
<script> <script>
import { fileIsValid } from '~/plugins/fileUtils' import { fileIsValid } from '~/plugins/fileUtils'
import UploadIcon from '~/assets/images/utils/upload.svg?inline'
export default { export default {
name: 'FileInput', name: 'FileInput',
components: { components: {},
UploadIcon,
},
props: { props: {
prompt: { prompt: {
type: String, type: String,
@@ -54,6 +49,10 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
longStyle: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return { return {
@@ -92,22 +91,22 @@ label {
svg { svg {
height: 1rem; height: 1rem;
} }
}
input { input {
display: none; 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);
} }
&::placeholder { &.long-style {
color: var(--color-warning-text); 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> </style>

View File

View File

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

179
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "knossos", "name": "knossos",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5",
"@nuxtjs/axios": "^5.13.1", "@nuxtjs/axios": "^5.13.1",
"@nuxtjs/dayjs": "^1.2.0", "@nuxtjs/dayjs": "^1.2.0",
"@nuxtjs/markdownit": "^2.0.0", "@nuxtjs/markdownit": "^2.0.0",
@@ -16,6 +17,8 @@
"cookie-universal-nuxt": "^2.1.5", "cookie-universal-nuxt": "^2.1.5",
"core-js": "^3.9.1", "core-js": "^3.9.1",
"highlight.js": "^10.3.2", "highlight.js": "^10.3.2",
"js-yaml": "^4.1.0",
"jszip": "^3.10.1",
"nuxt": "^2.15.3", "nuxt": "^2.15.3",
"sass": "^1.32.12", "sass": "^1.32.12",
"v-tooltip": "^2.0.3", "v-tooltip": "^2.0.3",
@@ -1709,6 +1712,19 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/@gar/promisify": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -1734,6 +1750,11 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true "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": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.1", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz",
@@ -5320,6 +5341,18 @@
"node": ">=4" "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": { "node_modules/cosmiconfig/node_modules/resolve-from": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
@@ -6871,6 +6904,19 @@
"node": ">=8" "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": { "node_modules/eslint/node_modules/supports-color": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -8395,6 +8441,11 @@
"node": ">= 4" "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": { "node_modules/immutable": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
@@ -9083,17 +9134,21 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "3.14.1", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dependencies": { "dependencies": {
"argparse": "^1.0.7", "argparse": "^2.0.1"
"esprima": "^4.0.0"
}, },
"bin": { "bin": {
"js-yaml": "bin/js-yaml.js" "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": { "node_modules/jsesc": {
"version": "2.5.2", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@@ -9146,6 +9201,17 @@
"graceful-fs": "^4.1.6" "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": { "node_modules/kind-of": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
@@ -9205,6 +9271,14 @@
"node": ">= 0.8.0" "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": { "node_modules/lines-and-columns": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "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" "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": { "node_modules/svgo/node_modules/nth-check": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
@@ -17708,6 +17794,16 @@
"requires": { "requires": {
"type-fest": "^0.20.2" "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==", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true "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": { "@jridgewell/gen-mapping": {
"version": "0.3.1", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz",
@@ -20609,6 +20710,15 @@
"resolve-from": "^3.0.0" "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": { "resolve-from": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
@@ -21489,6 +21599,16 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true "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": { "supports-color": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "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==", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true "dev": true
}, },
"immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
"immutable": { "immutable": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
@@ -23412,12 +23537,18 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
}, },
"js-yaml": { "js-yaml": {
"version": "3.14.1", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"requires": { "requires": {
"argparse": "^1.0.7", "argparse": "^2.0.1"
"esprima": "^4.0.0" },
"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": { "jsesc": {
@@ -23460,6 +23591,17 @@
"graceful-fs": "^4.1.6" "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": { "kind-of": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
@@ -23510,6 +23652,14 @@
"type-check": "~0.4.0" "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": { "lines-and-columns": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "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", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" "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": { "nth-check": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", "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 ." "fix": "eslint --fix --ext .js,.vue --ignore-path .eslintignore ."
}, },
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5",
"@nuxtjs/axios": "^5.13.1", "@nuxtjs/axios": "^5.13.1",
"@nuxtjs/dayjs": "^1.2.0", "@nuxtjs/dayjs": "^1.2.0",
"@nuxtjs/markdownit": "^2.0.0", "@nuxtjs/markdownit": "^2.0.0",
@@ -21,6 +22,8 @@
"cookie-universal-nuxt": "^2.1.5", "cookie-universal-nuxt": "^2.1.5",
"core-js": "^3.9.1", "core-js": "^3.9.1",
"highlight.js": "^10.3.2", "highlight.js": "^10.3.2",
"js-yaml": "^4.1.0",
"jszip": "^3.10.1",
"nuxt": "^2.15.3", "nuxt": "^2.15.3",
"sass": "^1.32.12", "sass": "^1.32.12",
"v-tooltip": "^2.0.3", "v-tooltip": "^2.0.3",

View File

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

View File

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

View File

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

View File

@@ -20,10 +20,4 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
.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>

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,22 @@
<template> <template>
<div class="content"> <div class="content">
<div v-if="currentMember" class="card header-buttons"> <div v-if="currentMember" class="card header-buttons">
<nuxt-link to="version/create" class="brand-button iconified-button"> <FileInput
<PlusIcon /> :max-size="524288000"
Create a version :accept="acceptFileFromProjectType(project.project_type)"
</nuxt-link> 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> </div>
<VersionFilterControl <VersionFilterControl
class="card" class="card"
@@ -45,7 +57,14 @@
> >
<DownloadIcon aria-hidden="true" /> <DownloadIcon aria-hidden="true" />
</a> </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"> <div class="version__metadata">
<VersionBadge <VersionBadge
v-if="version.version_type === 'release'" v-if="version.version_type === 'release'"
@@ -88,17 +107,24 @@
</div> </div>
</template> </template>
<script> <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 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 VersionBadge from '~/components/ui/Badge'
import FileInput from '~/components/ui/FileInput'
import VersionFilterControl from '~/components/ui/VersionFilterControl' import VersionFilterControl from '~/components/ui/VersionFilterControl'
import DropArea from '~/components/ui/DropArea.vue'
export default { export default {
components: { components: {
PlusIcon, DropArea,
DownloadIcon, DownloadIcon,
UploadIcon,
InfoIcon,
VersionBadge, VersionBadge,
VersionFilterControl, VersionFilterControl,
FileInput,
}, },
auth: false, auth: false,
props: { props: {
@@ -167,9 +193,20 @@ export default {
} }
}, },
methods: { methods: {
acceptFileFromProjectType,
updateVersions(updatedVersions) { updateVersions(updatedVersions) {
this.filteredVersions = 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> </script>
@@ -177,7 +214,15 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.header-buttons { .header-buttons {
display: flex; 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 { .all-versions {
@@ -215,7 +260,10 @@ export default {
.version-button { .version-button {
display: grid; 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; grid-template-columns: calc(2.25rem + var(--spacing-card-sm)) 1fr 1fr 1fr;
column-gap: var(--spacing-card-sm); column-gap: var(--spacing-card-sm);
justify-content: left; 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> </style>

View File

@@ -23,10 +23,12 @@
:max-size="262144" :max-size="262144"
:show-icon="true" :show-icon="true"
accept="image/png,image/jpeg,image/gif,image/webp" accept="image/png,image/jpeg,image/gif,image/webp"
class="choose-image" class="choose-image iconified-button"
prompt="Upload avatar" prompt="Upload avatar"
@change="showPreviewImage" @change="showPreviewImage"
/> >
<UploadIcon />
</FileInput>
<button <button
v-else-if="$auth.user && $auth.user.id === user.id" v-else-if="$auth.user && $auth.user.id === user.id"
class="iconified-button" 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 GridIcon from '~/assets/images/utils/grid.svg?inline'
import ListIcon from '~/assets/images/utils/list.svg?inline' import ListIcon from '~/assets/images/utils/list.svg?inline'
import ImageIcon from '~/assets/images/utils/image.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 FileInput from '~/components/ui/FileInput'
import ModalReport from '~/components/ui/ModalReport' import ModalReport from '~/components/ui/ModalReport'
import ModalCreation from '~/components/ui/ModalCreation' import ModalCreation from '~/components/ui/ModalCreation'
@@ -302,6 +305,7 @@ export default {
GridIcon, GridIcon,
ListIcon, ListIcon,
ImageIcon, ImageIcon,
UploadIcon,
}, },
async asyncData(data) { async asyncData(data) {
try { try {
@@ -322,52 +326,13 @@ export default {
} }
let gitHubUser = {} let gitHubUser = {}
let versions = []
try { try {
const [gitHubUserData, versionsData] = ( gitHubUser = (
await Promise.all([ await data.$axios.get(`https://api.github.com/user/` + user.github_id)
data.$axios.get(`https://api.github.com/user/` + user.github_id), ).data
data.$axios.get(
`versions?ids=${JSON.stringify(
[].concat.apply(
[],
projects.map((x) => x.versions)
)
)}`
),
])
).map((it) => it.data)
gitHubUser = gitHubUserData
versions = versionsData
} catch {} } 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 { return {
user, user,
projects, 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' 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.maxSize the max file size in bytes
* @param validationOptions.alertOnInvalid if an alert should pop up describing * @param validationOptions.alertOnInvalid if an alert should pop up describing
* each validation error * 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) => { export const fileIsValid = (file, validationOptions) => {
const { maxSize, alertOnInvalid } = validationOptions const { maxSize, alertOnInvalid } = validationOptions
@@ -25,3 +28,212 @@ export const fileIsValid = (file, validationOptions) => {
return true 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)
}
}
}