[WIP] Rework design (#34)

* WIP: Redesign the default layout

* Merge old & new default layouts

* Fix login logic; add proper user controls dropdown

* Fix latest version listing (#31) (#32)

Co-authored-by: Aeledfyr <45501007+Aeledfyr@users.noreply.github.com>

* First pass of design cleanup

* Improve ad integration and fix light theme

* Begin splitting up variables, change some styling to new mockup

* Continue redesign progress

* Work on some more pages

* Add missing dark theme variables for text

* Continue working on modularizing

* Continue progress, redo pagination

* Fix auth buttons in navbar layout

* Continue progress

* Continue progress more

* Redo ModResult

* Scope ModPage :irritater:

* Continue Dashboard

* Continue progress on Dashboard and cleanup

* Add missing variables for dark theme

* Small tweaks, cleanup, and continue mod page progress

* Fix user not being able to see hidden mods that they own

* Start reworking mod creation

* Continue revamp of mod creation page

* Yank v-html out

* Hotfix markdown rendering and some spacing issues

* Move legal; continue with mod creation; create reusable footer

* Create README.md

* Update README.md

* Update README.md

* Add in basic usage instructions

* Fix some stuff

* Continue with mod creation; fix some CSS errors

* Start user page

* Start transition to vue-select; fix a few bugs

* Continue mod creation page

* Finish mod pages

* Add very raw version editing

* Mod editing + creation

* Fixed versions that were in processing causing a 404 (#39)

Co-authored-by: Mikhail Oleynikov <falseresync@gmail.com>
Co-authored-by: Aeledfyr <45501007+Aeledfyr@users.noreply.github.com>
Co-authored-by: Jai A <jai.a@tuta.io>
Co-authored-by: MulverineX <mulverin3@gmail.com>
Co-authored-by: diabolical17 <calumproh28@gmail.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
Prospector
2020-11-30 13:55:01 -08:00
committed by GitHub
parent e025df0824
commit 7b84d8c3d5
70 changed files with 10339 additions and 3284 deletions

View File

@@ -1,28 +1,35 @@
<template>
<div class="main">
<h1>About</h1>
<p>
Founded in 2020, Modrinth was created to provide modders with an open and
intuitive platform to publish their mods on.
</p>
<div class="container">
<h1>About</h1>
<p>
Founded in 2020, Modrinth was created to provide modders with an open
and intuitive platform to publish their mods on.
</p>
<p>
Our primary goal is to be as open as possible, with all our code being
Open Source, while giving back to the modding community as much as
possible.
</p>
<p>
Our primary goal is to be as open as possible, with all our code being
Open Source, while giving back to the modding community as much as
possible.
</p>
<p>
While we still are in early alpha, we hope we can soon be a major modding
platform for all modders :)
</p>
<p>
While we still are in early alpha, we hope we can soon be a major
modding platform for all modders :)
</p>
</div>
<m-footer class="footer" centered />
</div>
</template>
<script>
import MFooter from '@/components/MFooter'
export default {
components: {
MFooter,
},
auth: false,
layout: 'home',
head: {
title: 'About - Modrinth',
meta: [
@@ -54,7 +61,12 @@ export default {
<style lang="scss" scoped>
.main {
margin: 0 auto;
margin: var(--spacing-card-sm) auto;
max-width: 800px;
}
.container {
@extend %card;
padding: var(--spacing-card-sm) var(--spacing-card-lg);
}
</style>

View File

@@ -1,80 +1,80 @@
<template>
<div class="content">
<h2>My projects</h2>
<div class="section-header">
<h3>Mods</h3>
<nuxt-link class="create-button" to="/mod/create"
>Create a new mod</nuxt-link
>
<div class="page-container">
<div class="page-contents">
<div class="sidebar-l">
<div class="card page-nav">
<nuxt-link :to="'/dashboard/projects'" class="tab last">
<ModIcon />
My mods
</nuxt-link>
</div>
<client-only>
<EthicalAd type="image" />
</client-only>
</div>
<div class="content">
<div class="section-header columns">
<h3 class="column-grow-1">My mods</h3>
<nuxt-link class="brand-button column" to="/mod/create">
Create a mod
</nuxt-link>
</div>
<ModCard
v-for="mod in mods"
:id="mod.id"
:key="mod.id"
:author="mod.author"
:name="mod.title"
:description="mod.description"
:latest-version="mod.latest_version"
:created-at="mod.published"
:updated-at="mod.updated"
:downloads="mod.downloads.toString()"
:icon-url="mod.icon_url"
:author-url="mod.author_url"
:page-url="mod.page_url"
:categories="mod.categories"
:edit-mode="true"
:status="mod.status"
:is-modrinth="true"
/>
</div>
</div>
<table>
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Role</th>
<th>Status</th>
<th>Downloads</th>
<th>Last updated</th>
</tr>
</thead>
<tbody>
<tr v-for="mod in mods" :key="mod.id">
<td>
<img class="rounded-md" :src="mod.icon_url" />
</td>
<td>
<nuxt-link :to="'/mod/' + mod.id">
{{ mod.title }}
</nuxt-link>
</td>
<td>Owner</td>
<td>
<span v-if="mod.status === 'approved'" class="badge green">
Approved
</span>
<span v-if="mod.status === 'rejected'" class="badge red">
Rejected
</span>
<span v-if="mod.status === 'draft'" class="badge yellow">
Draft
</span>
<span v-if="mod.status === 'processing'" class="badge yellow">
Processing
</span>
<span v-if="mod.status === 'unlisted'" class="badge gray">
Unlisted
</span>
<span v-if="mod.status === 'unknown'" class="badge gray">
Unknown
</span>
</td>
<td>{{ mod.downloads }}</td>
<td>{{ $dayjs(mod.published).format('YYYY-MM-DD') }}</td>
</tr>
</tbody>
</table>
<client-only>
<EthicalAd />
</client-only>
</div>
</template>
<script>
import axios from 'axios'
import EthicalAd from '@/components/EthicalAd'
import ModCard from '@/components/ProjectCard'
import ModIcon from '~/assets/images/sidebar/mod.svg?inline'
export default {
components: { EthicalAd },
components: {
EthicalAd,
ModCard,
ModIcon,
},
async fetch() {
const config = {
headers: {
Authorization: this.$auth.getToken('local'),
},
}
try {
let res = await axios.get(
`https://api.modrinth.com/api/v1/user/${this.$auth.user.id}/mods`
`https://api.modrinth.com/api/v1/user/${this.$auth.user.id}/mods`,
config
)
if (res.data) {
res = await axios.get(
`https://api.modrinth.com/api/v1/mods?ids=${JSON.stringify(res.data)}`
`https://api.modrinth.com/api/v1/mods?ids=${JSON.stringify(
res.data
)}`,
config
)
this.mods = res.data
}
@@ -90,29 +90,23 @@ export default {
<style lang="scss" scoped>
.section-header {
display: flex;
margin-bottom: 1rem;
& > * {
margin-right: 1rem;
@extend %card;
padding: var(--spacing-card-md) var(--spacing-card-lg);
margin-bottom: var(--spacing-card-md);
h3 {
margin: auto 0;
color: var(--color-text-dark);
font-weight: var(--font-weight-extrabold);
}
}
.create-button {
margin: auto 0;
padding: 4px 20px;
border-radius: 5px;
color: var(--color-grey-5);
background-color: var(--color-grey-1);
}
table {
background: var(--color-bg);
border-collapse: collapse;
border-radius: 0.5rem;
box-shadow: 0 2px 3px 1px var(--color-grey-2);
table-layout: fixed;
width: 100%;
margin-bottom: var(--spacing-card-md);
background: var(--color-raised-bg);
border-radius: var(--size-rounded-card);
* {
text-align: left;
@@ -122,7 +116,7 @@ table {
tr:first-child {
th,
td {
border-bottom: 1px solid var(--color-grey-2);
border-bottom: 1px solid var(--color-divider);
}
}
@@ -130,7 +124,7 @@ table {
td {
&:first-child {
text-align: center;
width: 5%;
width: 7%;
}
&:nth-child(2) {
@@ -140,7 +134,7 @@ table {
}
th {
color: #718096;
color: var(--color-heading);
font-size: 0.8rem;
letter-spacing: 0.02rem;
margin-bottom: 0.5rem;
@@ -150,7 +144,7 @@ table {
}
td {
padding: 0.25rem 1rem;
padding: 0.75rem;
img {
height: 3rem;
@@ -185,4 +179,8 @@ table {
}
}
}
.mod-name {
font-weight: bold;
}
</style>

View File

@@ -1,19 +0,0 @@
<template>
<div class="content">
<h2>Documentation</h2>
<p>Coming Soon!</p>
</div>
</template>
<script>
export default {
auth: false,
}
</script>
<style lang="scss" scoped>
.main {
margin: 0 auto;
max-width: 800px;
}
</style>

View File

@@ -2,7 +2,9 @@
<div>
<div class="main-hero columns">
<div class="left">
<h1 class="typewriter">{{ currentText }}</h1>
<h1 class="typewriter">
{{ currentText }}<span aria-hidden="true"></span>
</h1>
<h1>modding platform</h1>
</div>
<div class="right columns">
@@ -159,13 +161,18 @@ fetch('https://api.modrinth.com/api/v1/mod').then(res => res.json()).then(data =
</pre>
</div>
</div>
<m-footer class="footer" centered />
</div>
</template>
<script>
import MFooter from '@/components/MFooter'
export default {
components: {
MFooter,
},
auth: false,
layout: 'home',
data() {
return {
currentText: 'Open source',
@@ -208,6 +215,7 @@ export default {
}
.main-hero {
margin-top: 100px;
height: 600px;
.left {
@@ -217,12 +225,15 @@ export default {
.typewriter {
display: inline-block;
color: var(--color-brand);
border-right: 0.15em solid var(--color-brand);
animation: caret 1s steps(1) infinite;
@keyframes caret {
50% {
border-color: transparent;
span {
border-right: 0.15em solid var(--color-brand);
animation: caret 1s steps(1) infinite;
@keyframes caret {
50% {
border-color: transparent;
}
}
}
}
@@ -256,7 +267,7 @@ export default {
p {
line-height: 25px;
letter-spacing: 0.2px;
color: var(--color-grey-7);
color: var(--color-text);
span {
color: var(--color-brand);
@@ -276,7 +287,7 @@ export default {
}
.slanted-hero {
background: var(--color-grey-1);
background: var(--color-raised-bg);
height: 500px;
position: relative;
z-index: 1;
@@ -336,6 +347,10 @@ export default {
}
}
.footer {
margin-top: 150px;
}
@media screen and (max-width: 500px) {
.hero {
margin-top: 0 !important;

191
pages/legal/privacy.vue Normal file
View File

@@ -0,0 +1,191 @@
<template>
<div class="main">
<div class="container">
<h1>Privacy Policy</h1>
<p>
At Modrinth, accessible from https://modrinth.com, one of our main
priorities is the privacy of our visitors. This Privacy Policy document
contains types of information that is collected and recorded by Modrinth
and how we use it.
</p>
<p>
If you have additional questions or require more information about our
Privacy Policy, do not hesitate to contact us.
</p>
<h2>Log Files</h2>
<p>
Modrinth follows a standard procedure of using log files. These files
log visitors when they visit websites. All hosting companies do this and
a part of hosting services' analytics. The information collected by log
files include internet protocol (IP) addresses, browser type, Internet
Service Provider (ISP), date and time stamp, referring/exit pages, and
possibly the number of clicks. These are not linked to any information
that is personally identifiable. The purpose of the information is for
analyzing trends, administering the site, tracking users' movement on
the website, and gathering demographic information.
</p>
<h2>Cookies and Web Beacons</h2>
<p>
Like any other website, Modrinth uses 'cookies'. These cookies are used
to store information including visitors' preferences, and the pages on
the website that the visitor accessed or visited. The information is
used to optimize the users' experience by customizing our web page
content based on visitors' browser type and/or other information.
</p>
<p>
For more general information on cookies, please read
<a href="https://www.privacypolicies.com/blog/cookies/"
>"What Are Cookies"</a
>.
</p>
<h2>Our Advertising Partners</h2>
<p>
Some of advertisers on our site may use cookies and web beacons. Our
advertising partners are listed below. Each of our advertising partners
has their own Privacy Policy for their policies on user data. For easier
access, we hyperlinked to their Privacy Policies below.
</p>
<ul>
<li>
<p>EthicalAds</p>
<p>
<a href="https://www.ethicalads.io/privacy-policy/"
>https://www.ethicalads.io/privacy-policy/</a
>
</p>
</li>
</ul>
<h2>Privacy Policies</h2>
<P
>You may consult this list to find the Privacy Policy for each of the
advertising partners of Modrinth.</P
>
<p>
Third-party ad servers or ad networks uses technologies like cookies,
JavaScript, or Web Beacons that are used in their respective
advertisements and links that appear on Modrinth, which are sent
directly to users' browser. They automatically receive your IP address
when this occurs. These technologies are used to measure the
effectiveness of their advertising campaigns and/or to personalize the
advertising content that you see on websites that you visit.
</p>
<p>
Note that Modrinth has no access to or control over these cookies that
are used by third-party advertisers.
</p>
<h2>Third Party Privacy Policies</h2>
<p>
Modrinth's Privacy Policy does not apply to other advertisers or
websites. Thus, we are advising you to consult the respective Privacy
Policies of these third-party ad servers for more detailed information.
It may include their practices and instructions about how to opt-out of
certain options.
</p>
<p>
You can choose to disable cookies through your individual browser
options. To know more detailed information about cookie management with
specific web browsers, it can be found at the browsers' respective
websites. What Are Cookies?
</p>
<h2>Children's Information</h2>
<p>
Another part of our priority is adding protection for children while
using the internet. We encourage parents and guardians to observe,
participate in, and/or monitor and guide their online activity.
</p>
<p>
Modrinth does not knowingly collect any Personal Identifiable
Information from children under the age of 13. If you think that your
child provided this kind of information on our website, we strongly
encourage you to contact us immediately and we will do our best efforts
to promptly remove such information from our records.
</p>
<h2>Online Privacy Policy Only</h2>
<p>
This Privacy Policy applies only to our online activities and is valid
for visitors to our website with regards to the information that they
shared and/or collect in Modrinth. This policy is not applicable to any
information collected offline or via channels other than this website.
</p>
<h2>Consent</h2>
<p>
By using our website, you hereby consent to our Privacy Policy and agree
to its Terms and Conditions.
</p>
</div>
<m-footer class="footer" centered />
</div>
</template>
<script>
import MFooter from '@/components/MFooter'
export default {
components: {
MFooter,
},
auth: false,
head: {
title: 'Privacy - Modrinth',
meta: [
{
hid: 'description',
name: 'description',
content:
'The privacy policy of Modrinth, an open source modding platform. Modrinth currently supports Minecraft, including the forge and fabric mod loaders.',
},
{
hid: 'apple-mobile-web-app-title',
name: 'apple-mobile-web-app-title',
content: 'Privacy Policy',
},
{
hid: 'og:title',
name: 'og:title',
content: 'Privacy Policy',
},
{
hid: 'og:url',
name: 'og:url',
content: `https://modrinth.com/privacy`,
},
],
},
}
</script>
<style lang="scss" scoped>
.main {
margin: var(--spacing-card-sm) auto;
max-width: 800px;
}
.container {
@extend %card;
padding: var(--spacing-card-sm) var(--spacing-card-lg);
}
</style>

203
pages/legal/terms.vue Normal file
View File

@@ -0,0 +1,203 @@
<template>
<div class="main">
<div class="container">
<h1>Terms and Conditions</h1>
<h2>1. Terms</h2>
<p>
By accessing this Website, accessible from https://modrinth.com, you are
agreeing to be bound by these Website Terms and Conditions of Use and
agree that you are responsible for the agreement with any applicable
local laws. If you disagree with any of these terms, you are prohibited
from accessing this site. The materials contained in this Website are
protected by copyright and trade mark law.
</p>
<h2>2. Use License</h2>
<p>
Permission is granted to temporarily download one copy of the materials
on Guavy LLC's Website for personal, non-commercial transitory viewing
only. This is the grant of a license, not a transfer of title, and under
this license you may not:
</p>
<ul>
<li>modify or copy the materials;</li>
<li>
use the materials for any commercial purpose or for any public
display;
</li>
<li>
attempt to reverse engineer any software contained on Guavy LLC's
Website;
</li>
<li>
remove any copyright or other proprietary notations from the
materials; or
</li>
<li>
transferring the materials to another person or "mirror" the materials
on any other server.
</li>
</ul>
<p>
This will let Guavy LLC to terminate upon violations of any of these
restrictions. Upon termination, your viewing right will also be
terminated and you should destroy any downloaded materials in your
possession whether it is printed or electronic format.
</p>
<h2>3. Disclaimer</h2>
<p>
All the materials on Guavy LLCs Website are provided "as is". Guavy LLC
makes no warranties, may it be expressed or implied, therefore negates
all other warranties. Furthermore, Guavy LLC does not make any
representations concerning the accuracy or reliability of the use of the
materials on its Website or otherwise relating to such materials or any
sites linked to this Website.
</p>
<h2>4. Limitations</h2>
<p>
Guavy LLC or its suppliers will not be hold accountable for any damages
that will arise with the use or inability to use the materials on Guavy
LLCs Website, even if Guavy LLC or an authorize representative of this
Website has been notified, orally or written, of the possibility of such
damage. Some jurisdiction does not allow limitations on implied
warranties or limitations of liability for incidental damages, these
limitations may not apply to you.
</p>
<h2>5. Revisions and Errata</h2>
<p>
The materials appearing on Guavy LLCs Website may include technical,
typographical, or photographic errors. Guavy LLC will not promise that
any of the materials in this Website are accurate, complete, or current.
Guavy LLC may change the materials contained on its Website at any time
without notice. Guavy LLC does not make any commitment to update the
materials.
</p>
<h2>6. Links</h2>
<p>
Guavy LLC has not reviewed all of the sites linked to its Website and is
not responsible for the contents of any such linked site. The presence
of any link does not imply endorsement by Guavy LLC of the site. The use
of any linked website is at the users own risk.
</p>
<h2>7. Site Terms of Use Modifications</h2>
<p>
Guavy LLC may revise these Terms of Use for its Website at any time
without prior notice. By using this Website, you are agreeing to be
bound by the current version of these Terms and Conditions of Use.
</p>
<h2>8. Your Privacy</h2>
<p>
Please read our <nuxt-link to="/privacy"> Privacy Policy</nuxt-link>.
</p>
<h2>9. Governing Law</h2>
<p>
Any claim related to Guavy LLC's Website shall be governed by the laws
of us without regards to its conflict of law provisions.
</p>
<h2>10. Content</h2>
<p>
When you upload text, software, mods, scripts, graphics, photos, audio,
videos, links, interactive features and other materials that may be
viewed on, or accessed through Modrinth, we refer to it as “Content”.
</p>
<ul>
<li>
You must own or have the necessary licenses, rights, consents, and
permissions to store, share or distribute the Content that is uploaded
under your Modrinth account.
</li>
<li>
You are responsible for all activity and Content that is uploaded
under your Modrinth account.
</li>
<li>
You must not transmit any viruses, worms, malware, or any other code
of a destructive nature through Modrinth.
</li>
<li>
You retain all of your ownership rights to your Content. We do not
claim any ownership in or to any of your Content.
</li>
<li>
To enable us to provide the services of Modrinth, you hereby grant us
a worldwide, non-exclusive, royalty-free, and unrestricted license to
use, reproduce, distribute copies, prepare derivative works of, or
display Content in connection with Modrinth in any medium and for any
purpose (including commercial purposes), which is irrevocable.
</li>
</ul>
</div>
<m-footer class="footer" centered />
</div>
</template>
<script>
import MFooter from '@/components/MFooter'
export default {
components: {
MFooter,
},
auth: false,
head: {
title: 'Terms - Modrinth',
meta: [
{
hid: 'description',
name: 'description',
content:
'The Terms of Service of Modrinth, an open source modding platform. Modrinth currently supports Minecraft, including the forge and fabric mod loaders.',
},
{
hid: 'apple-mobile-web-app-title',
name: 'apple-mobile-web-app-title',
content: 'Terms of Service',
},
{
hid: 'og:title',
name: 'og:title',
content: 'Terms of Service',
},
{
hid: 'og:url',
name: 'og:url',
content: `https://modrinth.com/tos`,
},
],
},
}
</script>
<style lang="scss" scoped>
.main {
margin: var(--spacing-card-sm) auto;
max-width: 800px;
}
.container {
@extend %card;
padding: var(--spacing-card-sm) var(--spacing-card-lg);
}
</style>

571
pages/mod/_id/edit.vue Normal file
View File

@@ -0,0 +1,571 @@
<template>
<div class="page-container">
<div class="page-contents">
<header class="columns">
<h2 class="column-grow-1">Edit Mod</h2>
<button
title="Save"
class="brand-button column"
:disabled="!this.$nuxt.$loading"
@click="saveMod"
>
Save
</button>
</header>
<EthicalAd class="advert" />
<section class="essentials">
<h3>Name</h3>
<label>
<span>
Be creative. TechCraft v7 won't be searchable and won't be clicked
on
</span>
<input v-model="mod.title" type="text" placeholder="Enter the name" />
</label>
<h3>Summary</h3>
<label>
<span>
Give a quick description to your mod. It will appear in the search
</span>
<input
v-model="mod.description"
type="text"
placeholder="Enter the summary"
/>
</label>
<h3>Categories</h3>
<label>
<span>
Select up to 3 categories. They will help to find your mod
</span>
<multiselect
id="categories"
v-model="mod.categories"
:options="availableCategories"
:loading="availableCategories.length === 0"
:multiple="true"
:searchable="false"
:show-no-results="false"
:close-on-select="false"
:clear-on-select="false"
:show-labels="false"
:max="3"
:limit="6"
:hide-selected="true"
placeholder="Choose categories"
/>
</label>
<h3>Vanity URL (slug)</h3>
<label>
<span>
Set this to something pretty, so URLs to your mod are more readable
</span>
<input
id="name"
v-model="mod.slug"
type="text"
placeholder="Enter the vanity URL's last bit"
/>
</label>
</section>
<section class="mod-icon rows">
<h3>Icon</h3>
<div class="columns row-grow-1">
<div class="column-grow-1 rows">
<file-input
accept="image/png,image/jpeg,image/gif"
class="choose-image"
prompt="Choose image or drag it here"
@change="showPreviewImage"
/>
<ul class="row-grow-1">
<li>Must be a square</li>
<li>Minimum size is 100x100</li>
<li>Acceptable formats are PNG, JPEG and GIF</li>
</ul>
<button
class="transparent-button"
@click="
icon = null
previewImage = null
"
>
Reset icon
</button>
</div>
<img
:src="
mod.icon_url
? mod.icon_url
: 'https://cdn.modrinth.com/placeholder.svg'
"
alt="preview-image"
/>
</div>
</section>
<section class="game-sides">
<h3>Supported environments</h3>
<div class="columns">
<span>
Let others know if your mod is for clients, servers or universal.
For example, IC2 will be required + required, while OptiFine will be
required + no functionality
</span>
<div class="labeled-control">
<h3>Client</h3>
<Multiselect
v-model="clientSideType"
placeholder="Select one"
track-by="id"
label="label"
:options="sideTypes"
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
/>
</div>
<div class="labeled-control">
<h3>Server</h3>
<Multiselect
v-model="serverSideType"
placeholder="Select one"
track-by="id"
label="label"
:options="sideTypes"
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
/>
</div>
</div>
</section>
<section class="description">
<h3>
<label
for="body"
title="You can type the of the long form of your description here."
>
Description
</label>
</h3>
<span>
You can type the of the long form of your description here. This
editor supports markdown. You can find the syntax
<a
href="https://guides.github.com/features/mastering-markdown/"
target="_blank"
rel="noopener noreferrer"
>here</a
>.
</span>
<div class="columns">
<div class="textarea-wrapper">
<textarea id="body" v-model="body"></textarea>
</div>
<div v-compiled-markdown="body" class="markdown-body"></div>
</div>
</section>
<section class="extra-links">
<div class="title">
<h3>External links</h3>
</div>
<label
title="A place for users to report bugs, issues, and concerns about your mod."
>
<span>Issue tracker</span>
<input
v-model="mod.issues_url"
type="url"
placeholder="Enter a valid URL"
/>
</label>
<label title="A page/repository containing the source code">
<span>Source code</span>
<input
v-model="mod.source_url"
type="url"
placeholder="Enter a valid URL"
/>
</label>
<label
title="A page containing information, documentation, and help for the mod."
>
<span>Wiki page</span>
<input
v-model="mod.wiki_url"
type="url"
placeholder="Enter a valid URL"
/>
</label>
<label title="An inivitation link to your Discord server.">
<span>Discord invite</span>
<input
v-model="mod.wiki_url"
type="url"
placeholder="Enter a valid URL"
/>
</label>
</section>
<section class="license">
<div class="title">
<h3>License</h3>
</div>
<label>
<span>
It is really important to choose a proper license for your mod. You
may choose one from our list or provide a URL to your own license.
URL field will be filled automatically for provided licenses
</span>
<div class="input-group">
<Multiselect
v-model="mod.license"
placeholder="Select one"
track-by="short"
label="name"
:options="availableLicenses"
:searchable="true"
:close-on-select="true"
:show-labels="false"
/>
<input
v-model="mod.license.url"
type="url"
placeholder="License URL"
/>
</div>
</label>
</section>
<!--
<section class="donations">
<div class="title">
<h3>Donation links</h3>
<i> this section is optional</i>
</div>
</section>
-->
<m-footer class="footer" centered />
</div>
</div>
</template>
<script>
import axios from 'axios'
import Multiselect from 'vue-multiselect'
import MFooter from '@/components/MFooter'
import FileInput from '@/components/FileInput'
import EthicalAd from '@/components/EthicalAd'
export default {
components: {
MFooter,
FileInput,
EthicalAd,
Multiselect,
},
async asyncData(data) {
const [
mod,
availableCategories,
availableLoaders,
availableGameVersions,
availableLicenses,
// availableDonationPlatforms,
] = (
await Promise.all([
axios.get(`https://api.modrinth.com/api/v1/mod/${data.params.id}`),
axios.get(`https://api.modrinth.com/api/v1/tag/category`),
axios.get(`https://api.modrinth.com/api/v1/tag/loader`),
axios.get(`https://api.modrinth.com/api/v1/tag/game_version`),
axios.get(`https://api.modrinth.com/api/v1/tag/license`),
// axios.get(`https://api.modrinth.com/api/v1/tag/donation_platform`),
])
).map((it) => it.data)
mod.license = {
short: mod.license.id,
name: mod.license.name,
}
const res = await axios.get(mod.body_url)
return {
mod,
body: res.data,
clientSideType: {
label: mod.client_side,
id: mod.client_side,
},
serverSideType: {
label: mod.server_side,
id: mod.server_side,
},
availableCategories,
availableLoaders,
availableGameVersions,
availableLicenses,
// availableDonationPlatforms,
}
},
data() {
return {
previewImage: null,
compiledBody: '',
icon: null,
sideTypes: [
{ label: 'Required', id: 'required' },
{ label: 'No functionality', id: 'no-functionality' },
{ label: 'Unsupported', id: 'unsupported' },
],
}
},
watch: {
license(newValue, oldValue) {
if (newValue == null) {
this.license_url = ''
return
}
switch (newValue.short) {
case 'custom':
this.license_url = ''
break
default:
this.license_url = `https://cdn.modrinth.com/licenses/${newValue.short}.txt`
}
},
},
methods: {
async saveMod() {
this.$nuxt.$loading.start()
try {
await axios.patch(
`https://api.modrinth.com/api/v1/mod/${this.mod.id}`,
{
title: this.mod.title,
description: this.mod.description,
body: this.body,
categories: this.mod.categories,
issues_url: this.mod.issues_url,
source_url: this.mod.source_url,
wiki_url: this.mod.wiki_url,
license_url: this.mod.license_url,
discord_url: this.mod.discord_url,
license_id: this.mod.license.short,
client_side: this.clientSideType.id,
server_side: this.serverSideType.id,
slug: this.mod.mod_slug,
},
{
headers: {
Authorization: this.$auth.getToken('local'),
},
}
)
await this.$router.replace(`/mod/${this.mod.id}`)
} catch (err) {
this.$notify({
group: 'main',
title: 'An Error Occurred',
text: err.response.data.description,
type: 'error',
})
window.scrollTo({ top: 0, behavior: 'smooth' })
}
this.$nuxt.$loading.finish()
},
showPreviewImage(e) {
const reader = new FileReader()
this.icon = e.target.files[0]
reader.readAsDataURL(this.icon)
reader.onload = (event) => {
this.previewImage = event.target.result
}
},
},
}
</script>
<style lang="scss" scoped>
.title {
* {
display: inline;
}
}
label {
display: flex;
span {
flex: 2;
padding-right: var(--spacing-card-lg);
}
input,
.multiselect,
.input-group {
flex: 3;
height: fit-content;
}
}
.input-group {
display: flex;
flex-direction: column;
* {
margin-bottom: var(--spacing-card-sm);
}
}
.textarea-wrapper {
display: flex;
flex-direction: column;
align-items: stretch;
textarea {
flex: 1;
overflow-y: auto;
resize: none;
max-width: 100%;
}
}
.page-contents {
display: grid;
grid-template:
'header header header' auto
'advert advert advert' auto
'essentials essentials mod-icon' auto
'game-sides game-sides game-sides' auto
'description description description' auto
'versions versions versions' auto
'extra-links license license' auto
'donations donations .' auto
'footer footer footer' auto
/ 4fr 1fr 4fr;
column-gap: var(--spacing-card-md);
row-gap: var(--spacing-card-md);
}
header {
@extend %card;
grid-area: header;
padding: var(--spacing-card-md) var(--spacing-card-lg);
h2 {
margin: auto 0;
color: var(--color-text-dark);
font-weight: var(--font-weight-extrabold);
}
button {
margin-left: 0.5rem;
}
}
.advert {
grid-area: advert;
}
section {
@extend %card;
padding: var(--spacing-card-md) var(--spacing-card-lg);
}
section.essentials {
grid-area: essentials;
}
section.mod-icon {
grid-area: mod-icon;
img {
align-self: flex-start;
max-width: 50%;
margin-left: var(--spacing-card-lg);
}
}
section.game-sides {
grid-area: game-sides;
.columns {
flex-wrap: wrap;
span {
flex: 2;
}
.labeled-control {
flex: 2;
margin-left: var(--spacing-card-lg);
}
}
}
section.description {
grid-area: description;
& > .columns {
align-items: stretch;
min-height: 10rem;
max-height: 40rem;
& > * {
flex: 1;
max-width: 50%;
}
}
.markdown-body {
overflow-y: auto;
padding: 0 var(--spacing-card-sm);
}
}
section.extra-links {
grid-area: extra-links;
label {
align-items: center;
margin-top: var(--spacing-card-sm);
span {
flex: 1;
}
}
}
section.license {
grid-area: license;
label {
margin-top: var(--spacing-card-sm);
}
}
section.donations {
grid-area: donations;
}
.footer {
grid-area: footer;
}
.choose-image {
cursor: pointer;
}
</style>

View File

@@ -1,14 +1,11 @@
<template>
<ModPage :mod="mod" :versions="versions" :members="members">
<div class="markdown-body" v-html="body"></div>
<div v-compiled-markdown="body" class="markdown-body"></div>
</ModPage>
</template>
<script>
import axios from 'axios'
import xss from 'xss'
import marked from 'marked'
import ModPage from '@/components/ModPage'
export default {
@@ -31,17 +28,20 @@ export default {
members[i].avatar_url = res.data.avatar_url
}
res = await axios.get(mod.body_url)
const body = xss(marked(res.data))
const body = (await axios.get(mod.body_url)).data
const versions = []
for (const version of mod.versions) {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
try {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
} catch {
// eslint-disable-next-line no-console
console.log('Some versions may be missing...')
}
}
return {
@@ -102,9 +102,9 @@ export default {
<style lang="scss" scoped>
.markdown-body {
padding: 20px;
box-shadow: 0 2px 3px 1px var(--color-grey-2);
background: var(--color-bg);
border-radius: 0 0 var(--size-rounded-sm) var(--size-rounded-sm);
padding: 1rem;
margin-bottom: var(--spacing-card-md);
background: var(--color-raised-bg);
border-radius: var(--size-rounded-card);
}
</style>

View File

@@ -31,11 +31,15 @@ export default {
const versions = []
for (const version of mod.versions) {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
try {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
} catch {
// eslint-disable-next-line no-console
console.log('Some versions may be missing...')
}
}
return {

View File

@@ -1,109 +1,115 @@
<template>
<ModPage :mod="mod" :versions="versions" :members="members">
<div class="version">
<div class="header">
<h3>{{ version.name }}</h3>
<div
v-if="
this.$auth.loggedIn &&
members.find((x) => x.user_id === this.$auth.user.id)
<div class="version-header">
<h4>{{ version.name }}</h4>
<span v-if="version.version_type === 'release'" class="badge green">
Release
</span>
<span v-if="version.version_type === 'beta'" class="badge yellow">
Beta
</span>
<span v-if="version.version_type === 'alpha'" class="badge red">
Alpha
</span>
<span>
{{ version.version_number }}
</span>
<Categories :categories="version.loaders" />
<a
:href="primaryFile.url"
class="download-button"
@click.prevent="
downloadFile(primaryFile.hashes.sha1, primaryFile.url)
"
class="user-actions"
>
<button class="trash red">
<TrashIcon />
</button>
<button class="upload" @click="showPopup = !showPopup">
<UploadIcon />
</button>
<DownloadIcon />
Download
</a>
</div>
<div class="stats">
<div class="stat">
<DownloadIcon />
<div class="info">
<h4>Downloads</h4>
<p class="value">{{ version.downloads }}</p>
</div>
</div>
<div class="stat">
<CalendarIcon />
<div class="info">
<h4>Created</h4>
<p
v-tooltip="
$dayjs(version.published).format(
'[Created on] YYYY-MM-DD [at] HH:mm A'
)
"
class="value"
>
{{ $dayjs(version.published).fromNow() }}
</p>
</div>
</div>
<div class="stat">
<TagIcon />
<div class="info">
<h4>Available For</h4>
<p class="value">
{{ version.game_versions.join(', ') }}
</p>
</div>
</div>
</div>
<div class="markdown-body" v-html="changelog"></div>
<hr />
<div class="columns metadata">
<div class="author">
<img :src="version.author.avatar_url" />
<p>{{ version.author.name }}</p>
</div>
<p>{{ version.downloads }} Downloads</p>
<div>
<FabricIcon
v-if="version.loaders.includes('fabric')"
stroke="#AC6C3A"
/>
<ForgeIcon
v-if="version.loaders.includes('forge')"
stroke="#8B81E6"
/>
</div>
<div class="game-versions">
<p v-for="gameVersion in version.game_versions" :key="gameVersion">
{{ gameVersion }}
</p>
</div>
</div>
<hr />
<div v-compiled-markdown="changelog" class="markdown-body"></div>
<div class="files">
<div v-for="file in version.files" :key="file.hashes.sha1">
<p>{{ file.filename }}</p>
<a :href="file.url" download>
<div class="text-wrapper">
<p>{{ file.filename }}</p>
<div
v-if="
$auth.loggedIn &&
members.find((x) => x.user_id === $auth.user.id)
"
class="actions"
>
<button @click="deleteFile(file.hashes.sha1)">Delete File</button>
<button @click="makePrimary(file.hashes.sha1)">
Make Primary
</button>
</div>
</div>
<a
:href="file.url"
@click.prevent="downloadFile(file.hash, file.url)"
>
<DownloadIcon />
</a>
</div>
</div>
<FileInput class="file-input" @change="addFiles" />
</div>
<Popup :show-popup="showPopup">
<h3 class="popup-title">Upload Files</h3>
<FileInput
input-id="version-files"
input-accept="application/*"
default-text="Upload Files"
:input-multiple="true"
@change="addFiles"
>
<label class="required" title="The files associated with the version">
Version Files
</label>
</FileInput>
<div class="popup-buttons">
<button
class="trash-button"
@click="
showPopup = false
filesToUpload = []
"
>
<TrashIcon />
</button>
<button class="default-button" @click="uploadFiles">Upload</button>
</div>
</Popup>
</ModPage>
</template>
<script>
import axios from 'axios'
import ModPage from '@/components/ModPage'
import xss from 'xss'
import marked from 'marked'
import Popup from '@/components/Popup'
import Categories from '@/components/Categories'
import FileInput from '@/components/FileInput'
import DownloadIcon from '~/assets/images/utils/download.svg?inline'
import UploadIcon from '~/assets/images/utils/upload.svg?inline'
import TrashIcon from '~/assets/images/utils/trash.svg?inline'
import ForgeIcon from '~/assets/images/categories/forge.svg?inline'
import FabricIcon from '~/assets/images/categories/fabric.svg?inline'
import CalendarIcon from '~/assets/images/utils/calendar.svg?inline'
import TagIcon from '~/assets/images/utils/tag.svg?inline'
export default {
components: {
Popup,
FileInput,
Categories,
ModPage,
ForgeIcon,
FabricIcon,
DownloadIcon,
UploadIcon,
TrashIcon,
CalendarIcon,
TagIcon,
},
auth: false,
async asyncData(data) {
@@ -125,11 +131,15 @@ export default {
const versions = []
for (const version of mod.versions) {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
try {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
} catch {
// eslint-disable-next-line no-console
console.log('Some versions may be missing...')
}
}
const version = versions.find((x) => x.id === data.params.version)
@@ -138,8 +148,13 @@ export default {
let changelog = ''
if (version.changelog_url) {
res = await axios.get(version.changelog_url)
changelog = xss(marked(res.data))
changelog = (await axios.get(version.changelog_url)).data
}
let primaryFile = version.files.find((file) => file.primary)
if (!primaryFile) {
primaryFile = version.files[0]
}
return {
@@ -148,16 +163,55 @@ export default {
members,
version,
changelog,
primaryFile,
}
},
data() {
return {
showPopup: false,
filesToUpload: [],
}
},
methods: {
addFiles(e) {
async downloadFile(hash, url) {
await axios.get(
`https://api.modrinth.com/api/v1/version_file/${hash}/download`
)
const elem = document.createElement('a')
elem.download = hash
elem.href = url
elem.click()
},
async deleteFile(hash) {
const config = {
headers: {
Authorization: this.$auth.getToken('local'),
},
}
await axios.delete(
`https://api.modrinth.com/api/v1/version_file/${hash}`,
config
)
},
async makePrimary(hash) {
const config = {
headers: {
Authorization: this.$auth.getToken('local'),
},
}
await axios.patch(
`https://api.modrinth.com/api/v1/version/${this.version.id}`,
{
primary_file: {
sha1: hash,
},
},
config
)
},
async addFiles(e) {
this.filesToUpload = e.target.files
for (let i = 0; i < e.target.files.length; i++) {
@@ -165,8 +219,7 @@ export default {
'-' + i
)
}
},
async uploadFiles() {
this.$nuxt.$loading.start()
const formData = new FormData()
@@ -254,70 +307,38 @@ export default {
<style lang="scss" scoped>
.version {
background: var(--color-bg);
border-radius: 0 0 0.5rem 0.5rem;
box-shadow: 0 2px 3px 1px var(--color-grey-2);
padding: 1em;
margin-bottom: var(--spacing-card-md);
background: var(--color-raised-bg);
border-radius: var(--size-rounded-card);
padding: 1rem;
.header {
h3 {
display: inline-block;
}
.user-actions {
float: right;
button {
cursor: pointer;
margin-right: 10px;
padding: 5px;
border: none;
border-radius: var(--size-rounded-sm);
}
.trash {
color: #9b2c2c;
background-color: var(--color-bg);
}
.upload {
color: var(--color-text);
background-color: var(--color-grey-1);
* {
margin: auto 0;
}
}
}
}
hr {
margin: 20px 0;
color: var(--color-grey-1);
}
.metadata {
.version-header {
display: flex;
align-items: center;
justify-content: space-between;
.author {
h4,
span {
margin: auto 0.5rem auto 0;
}
.download-button {
margin-left: auto;
padding: 0.5rem;
color: var(--color-button-text);
background-color: var(--color-button-bg);
justify-self: flex-end;
display: flex;
align-items: center;
img {
height: 50px;
width: 50px;
margin-right: 10px;
}
}
}
border-radius: var(--size-rounded-sm);
.game-versions {
max-width: 200px;
p {
margin: 0 0 0 10px;
padding: 4px;
font-size: 15px;
color: var(--color-text);
background-color: var(--color-grey-1);
display: inline-block;
&:hover,
&:focus {
background-color: var(--color-button-bg-hover);
}
svg {
margin-right: 0.25rem;
}
}
}
@@ -326,71 +347,77 @@ export default {
div {
display: flex;
margin-right: 10px;
border: 1px solid var(--color-grey-1);
border-radius: var(--size-rounded-sm);
margin-right: 0.5rem;
background: var(--color-bg);
border-radius: var(--size-rounded-control);
p {
margin-left: 10px;
margin-right: 10px;
.text-wrapper {
display: flex;
flex-direction: column;
padding: 0.5rem;
.actions {
width: 100%;
display: flex;
justify-content: space-between;
max-height: 3rem;
font-size: var(--font-size-sm);
button {
display: flex;
align-items: center;
svg {
margin-left: 0.25rem;
}
}
}
}
a {
display: table-cell;
display: flex;
align-items: center;
margin-left: auto;
width: 40px;
height: 60px;
background-color: var(--color-grey-1);
color: var(--color-grey-3);
width: 2.5rem;
height: auto;
background-color: var(--color-button-bg);
color: var(--color-button-text);
border-radius: 0 var(--size-rounded-control) var(--size-rounded-control)
0;
svg {
margin-top: 15px;
vertical-align: center;
height: 30px;
width: 40px;
}
&:hover,
&:focus {
background-color: var(--color-grey-3);
color: var(--color-grey-4);
background-color: var(--color-button-bg-hover);
color: var(--color-button-text-hover);
}
}
}
}
}
.popup-title {
margin-bottom: 40px;
}
.popup-buttons {
margin-top: 40px;
.stats {
display: flex;
justify-content: left;
align-items: center;
flex-wrap: wrap;
margin: 0.5rem 0;
.stat {
margin-right: 0.75rem;
@extend %stat;
.default-button {
border-radius: var(--size-rounded-sm);
cursor: pointer;
border: none;
padding: 10px;
background-color: var(--color-grey-1);
color: var(--color-grey-5);
&:hover,
&:focus {
color: var(--color-grey-4);
svg {
padding: 0.25rem;
border-radius: 50%;
background-color: var(--color-button-bg);
}
}
}
.trash-button {
cursor: pointer;
margin-right: 10px;
padding: 5px;
border: none;
border-radius: var(--size-rounded-sm);
color: #9b2c2c;
background-color: var(--color-bg);
}
.file-input {
margin-top: 2rem;
}
</style>

View File

@@ -5,20 +5,29 @@
<tr>
<th></th>
<th>Name</th>
<th>Number</th>
<th>Loaders</th>
<th>Game Versions</th>
<th>Version</th>
<th>Mod Loader</th>
<th>Minecraft Version</th>
<th>Status</th>
<th>Downloads</th>
<th>Published</th>
<th>Date Published</th>
</tr>
</thead>
<tbody>
<tr v-for="version in versions" :key="version.id">
<td>
<nuxt-link :to="'/mod/' + mod.id + '/version/' + version.id">
<a
:href="findPrimary(version).url"
class="download"
@click.prevent="
downloadFile(
findPrimary(version).hashes.sha1,
findPrimary(version).url
)
"
>
<DownloadIcon />
</nuxt-link>
</a>
</td>
<td>
<nuxt-link :to="'/mod/' + mod.id + '/version/' + version.id">
@@ -27,14 +36,8 @@
</td>
<td>{{ version.version_number }}</td>
<td>
<FabricIcon
v-if="version.loaders.includes('fabric')"
stroke="#AC6C3A"
/>
<ForgeIcon
v-if="version.loaders.includes('forge')"
stroke="#8B81E6"
/>
<FabricIcon v-if="version.loaders.includes('fabric')" />
<ForgeIcon v-if="version.loaders.includes('forge')" />
</td>
<td>{{ version.game_versions.join(', ') }}</td>
<td>
@@ -62,10 +65,6 @@
class="create-version-popup-body"
>
<h3>New Version</h3>
<div v-if="currentError" class="error">
<h4>Error</h4>
<p>{{ currentError }}</p>
</div>
<label
for="version-title"
class="required"
@@ -110,7 +109,7 @@
<label
title="The version number of this version. Preferably following semantic versioning"
>
Loaders
Mod Loaders
</label>
<multiselect
v-model="createdVersion.loaders"
@@ -128,7 +127,7 @@
placeholder="Choose loaders..."
/>
<label title="The versions of minecraft that this mod version supports">
Game Versions
Minecraft Versions
</label>
<multiselect
v-model="createdVersion.game_versions"
@@ -155,7 +154,7 @@
/>
<FileInput
input-id="version-files"
input-accept="application/java-archive,application/zip"
accept="application/java-archive,application/zip"
default-text="Upload Files"
:input-multiple="true"
@change="updateVersionFiles"
@@ -237,11 +236,15 @@ export default {
const versions = []
for (const version of mod.versions) {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
try {
res = await axios.get(
`https://api.modrinth.com/api/v1/version/${version}`
)
versions.push(res.data)
} catch {
// eslint-disable-next-line no-console
console.log('Some versions may be missing...')
}
}
res = await axios.get(`https://api.modrinth.com/api/v1/tag/loader`)
@@ -320,6 +323,29 @@ export default {
this.$nuxt.$loading.finish()
},
findPrimary(version) {
let file = version.files.find((x) => x.primary)
if (!file) {
file = version.files[0]
}
if (!file) {
file = { url: `/mod/${this.mod.id}/version/${version.id}` }
}
return file
},
async downloadFile(hash, url) {
await axios.get(
`https://api.modrinth.com/api/v1/version_file/${hash}/download`
)
const elem = document.createElement('a')
elem.download = hash
elem.href = url
elem.click()
},
},
head() {
return {
@@ -368,10 +394,10 @@ export default {
<style lang="scss" scoped>
table {
background: var(--color-bg);
border-collapse: collapse;
border-radius: 0 0 0.5rem 0.5rem;
box-shadow: 0 2px 3px 1px var(--color-grey-2);
margin-bottom: var(--spacing-card-md);
background: var(--color-raised-bg);
border-radius: var(--size-rounded-card);
table-layout: fixed;
width: 100%;
@@ -383,7 +409,7 @@ table {
tr:first-child {
th,
td {
border-bottom: 1px solid var(--color-grey-2);
border-bottom: 1px solid var(--color-divider);
}
}
@@ -391,14 +417,14 @@ table {
td {
&:first-child {
text-align: center;
width: 5%;
width: 7%;
svg {
color: var(--color-grey-3);
color: var(--color-text);
&:hover,
&:focus {
color: var(--color-grey-5);
color: var(--color-text-hover);
}
}
}
@@ -406,23 +432,23 @@ table {
&:nth-child(2),
&:nth-child(5) {
padding-left: 0;
width: 15%;
width: 12%;
}
}
th {
color: #718096;
color: var(--color-heading);
font-size: 0.8rem;
letter-spacing: 0.02rem;
margin-bottom: 0.5rem;
margin-top: 1.5rem;
padding: 1rem 1rem;
padding: 0.75rem 1rem;
text-transform: uppercase;
}
td {
overflow: hidden;
padding: 0.25rem 1rem;
padding: 0.75rem 1rem;
img {
height: 3rem;
@@ -448,7 +474,7 @@ input {
outline: none;
border: none;
margin: 10px 0 30px;
background-color: var(--color-grey-1);
background-color: var(--color-button-bg);
color: var(--color-text);
font-family: monospace;
}
@@ -482,21 +508,15 @@ input {
cursor: pointer;
border: none;
padding: 10px;
background-color: var(--color-grey-1);
color: var(--color-grey-5);
background-color: var(--color-button-bg);
color: var(--color-button-text);
&:hover,
&:focus {
color: var(--color-grey-4);
background-color: var(--color-button-bg-hover);
}
}
.error {
margin: 20px 0;
border-left: #e04e3e 7px solid;
padding: 5px 20px 20px 20px;
}
@media screen and (max-width: 400px) {
th,
td {

File diff suppressed because it is too large Load Diff

View File

@@ -1,276 +1,294 @@
<template>
<div class="columns">
<div class="content column-grow-5">
<h2>Mods</h2>
<section class="search-bar">
<div class="iconified-input column-grow-2">
<label class="hidden" for="search">Search Mods</label>
<input
id="search"
v-model="query"
type="search"
name="search"
placeholder="Search mods"
@input="onSearchChange(1)"
<div class="page-container">
<div class="page-contents">
<div class="content">
<section class="search-nav">
<div class="iconified-input column-grow-2">
<label class="hidden" for="search">Search Mods</label>
<input
id="search"
v-model="query"
type="search"
name="search"
placeholder="Search..."
autocomplete="off"
@input="onSearchChange(1)"
/>
<SearchIcon />
</div>
<div class="sort-paginate">
<div class="labeled-control">
<h3>Sort By</h3>
<Multiselect
v-model="sortType"
class="sort-types"
placeholder="Select one"
track-by="display"
label="display"
:options="sortTypes"
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
@input="onSearchChange(1)"
>
<template slot="singleLabel" slot-scope="{ option }">{{
option.display
}}</template>
</Multiselect>
</div>
<div class="labeled-control per-page">
<h3>Per Page</h3>
<Multiselect
v-model="maxResults"
class="max-results"
placeholder="Select one"
:options="[5, 10, 15, 20, 50, 100]"
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
@input="onSearchChange(currentPage)"
>
</Multiselect>
</div>
<div class="mobile-filters-button">
<button @click="toggleFiltersMenu">Filter</button>
</div>
</div>
<pagination
:current-page="currentPage"
:pages="pages"
@switch-page="onSearchChange"
></pagination>
</section>
<div class="results column-grow-4">
<client-only>
<EthicalAd type="text" />
</client-only>
<SearchResult
v-for="(result, index) in results"
:id="result.mod_id.split('-')[1]"
:key="result.mod_id"
:author="result.author"
:name="result.title"
:description="result.description"
:latest-version="result.latest_version"
:created-at="result.date_created"
:updated-at="result.date_modified"
:downloads="result.downloads.toString()"
:icon-url="result.icon_url"
:author-url="result.author_url"
:page-url="result.page_url"
:categories="result.categories"
:is-ad="index === -1"
:is-modrinth="result.host === 'modrinth'"
/>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
</div>
<div class="sort-paginate">
<Multiselect
v-model="sortType"
class="sort-types"
placeholder="Select one"
track-by="display"
label="display"
:options="sortTypes"
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
@input="onSearchChange(1)"
>
<template slot="singleLabel" slot-scope="{ option }">{{
option.display
}}</template>
</Multiselect>
<div class="mobile-filters-button">
<button @click="toggleFiltersMenu">Filter...</button>
<div v-if="results.length === 0" class="no-results">
<p>No results found for your query!</p>
</div>
</div>
<pagination
:current-page="currentPage"
:pages="pages"
@switch-page="onSearchChange"
></pagination>
</section>
<div class="results column-grow-4">
<client-only>
<EthicalAd type="text" />
</client-only>
<SearchResult
v-for="(result, index) in results"
:id="result.mod_id"
:key="result.mod_id"
:author="result.author"
:name="result.title"
:description="result.description"
:latest-version="result.latest_version"
:created-at="result.date_created"
:updated-at="result.date_modified"
:downloads="result.downloads.toString()"
:icon-url="result.icon_url"
:author-url="result.author_url"
:page-url="result.page_url"
:categories="result.categories"
:is-ad="index === -1"
/>
<div v-if="results.length === 0" class="no-results">
<p>No results found for your query!</p>
</div>
<section v-if="pages.length > 1" class="search-bottom">
<div class="labeled-control">
<h3>Per Page</h3>
<Multiselect
v-model="maxResults"
class="max-results"
placeholder="Select one"
:options="[5, 10, 15, 20, 50, 100]"
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
@input="onSearchChange(currentPage)"
>
</Multiselect>
</div>
<pagination
:current-page="currentPage"
:pages="pages"
@switch-page="onSearchChangeToTop"
></pagination>
</section>
</div>
<section v-if="pages.length > 1" class="search-bottom">
<Multiselect
v-model="maxResults"
class="max-results"
placeholder="Select one"
:options="[5, 10, 15, 20, 50, 100]"
:searchable="false"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
@input="onSearchChange(currentPage)"
>
</Multiselect>
<pagination
:current-page="currentPage"
:pages="pages"
@switch-page="onSearchChangeToTop"
></pagination>
<section id="filters" class="filters">
<div class="filters-wrapper">
<section class="filter-group">
<button class="filter-button-done" @click="toggleFiltersMenu">
Done
</button>
<button @click="clearFilters">Reset filters</button>
<h3>Categories</h3>
<SearchFilter
:active-filters="facets"
display-name="Technology"
facet-name="categories:technology"
@toggle="toggleFacet"
>
<TechCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Adventure"
facet-name="categories:adventure"
@toggle="toggleFacet"
>
<AdventureCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Magic"
facet-name="categories:magic"
@toggle="toggleFacet"
>
<MagicCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Utility"
facet-name="categories:utility"
@toggle="toggleFacet"
>
<UtilityCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Decoration"
facet-name="categories:decoration"
@toggle="toggleFacet"
>
<DecorationCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Library"
facet-name="categories:library"
@toggle="toggleFacet"
>
<LibraryCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Cursed"
facet-name="categories:cursed"
@toggle="toggleFacet"
>
<CursedCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="World Generation"
facet-name="categories:worldgen"
@toggle="toggleFacet"
>
<WorldGenCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Storage"
facet-name="categories:storage"
@toggle="toggleFacet"
>
<StorageCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Food"
facet-name="categories:food"
@toggle="toggleFacet"
>
<FoodCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Equipment"
facet-name="categories:equipment"
@toggle="toggleFacet"
>
<EquipmentCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Miscellaneous"
facet-name="categories:misc"
@toggle="toggleFacet"
>
<MiscCategory />
</SearchFilter>
<h3>Mod Loaders</h3>
<SearchFilter
:active-filters="facets"
display-name="Fabric"
facet-name="categories:fabric"
@toggle="toggleFacet"
>
<FabricLoader />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Forge"
facet-name="categories:forge"
@toggle="toggleFacet"
>
<ForgeLoader />
</SearchFilter>
<h3>Host</h3>
<SearchFilter
:active-filters="facets"
display-name="Modrinth"
facet-name="host:modrinth"
@toggle="toggleFacet"
>
<Modrinth />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="CurseForge"
facet-name="host:curseforge"
@toggle="toggleFacet"
>
<FlameAnvil />
</SearchFilter>
<h3>Versions</h3>
<SearchFilter
:active-filters="showVersions"
display-name="Include snapshots"
facet-name="snapshots"
style="margin-bottom: 10px"
@toggle="fillInitialVersions"
/>
</section>
<multiselect
v-model="selectedVersions"
:options="versions"
:loading="versions.length === 0"
:multiple="true"
:searchable="true"
:show-no-results="false"
:close-on-select="false"
:clear-on-select="false"
:show-labels="false"
:limit="6"
:hide-selected="true"
placeholder="Choose versions..."
@input="onSearchChange(1)"
></multiselect>
</div>
<m-footer class="footer" />
</section>
</div>
<section id="filters" class="filters">
<div class="filters-wrapper">
<section class="filter-group">
<button class="filter-button-done" @click="toggleFiltersMenu">
Done
</button>
<button @click="clearFilters">Clear Filters</button>
<h3>Categories</h3>
<SearchFilter
:active-filters="facets"
display-name="Technology"
facet-name="categories:technology"
@toggle="toggleFacet"
>
<TechCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Adventure"
facet-name="categories:adventure"
@toggle="toggleFacet"
>
<AdventureCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Magic"
facet-name="categories:magic"
@toggle="toggleFacet"
>
<MagicCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Utility"
facet-name="categories:utility"
@toggle="toggleFacet"
>
<UtilityCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Decoration"
facet-name="categories:decoration"
@toggle="toggleFacet"
>
<DecorationCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Library"
facet-name="categories:library"
@toggle="toggleFacet"
>
<LibraryCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Cursed"
facet-name="categories:cursed"
@toggle="toggleFacet"
>
<CursedCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Worldgen"
facet-name="categories:worldgen"
@toggle="toggleFacet"
>
<WorldGenCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Storage"
facet-name="categories:storage"
@toggle="toggleFacet"
>
<StorageCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Food"
facet-name="categories:food"
@toggle="toggleFacet"
>
<FoodCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Equipment"
facet-name="categories:equipment"
@toggle="toggleFacet"
>
<EquipmentCategory />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Misc"
facet-name="categories:misc"
@toggle="toggleFacet"
>
<MiscCategory />
</SearchFilter>
<h3>Loaders</h3>
<SearchFilter
:active-filters="facets"
display-name="Forge"
facet-name="categories:forge"
@toggle="toggleFacet"
>
<ForgeLoader />
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Fabric"
facet-name="categories:fabric"
@toggle="toggleFacet"
>
<FabricLoader />
</SearchFilter>
<h3>Platforms</h3>
<SearchFilter
:active-filters="facets"
display-name="Modrinth"
facet-name="host:modrinth"
@toggle="toggleFacet"
>
</SearchFilter>
<SearchFilter
:active-filters="facets"
display-name="Curseforge"
facet-name="host:curseforge"
@toggle="toggleFacet"
>
</SearchFilter>
<h3>Versions</h3>
<SearchFilter
:active-filters="showVersions"
display-name="Snapshots"
facet-name="snapshots"
style="margin-bottom: 10px"
@toggle="fillInitialVersions"
/>
</section>
<multiselect
v-model="selectedVersions"
:options="versions"
:loading="versions.length === 0"
:multiple="true"
:searchable="true"
:show-no-results="false"
:close-on-select="false"
:clear-on-select="false"
:show-labels="false"
:limit="6"
:hide-selected="true"
placeholder="Choose versions..."
@input="onSearchChange(1)"
></multiselect>
</div>
</section>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
import axios from 'axios'
import SearchResult from '@/components/ModResult'
import SearchResult from '@/components/ProjectCard'
import Pagination from '@/components/Pagination'
import SearchFilter from '@/components/SearchFilter'
import EthicalAd from '@/components/EthicalAd'
import MFooter from '@/components/MFooter'
import TechCategory from '~/assets/images/categories/tech.svg?inline'
import AdventureCategory from '~/assets/images/categories/adventure.svg?inline'
import CursedCategory from '~/assets/images/categories/cursed.svg?inline'
@@ -287,9 +305,15 @@ import WorldGenCategory from '~/assets/images/categories/worldgen.svg?inline'
import ForgeLoader from '~/assets/images/categories/forge.svg?inline'
import FabricLoader from '~/assets/images/categories/fabric.svg?inline'
import Modrinth from '~/assets/images/categories/modrinth.svg?inline'
import FlameAnvil from '~/assets/images/categories/flameanvil.svg?inline'
import SearchIcon from '~/assets/images/utils/search.svg?inline'
export default {
auth: false,
components: {
MFooter,
EthicalAd,
SearchResult,
Pagination,
@@ -309,6 +333,9 @@ export default {
WorldGenCategory,
ForgeLoader,
FabricLoader,
Modrinth,
FlameAnvil,
SearchIcon,
},
async fetch() {
if (this.$route.query.q) this.query = this.$route.query.q
@@ -345,12 +372,12 @@ export default {
currentPage: 1,
sortTypes: [
{ display: 'Relevance', name: 'relevance' },
{ display: 'Total Downloads', name: 'downloads' },
{ display: 'Newest', name: 'newest' },
{ display: 'Updated', name: 'updated' },
{ display: 'Download count', name: 'downloads' },
{ display: 'Recently created', name: 'newest' },
{ display: 'Recently updated', name: 'updated' },
],
sortType: { display: 'Relevance', name: 'relevance' },
maxResults: 5,
maxResults: 20,
}
},
methods: {
@@ -482,7 +509,7 @@ export default {
url += `&v=${encodeURIComponent(this.selectedVersions)}`
if (this.sortType.name !== 'relevance')
url += `&s=${encodeURIComponent(this.sortType.name)}`
if (this.maxResults > 5)
if (this.maxResults > 20)
url += `&m=${encodeURIComponent(this.maxResults)}`
window.history.pushState(new Date(), 'Mods', url)
@@ -525,17 +552,32 @@ export default {
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
<style lang="scss">
.search-bar {
.search-nav {
align-items: center;
display: flex;
justify-content: space-between;
flex-flow: column;
background: var(--color-raised-bg);
border-radius: var(--size-rounded-card);
padding: 0.25rem 1rem 0.25rem 1rem;
margin-bottom: var(--spacing-card-md);
input {
border: none;
background: transparent;
min-width: 200px;
}
.iconified-input {
width: 100%;
}
.sort-paginate {
margin-left: 0.5rem;
margin-right: 0.5rem;
display: flex;
width: 100%;
.per-page {
margin-left: 0.5rem;
display: none;
}
}
@media screen and (min-width: 900px) {
flex-flow: row;
@@ -543,34 +585,45 @@ export default {
width: auto;
}
.sort-paginate {
display: block;
width: auto;
}
}
@media screen and (min-width: 1024px) {
.sort-paginate {
.per-page {
display: unset;
}
}
}
}
.search-bottom {
align-items: center;
display: flex;
justify-content: flex-end;
background: var(--color-raised-bg);
border-radius: var(--size-rounded-card);
padding: 0.25rem 1rem 0.25rem 1rem;
select {
width: 100px;
margin-right: 20px;
}
}
.content {
min-height: 96vh;
.labeled-control {
h3 {
@extend %small-label;
margin-left: 0.5rem;
}
}
.mobile-filters-button {
display: inline-block;
button {
background: var(--color-bg);
color: var(--color-text);
border: 2px solid var(--color-grey-3);
border-radius: var(--size-rounded-sm);
padding: 0.5rem;
margin-top: 0;
height: 2.5rem;
padding-left: 1rem;
padding-right: 1rem;
}
// Hide button on larger screens where it's not needed
@@ -581,48 +634,47 @@ export default {
.filters {
overflow-y: auto;
background-color: var(--color-bg);
border-left: 1px solid var(--color-grey-2);
position: fixed;
width: 100vw;
right: -100vw;
max-height: 100vh;
min-width: 15%;
top: 3.5rem;
height: calc(100vh - 3.5rem);
top: var(--size-navbar-height);
height: calc(100vh - var(--size-navbar-height));
transition: right 150ms;
background-color: var(--color-raised-bg);
flex-shrink: 0; // Stop shrinking when page contents change
.filters-wrapper {
padding: 0 0.75rem;
padding: 0.25rem 0.75rem 0.75rem 0.75rem;
}
h3 {
color: #718096;
font-size: 0.8rem;
letter-spacing: 0.02rem;
margin-bottom: 0.5rem;
margin-top: 1.5rem;
text-transform: uppercase;
@extend %large-label;
margin-top: 1.25em;
}
// Larger screens that don't need to collapse
@media screen and (min-width: 900px) {
position: sticky;
width: 215px;
padding-right: 1rem;
transition: none;
}
// Desktop
@media screen and (min-width: 1145px) {
top: 0;
height: 100vh;
}
&.active {
right: 0;
}
// Larger screens that don't need to collapse
@media screen and (min-width: 900px) {
top: 0;
right: auto;
position: unset;
height: unset;
max-height: unset;
transition: none;
margin-left: var(--spacing-card-lg);
overflow-y: unset;
padding-right: 1rem;
width: 18vw;
background-color: transparent;
.filters-wrapper {
background-color: var(--color-raised-bg);
border-radius: var(--size-rounded-card);
}
}
@media screen and (min-width: 1024px) {
width: 300px;
}
}
.filter-group {
@@ -631,17 +683,6 @@ export default {
button {
cursor: pointer;
width: 100%;
padding: 5px 0;
outline: none;
color: var(--color-grey-5);
background-color: var(--color-grey-1);
border: none;
border-radius: 5px;
&:hover {
background-color: var(--color-grey-2);
color: var(--color-text);
}
}
.filter-button-done {
@@ -654,65 +695,12 @@ export default {
display: none;
}
}
// Desktop
@media screen and (min-width: 1145px) {
margin-top: 2em;
}
}
.iconified-select {
margin-left: 1em;
align-items: center;
display: inline-flex;
flex-direction: row-reverse;
select {
padding-left: 2.5rem;
&:hover {
& + svg {
color: var(--color-grey-6);
}
}
&:focus {
& + svg {
color: var(--color-text);
}
}
}
svg {
color: var(--color-grey-5);
margin-right: -2rem;
width: 24px;
height: 24px;
}
}
select {
width: 220px;
padding: 0.5rem 1rem;
background: var(--color-bg);
border: 2px solid var(--color-grey-3);
border-radius: var(--size-rounded-sm);
color: var(--color-grey-9);
font-size: 1rem;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
&:hover {
border-color: var(--color-grey-4);
color: var(--color-text);
}
}
.sort-types {
min-width: 200px;
border: 2px solid var(--color-grey-3);
border-radius: var(--size-rounded-sm);
border: none;
border-radius: var(--size-rounded-control);
.multiselect__tags {
padding: 10px 50px 0 8px;
@@ -723,45 +711,14 @@ select {
.no-results {
text-align: center;
padding: 20px 0;
font-size: 30px;
font-size: 1.25rem;
color: var(--color-text);
background-color: var(--color-grey-1);
margin-bottom: var(--spacing-card-md);
background: var(--color-raised-bg);
border-radius: var(--size-rounded-card);
}
.max-results {
max-width: 80px;
}
.multiselect__content-wrapper {
overflow-x: hidden;
}
.multiselect__tags {
border: 2px solid var(--color-grey-3);
border-radius: var(--size-rounded-sm);
}
.multiselect__tags,
.multiselect__spinner {
background: var(--color-bg);
cursor: pointer;
}
.multiselect__spinner::before,
.multiselect__spinner::after {
border-top-color: var(--color-brand);
}
.multiselect__option--selected.multiselect__option--highlight,
.multiselect__option,
.multiselect__single,
.multiselect__input {
color: var(--color-text);
background: var(--color-bg);
}
.multiselect__option--highlight,
.multiselect__tag {
background: var(--color-brand);
}
</style>

View File

@@ -1,178 +0,0 @@
<template>
<div class="main">
<h1>Privacy Policy</h1>
<p>
At Modrinth, accessible from https://modrinth.com, one of our main
priorities is the privacy of our visitors. This Privacy Policy document
contains types of information that is collected and recorded by Modrinth
and how we use it.
</p>
<p>
If you have additional questions or require more information about our
Privacy Policy, do not hesitate to contact us.
</p>
<h2>Log Files</h2>
<p>
Modrinth follows a standard procedure of using log files. These files log
visitors when they visit websites. All hosting companies do this and a
part of hosting services' analytics. The information collected by log
files include internet protocol (IP) addresses, browser type, Internet
Service Provider (ISP), date and time stamp, referring/exit pages, and
possibly the number of clicks. These are not linked to any information
that is personally identifiable. The purpose of the information is for
analyzing trends, administering the site, tracking users' movement on the
website, and gathering demographic information.
</p>
<h2>Cookies and Web Beacons</h2>
<p>
Like any other website, Modrinth uses 'cookies'. These cookies are used to
store information including visitors' preferences, and the pages on the
website that the visitor accessed or visited. The information is used to
optimize the users' experience by customizing our web page content based
on visitors' browser type and/or other information.
</p>
<p>
For more general information on cookies, please read
<a href="https://www.privacypolicies.com/blog/cookies/"
>"What Are Cookies"</a
>.
</p>
<h2>Our Advertising Partners</h2>
<p>
Some of advertisers on our site may use cookies and web beacons. Our
advertising partners are listed below. Each of our advertising partners
has their own Privacy Policy for their policies on user data. For easier
access, we hyperlinked to their Privacy Policies below.
</p>
<ul>
<li>
<p>EthicalAds</p>
<p>
<a href="https://www.ethicalads.io/privacy-policy/"
>https://www.ethicalads.io/privacy-policy/</a
>
</p>
</li>
</ul>
<h2>Privacy Policies</h2>
<P
>You may consult this list to find the Privacy Policy for each of the
advertising partners of Modrinth.</P
>
<p>
Third-party ad servers or ad networks uses technologies like cookies,
JavaScript, or Web Beacons that are used in their respective
advertisements and links that appear on Modrinth, which are sent directly
to users' browser. They automatically receive your IP address when this
occurs. These technologies are used to measure the effectiveness of their
advertising campaigns and/or to personalize the advertising content that
you see on websites that you visit.
</p>
<p>
Note that Modrinth has no access to or control over these cookies that are
used by third-party advertisers.
</p>
<h2>Third Party Privacy Policies</h2>
<p>
Modrinth's Privacy Policy does not apply to other advertisers or websites.
Thus, we are advising you to consult the respective Privacy Policies of
these third-party ad servers for more detailed information. It may include
their practices and instructions about how to opt-out of certain options.
</p>
<p>
You can choose to disable cookies through your individual browser options.
To know more detailed information about cookie management with specific
web browsers, it can be found at the browsers' respective websites. What
Are Cookies?
</p>
<h2>Children's Information</h2>
<p>
Another part of our priority is adding protection for children while using
the internet. We encourage parents and guardians to observe, participate
in, and/or monitor and guide their online activity.
</p>
<p>
Modrinth does not knowingly collect any Personal Identifiable Information
from children under the age of 13. If you think that your child provided
this kind of information on our website, we strongly encourage you to
contact us immediately and we will do our best efforts to promptly remove
such information from our records.
</p>
<h2>Online Privacy Policy Only</h2>
<p>
This Privacy Policy applies only to our online activities and is valid for
visitors to our website with regards to the information that they shared
and/or collect in Modrinth. This policy is not applicable to any
information collected offline or via channels other than this website.
</p>
<h2>Consent</h2>
<p>
By using our website, you hereby consent to our Privacy Policy and agree
to its Terms and Conditions.
</p>
</div>
</template>
<script>
export default {
auth: false,
layout: 'home',
head: {
title: 'Privacy - Modrinth',
meta: [
{
hid: 'description',
name: 'description',
content:
'The privacy policy of Modrinth, an open source modding platform. Modrinth currently supports Minecraft, including the forge and fabric mod loaders.',
},
{
hid: 'apple-mobile-web-app-title',
name: 'apple-mobile-web-app-title',
content: 'Privacy Policy',
},
{
hid: 'og:title',
name: 'og:title',
content: 'Privacy Policy',
},
{
hid: 'og:url',
name: 'og:url',
content: `https://modrinth.com/privacy`,
},
],
},
}
</script>
<style lang="scss" scoped>
.main {
margin: 0 auto;
max-width: 800px;
}
</style>

View File

@@ -1,187 +0,0 @@
<template>
<div class="main">
<h1>Terms and Conditions</h1>
<h2>1. Terms</h2>
<p>
By accessing this Website, accessible from https://modrinth.com, you are
agreeing to be bound by these Website Terms and Conditions of Use and
agree that you are responsible for the agreement with any applicable local
laws. If you disagree with any of these terms, you are prohibited from
accessing this site. The materials contained in this Website are protected
by copyright and trade mark law.
</p>
<h2>2. Use License</h2>
<p>
Permission is granted to temporarily download one copy of the materials on
Guavy LLC's Website for personal, non-commercial transitory viewing only.
This is the grant of a license, not a transfer of title, and under this
license you may not:
</p>
<ul>
<li>modify or copy the materials;</li>
<li>
use the materials for any commercial purpose or for any public display;
</li>
<li>
attempt to reverse engineer any software contained on Guavy LLC's
Website;
</li>
<li>
remove any copyright or other proprietary notations from the materials;
or
</li>
<li>
transferring the materials to another person or "mirror" the materials
on any other server.
</li>
</ul>
<p>
This will let Guavy LLC to terminate upon violations of any of these
restrictions. Upon termination, your viewing right will also be terminated
and you should destroy any downloaded materials in your possession whether
it is printed or electronic format.
</p>
<h2>3. Disclaimer</h2>
<p>
All the materials on Guavy LLCs Website are provided "as is". Guavy LLC
makes no warranties, may it be expressed or implied, therefore negates all
other warranties. Furthermore, Guavy LLC does not make any representations
concerning the accuracy or reliability of the use of the materials on its
Website or otherwise relating to such materials or any sites linked to
this Website.
</p>
<h2>4. Limitations</h2>
<p>
Guavy LLC or its suppliers will not be hold accountable for any damages
that will arise with the use or inability to use the materials on Guavy
LLCs Website, even if Guavy LLC or an authorize representative of this
Website has been notified, orally or written, of the possibility of such
damage. Some jurisdiction does not allow limitations on implied warranties
or limitations of liability for incidental damages, these limitations may
not apply to you.
</p>
<h2>5. Revisions and Errata</h2>
<p>
The materials appearing on Guavy LLCs Website may include technical,
typographical, or photographic errors. Guavy LLC will not promise that any
of the materials in this Website are accurate, complete, or current. Guavy
LLC may change the materials contained on its Website at any time without
notice. Guavy LLC does not make any commitment to update the materials.
</p>
<h2>6. Links</h2>
<p>
Guavy LLC has not reviewed all of the sites linked to its Website and is
not responsible for the contents of any such linked site. The presence of
any link does not imply endorsement by Guavy LLC of the site. The use of
any linked website is at the users own risk.
</p>
<h2>7. Site Terms of Use Modifications</h2>
<p>
Guavy LLC may revise these Terms of Use for its Website at any time
without prior notice. By using this Website, you are agreeing to be bound
by the current version of these Terms and Conditions of Use.
</p>
<h2>8. Your Privacy</h2>
<p>Please read our <nuxt-link to="/privacy"> Privacy Policy</nuxt-link>.</p>
<h2>9. Governing Law</h2>
<p>
Any claim related to Guavy LLC's Website shall be governed by the laws of
us without regards to its conflict of law provisions.
</p>
<h2>10. Content</h2>
<p>
When you upload text, software, mods, scripts, graphics, photos, audio,
videos, links, interactive features and other materials that may be viewed
on, or accessed through Modrinth, we refer to it as “Content”.
</p>
<ul>
<li>
You must own or have the necessary licenses, rights, consents, and
permissions to store, share or distribute the Content that is uploaded
under your Modrinth account.
</li>
<li>
You are responsible for all activity and Content that is uploaded under
your Modrinth account.
</li>
<li>
You must not transmit any viruses, worms, malware, or any other code of
a destructive nature through Modrinth.
</li>
<li>
You retain all of your ownership rights to your Content. We do not claim
any ownership in or to any of your Content.
</li>
<li>
To enable us to provide the services of Modrinth, you hereby grant us a
worldwide, non-exclusive, royalty-free, and unrestricted license to use,
reproduce, distribute copies, prepare derivative works of, or display
Content in connection with Modrinth in any medium and for any purpose
(including commercial purposes), which is irrevocable.
</li>
</ul>
</div>
</template>
<script>
export default {
auth: false,
layout: 'home',
head: {
title: 'TOS - Modrinth',
meta: [
{
hid: 'description',
name: 'description',
content:
'The Terms of Service of Modrinth, an open source modding platform. Modrinth currently supports Minecraft, including the forge and fabric mod loaders.',
},
{
hid: 'apple-mobile-web-app-title',
name: 'apple-mobile-web-app-title',
content: 'Terms of Service',
},
{
hid: 'og:title',
name: 'og:title',
content: 'Terms of Service',
},
{
hid: 'og:url',
name: 'og:url',
content: `https://modrinth.com/tos`,
},
],
},
}
</script>
<style lang="scss" scoped>
.main {
margin: 0 auto;
max-width: 800px;
}
</style>

View File

@@ -1,49 +1,94 @@
<template>
<div class="content">
<div class="user-profile">
<img :src="user.avatar_url" :alt="user.username" />
<div class="info">
<h1>{{ user.username }}</h1>
<p class="joined-text">Joined {{ $dayjs(user.created).fromNow() }}</p>
<p v-if="user.bio" class="bio">{{ user.bio }}</p>
<p v-if="user.role === 'admin'" class="badge red">Admin</p>
<p v-if="user.role === 'moderator'" class="badge yellow">Moderator</p>
<p v-if="user.role === 'developer'" class="badge green">Developer</p>
<div class="page-container">
<div class="page-contents">
<div class="sidebar-l">
<div class="card">
<div class="user-info">
<img :src="user.avatar_url" :alt="user.username" />
<div class="text">
<h2>{{ user.username }}</h2>
<p v-if="user.role === 'admin'" class="badge red">Admin</p>
<p v-if="user.role === 'moderator'" class="badge yellow">
Moderator
</p>
<p v-if="user.role === 'developer'" class="badge green">
Developer
</p>
</div>
</div>
<p v-if="user.bio" class="bio">{{ user.bio }}</p>
</div>
<div class="card stats">
<div class="stat">
<CalendarIcon />
<div class="info">
<h4>Created</h4>
<p
v-tooltip="
$dayjs(user.created).format(
'[Created on] YYYY-MM-DD [at] HH:mm A'
)
"
class="value"
>
{{ $dayjs(user.created).fromNow() }}
</p>
</div>
</div>
<div class="stat">
<DownloadIcon />
<div class="info">
<h4>Downloads</h4>
<p class="value">
{{ sumDownloads() }}
</p>
</div>
</div>
</div>
<m-footer class="footer" />
</div>
<div class="content">
<client-only>
<EthicalAd />
</client-only>
<div class="mods">
<SearchResult
v-for="result in mods"
:id="result.mod_id"
:key="result.mod_id"
:name="result.title"
:description="result.description"
:created-at="result.published"
:updated-at="result.updated"
:downloads="result.downloads.toString()"
:icon-url="result.icon_url"
:author-url="result.author_url"
:categories="result.categories"
:is-modrinth="true"
/>
</div>
</div>
</div>
<client-only>
<EthicalAd />
</client-only>
<div class="user-mods">
<SearchResult
v-for="result in mods"
:id="result.mod_id"
:key="result.mod_id"
:name="result.title"
:description="result.description"
:latest-version="result.versions[0]"
:created-at="result.published"
:updated-at="result.updated"
:downloads="result.downloads.toString()"
:icon-url="result.icon_url"
:author-url="result.author_url"
:page-url="result.page_url"
:categories="result.categories"
/>
</div>
</div>
</template>
<script>
import axios from 'axios'
import SearchResult from '@/components/ModResult'
import SearchResult from '@/components/ProjectCard'
import EthicalAd from '@/components/EthicalAd'
import MFooter from '@/components/MFooter'
import CalendarIcon from '~/assets/images/utils/calendar.svg?inline'
import DownloadIcon from '~/assets/images/utils/download.svg?inline'
export default {
auth: false,
components: {
EthicalAd,
SearchResult,
CalendarIcon,
DownloadIcon,
MFooter,
},
async asyncData(data) {
let res = await axios.get(
@@ -67,56 +112,61 @@ export default {
user,
}
},
methods: {
formatNumber(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
},
sumDownloads() {
let sum = 0
for (const mod of this.mods) {
sum += mod.downloads
}
return this.formatNumber(sum)
},
},
}
</script>
<style lang="scss" scoped>
.user-profile {
@media screen and (min-width: 900px) {
display: inline-flex;
text-align: left;
.sidebar-l {
min-width: 21rem;
.user-info {
@extend %row;
img {
width: 6rem;
height: 6rem;
margin-right: var(--spacing-card-md);
border-radius: var(--size-rounded-icon);
}
.text {
h2 {
margin: 0;
color: var(--color-text-dark);
font-size: var(--font-size-lg);
}
.badge {
display: inline-block;
}
}
}
text-align: center;
margin-bottom: 20px;
margin-left: 15px;
.stats {
display: flex;
flex-wrap: wrap;
.stat {
@extend %stat;
img {
border-radius: var(--size-rounded-md);
width: 250px;
height: 250px;
}
.info {
@media screen and (min-width: 900px) {
margin-left: 15px;
}
h1 {
margin-bottom: 0;
}
.joined-text {
margin-top: 5px;
color: var(--color-grey-4);
}
.bio {
margin-top: 5px;
font-size: 16pt;
}
.badge {
display: inline-block;
svg {
padding: 0.25rem;
border-radius: 50%;
background-color: var(--color-button-bg);
}
}
}
}
.user-mods {
border-top: 1px solid var(--color-grey-1);
padding-top: 10px;
margin: 10px;
* {
margin-left: 0;
}
.mods {
}
</style>