chore: lint frontend
This commit is contained in:
parent
5b97f1e9b8
commit
f7fc208b15
@ -1,2 +1,8 @@
|
|||||||
import config from '@modrinth/tooling-config/eslint/nuxt.mjs'
|
import config from '@modrinth/tooling-config/eslint/nuxt.mjs'
|
||||||
export default config
|
export default config.append([{
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
"import/no-unresolved": "off",
|
||||||
|
'no-undef': 'off'
|
||||||
|
}
|
||||||
|
}])
|
||||||
@ -1,27 +1,28 @@
|
|||||||
import { promises as fs } from "fs";
|
import { pathToFileURL } from 'node:url'
|
||||||
import { pathToFileURL } from "node:url";
|
|
||||||
import svgLoader from "vite-svg-loader";
|
|
||||||
import { resolve, basename, relative } from "pathe";
|
|
||||||
import { defineNuxtConfig } from "nuxt/config";
|
|
||||||
import { $fetch } from "ofetch";
|
|
||||||
import { globIterate } from "glob";
|
|
||||||
import { match as matchLocale } from "@formatjs/intl-localematcher";
|
|
||||||
import { consola } from "consola";
|
|
||||||
|
|
||||||
const STAGING_API_URL = "https://staging-api.modrinth.com/v2/";
|
import { match as matchLocale } from '@formatjs/intl-localematcher'
|
||||||
|
import { consola } from 'consola'
|
||||||
|
import { promises as fs } from 'fs'
|
||||||
|
import { globIterate } from 'glob'
|
||||||
|
import { defineNuxtConfig } from 'nuxt/config'
|
||||||
|
import { $fetch } from 'ofetch'
|
||||||
|
import { basename, relative, resolve } from 'pathe'
|
||||||
|
import svgLoader from 'vite-svg-loader'
|
||||||
|
|
||||||
|
const STAGING_API_URL = 'https://staging-api.modrinth.com/v2/'
|
||||||
|
|
||||||
const preloadedFonts = [
|
const preloadedFonts = [
|
||||||
"inter/Inter-Regular.woff2",
|
'inter/Inter-Regular.woff2',
|
||||||
"inter/Inter-Medium.woff2",
|
'inter/Inter-Medium.woff2',
|
||||||
"inter/Inter-SemiBold.woff2",
|
'inter/Inter-SemiBold.woff2',
|
||||||
"inter/Inter-Bold.woff2",
|
'inter/Inter-Bold.woff2',
|
||||||
];
|
]
|
||||||
|
|
||||||
const favicons = {
|
const favicons = {
|
||||||
"(prefers-color-scheme:no-preference)": "/favicon-light.ico",
|
'(prefers-color-scheme:no-preference)': '/favicon-light.ico',
|
||||||
"(prefers-color-scheme:light)": "/favicon-light.ico",
|
'(prefers-color-scheme:light)': '/favicon-light.ico',
|
||||||
"(prefers-color-scheme:dark)": "/favicon.ico",
|
'(prefers-color-scheme:dark)': '/favicon.ico',
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tags of locales that are auto-discovered besides the default locale.
|
* Tags of locales that are auto-discovered besides the default locale.
|
||||||
@ -29,52 +30,52 @@ const favicons = {
|
|||||||
* Preferably only the locales that reach a certain threshold of complete
|
* Preferably only the locales that reach a certain threshold of complete
|
||||||
* translations would be included in this array.
|
* translations would be included in this array.
|
||||||
*/
|
*/
|
||||||
const enabledLocales: string[] = [];
|
const enabledLocales: string[] = []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides for the categories of the certain locales.
|
* Overrides for the categories of the certain locales.
|
||||||
*/
|
*/
|
||||||
const localesCategoriesOverrides: Partial<Record<string, "fun" | "experimental">> = {
|
const localesCategoriesOverrides: Partial<Record<string, 'fun' | 'experimental'>> = {
|
||||||
"en-x-pirate": "fun",
|
'en-x-pirate': 'fun',
|
||||||
"en-x-updown": "fun",
|
'en-x-updown': 'fun',
|
||||||
"en-x-lolcat": "fun",
|
'en-x-lolcat': 'fun',
|
||||||
"en-x-uwu": "fun",
|
'en-x-uwu': 'fun',
|
||||||
"ru-x-bandit": "fun",
|
'ru-x-bandit': 'fun',
|
||||||
ar: "experimental",
|
ar: 'experimental',
|
||||||
he: "experimental",
|
he: 'experimental',
|
||||||
pes: "experimental",
|
pes: 'experimental',
|
||||||
};
|
}
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
srcDir: "src/",
|
srcDir: 'src/',
|
||||||
app: {
|
app: {
|
||||||
head: {
|
head: {
|
||||||
htmlAttrs: {
|
htmlAttrs: {
|
||||||
lang: "en",
|
lang: 'en',
|
||||||
},
|
},
|
||||||
title: "Modrinth",
|
title: 'Modrinth',
|
||||||
link: [
|
link: [
|
||||||
// The type is necessary because the linter can't always compare this very nested/complex type on itself
|
// The type is necessary because the linter can't always compare this very nested/complex type on itself
|
||||||
...preloadedFonts.map((font): object => {
|
...preloadedFonts.map((font): object => {
|
||||||
return {
|
return {
|
||||||
rel: "preload",
|
rel: 'preload',
|
||||||
href: `https://cdn-raw.modrinth.com/fonts/${font}?v=3.19`,
|
href: `https://cdn-raw.modrinth.com/fonts/${font}?v=3.19`,
|
||||||
as: "font",
|
as: 'font',
|
||||||
type: "font/woff2",
|
type: 'font/woff2',
|
||||||
crossorigin: "anonymous",
|
crossorigin: 'anonymous',
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
...Object.entries(favicons).map(([media, href]): object => {
|
...Object.entries(favicons).map(([media, href]): object => {
|
||||||
return { rel: "icon", type: "image/x-icon", href, media };
|
return { rel: 'icon', type: 'image/x-icon', href, media }
|
||||||
}),
|
}),
|
||||||
...Object.entries(favicons).map(([media, href]): object => {
|
...Object.entries(favicons).map(([media, href]): object => {
|
||||||
return { rel: "apple-touch-icon", type: "image/x-icon", href, media, sizes: "64x64" };
|
return { rel: 'apple-touch-icon', type: 'image/x-icon', href, media, sizes: '64x64' }
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
rel: "search",
|
rel: 'search',
|
||||||
type: "application/opensearchdescription+xml",
|
type: 'application/opensearchdescription+xml',
|
||||||
href: "/opensearch.xml",
|
href: '/opensearch.xml',
|
||||||
title: "Modrinth mods",
|
title: 'Modrinth mods',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -85,19 +86,19 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
esbuild: {
|
esbuild: {
|
||||||
define: {
|
define: {
|
||||||
global: "globalThis",
|
global: 'globalThis',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cacheDir: "../../node_modules/.vite/apps/knossos",
|
cacheDir: '../../node_modules/.vite/apps/knossos',
|
||||||
resolve: {
|
resolve: {
|
||||||
dedupe: ["vue"],
|
dedupe: ['vue'],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
svgLoader({
|
svgLoader({
|
||||||
svgoConfig: {
|
svgoConfig: {
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
{
|
||||||
name: "preset-default",
|
name: 'preset-default',
|
||||||
params: {
|
params: {
|
||||||
overrides: {
|
overrides: {
|
||||||
removeViewBox: false,
|
removeViewBox: false,
|
||||||
@ -110,33 +111,33 @@ export default defineNuxtConfig({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
hooks: {
|
hooks: {
|
||||||
async "build:before"() {
|
async 'build:before'() {
|
||||||
// 30 minutes
|
// 30 minutes
|
||||||
const TTL = 30 * 60 * 1000;
|
const TTL = 30 * 60 * 1000
|
||||||
|
|
||||||
let state: {
|
let state: {
|
||||||
lastGenerated?: string;
|
lastGenerated?: string
|
||||||
apiUrl?: string;
|
apiUrl?: string
|
||||||
categories?: any[];
|
categories?: any[]
|
||||||
loaders?: any[];
|
loaders?: any[]
|
||||||
gameVersions?: any[];
|
gameVersions?: any[]
|
||||||
donationPlatforms?: any[];
|
donationPlatforms?: any[]
|
||||||
reportTypes?: any[];
|
reportTypes?: any[]
|
||||||
homePageProjects?: any[];
|
homePageProjects?: any[]
|
||||||
homePageSearch?: any[];
|
homePageSearch?: any[]
|
||||||
homePageNotifs?: any[];
|
homePageNotifs?: any[]
|
||||||
products?: any[];
|
products?: any[]
|
||||||
errors?: number[];
|
errors?: number[]
|
||||||
} = {};
|
} = {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state = JSON.parse(await fs.readFile("./src/generated/state.json", "utf8"));
|
state = JSON.parse(await fs.readFile('./src/generated/state.json', 'utf8'))
|
||||||
} catch {
|
} catch {
|
||||||
// File doesn't exist, create folder
|
// File doesn't exist, create folder
|
||||||
await fs.mkdir("./src/generated", { recursive: true });
|
await fs.mkdir('./src/generated', { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_URL = getApiUrl();
|
const API_URL = getApiUrl()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
// Skip regeneration if within TTL...
|
// Skip regeneration if within TTL...
|
||||||
@ -145,25 +146,25 @@ export default defineNuxtConfig({
|
|||||||
// ...but only if the API URL is the same
|
// ...but only if the API URL is the same
|
||||||
state.apiUrl === API_URL
|
state.apiUrl === API_URL
|
||||||
) {
|
) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state.lastGenerated = new Date().toISOString();
|
state.lastGenerated = new Date().toISOString()
|
||||||
|
|
||||||
state.apiUrl = API_URL;
|
state.apiUrl = API_URL
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
headers: {
|
headers: {
|
||||||
"user-agent": "Knossos generator (support@modrinth.com)",
|
'user-agent': 'Knossos generator (support@modrinth.com)',
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
const caughtErrorCodes = new Set<number>();
|
const caughtErrorCodes = new Set<number>()
|
||||||
|
|
||||||
function handleFetchError(err: any, defaultValue: any) {
|
function handleFetchError(err: any, defaultValue: any) {
|
||||||
console.error("Error generating state: ", err);
|
console.error('Error generating state: ', err)
|
||||||
caughtErrorCodes.add(err.status);
|
caughtErrorCodes.add(err.status)
|
||||||
return defaultValue;
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
const [
|
const [
|
||||||
@ -193,152 +194,152 @@ export default defineNuxtConfig({
|
|||||||
$fetch(`${API_URL}search?limit=3&query=&index=updated`, headers).catch((err) =>
|
$fetch(`${API_URL}search?limit=3&query=&index=updated`, headers).catch((err) =>
|
||||||
handleFetchError(err, {}),
|
handleFetchError(err, {}),
|
||||||
),
|
),
|
||||||
$fetch(`${API_URL.replace("/v2/", "/_internal/")}billing/products`, headers).catch((err) =>
|
$fetch(`${API_URL.replace('/v2/', '/_internal/')}billing/products`, headers).catch((err) =>
|
||||||
handleFetchError(err, []),
|
handleFetchError(err, []),
|
||||||
),
|
),
|
||||||
]);
|
])
|
||||||
|
|
||||||
state.categories = categories;
|
state.categories = categories
|
||||||
state.loaders = loaders;
|
state.loaders = loaders
|
||||||
state.gameVersions = gameVersions;
|
state.gameVersions = gameVersions
|
||||||
state.donationPlatforms = donationPlatforms;
|
state.donationPlatforms = donationPlatforms
|
||||||
state.reportTypes = reportTypes;
|
state.reportTypes = reportTypes
|
||||||
state.homePageProjects = homePageProjects;
|
state.homePageProjects = homePageProjects
|
||||||
state.homePageSearch = homePageSearch;
|
state.homePageSearch = homePageSearch
|
||||||
state.homePageNotifs = homePageNotifs;
|
state.homePageNotifs = homePageNotifs
|
||||||
state.products = products;
|
state.products = products
|
||||||
state.errors = [...caughtErrorCodes];
|
state.errors = [...caughtErrorCodes]
|
||||||
|
|
||||||
await fs.writeFile("./src/generated/state.json", JSON.stringify(state));
|
await fs.writeFile('./src/generated/state.json', JSON.stringify(state))
|
||||||
|
|
||||||
console.log("Tags generated!");
|
console.log('Tags generated!')
|
||||||
},
|
},
|
||||||
"pages:extend"(routes) {
|
'pages:extend'(routes) {
|
||||||
routes.splice(
|
routes.splice(
|
||||||
routes.findIndex((x) => x.name === "search-searchProjectType"),
|
routes.findIndex((x) => x.name === 'search-searchProjectType'),
|
||||||
1,
|
1,
|
||||||
);
|
)
|
||||||
|
|
||||||
const types = ["mods", "modpacks", "plugins", "resourcepacks", "shaders", "datapacks"];
|
const types = ['mods', 'modpacks', 'plugins', 'resourcepacks', 'shaders', 'datapacks']
|
||||||
|
|
||||||
types.forEach((type) =>
|
types.forEach((type) =>
|
||||||
routes.push({
|
routes.push({
|
||||||
name: `search-${type}`,
|
name: `search-${type}`,
|
||||||
path: `/${type}`,
|
path: `/${type}`,
|
||||||
file: resolve(__dirname, "src/pages/search/[searchProjectType].vue"),
|
file: resolve(__dirname, 'src/pages/search/[searchProjectType].vue'),
|
||||||
children: [],
|
children: [],
|
||||||
}),
|
}),
|
||||||
);
|
)
|
||||||
},
|
},
|
||||||
async "vintl:extendOptions"(opts) {
|
async 'vintl:extendOptions'(opts) {
|
||||||
opts.locales ??= [];
|
opts.locales ??= []
|
||||||
|
|
||||||
const isProduction = getDomain() === "https://modrinth.com";
|
const isProduction = getDomain() === 'https://modrinth.com'
|
||||||
|
|
||||||
const resolveCompactNumberDataImport = await (async () => {
|
const resolveCompactNumberDataImport = await (async () => {
|
||||||
const compactNumberLocales: string[] = [];
|
const compactNumberLocales: string[] = []
|
||||||
|
|
||||||
for await (const localeFile of globIterate(
|
for await (const localeFile of globIterate(
|
||||||
"node_modules/@vintl/compact-number/dist/locale-data/*.mjs",
|
'node_modules/@vintl/compact-number/dist/locale-data/*.mjs',
|
||||||
{ ignore: "**/*.data.mjs" },
|
{ ignore: '**/*.data.mjs' },
|
||||||
)) {
|
)) {
|
||||||
const tag = basename(localeFile, ".mjs");
|
const tag = basename(localeFile, '.mjs')
|
||||||
compactNumberLocales.push(tag);
|
compactNumberLocales.push(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveImport(tag: string) {
|
function resolveImport(tag: string) {
|
||||||
const matchedTag = matchLocale([tag], compactNumberLocales, "en-x-placeholder");
|
const matchedTag = matchLocale([tag], compactNumberLocales, 'en-x-placeholder')
|
||||||
return matchedTag === "en-x-placeholder"
|
return matchedTag === 'en-x-placeholder'
|
||||||
? undefined
|
? undefined
|
||||||
: `@vintl/compact-number/locale-data/${matchedTag}`;
|
: `@vintl/compact-number/locale-data/${matchedTag}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolveImport;
|
return resolveImport
|
||||||
})();
|
})()
|
||||||
|
|
||||||
const resolveOmorphiaLocaleImport = await (async () => {
|
const resolveOmorphiaLocaleImport = await (async () => {
|
||||||
const omorphiaLocales: string[] = [];
|
const omorphiaLocales: string[] = []
|
||||||
const omorphiaLocaleSets = new Map<string, { files: { from: string }[] }>();
|
const omorphiaLocaleSets = new Map<string, { files: { from: string }[] }>()
|
||||||
|
|
||||||
for await (const localeDir of globIterate("node_modules/@modrinth/ui/src/locales/*", {
|
for await (const localeDir of globIterate('node_modules/@modrinth/ui/src/locales/*', {
|
||||||
posix: true,
|
posix: true,
|
||||||
})) {
|
})) {
|
||||||
const tag = basename(localeDir);
|
const tag = basename(localeDir)
|
||||||
omorphiaLocales.push(tag);
|
omorphiaLocales.push(tag)
|
||||||
|
|
||||||
const localeFiles: { from: string; format?: string }[] = [];
|
const localeFiles: { from: string; format?: string }[] = []
|
||||||
|
|
||||||
omorphiaLocaleSets.set(tag, { files: localeFiles });
|
omorphiaLocaleSets.set(tag, { files: localeFiles })
|
||||||
|
|
||||||
for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) {
|
for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) {
|
||||||
localeFiles.push({
|
localeFiles.push({
|
||||||
from: pathToFileURL(localeFile).toString(),
|
from: pathToFileURL(localeFile).toString(),
|
||||||
format: "default",
|
format: 'default',
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return function resolveLocaleImport(tag: string) {
|
return function resolveLocaleImport(tag: string) {
|
||||||
return omorphiaLocaleSets.get(matchLocale([tag], omorphiaLocales, "en-x-placeholder"));
|
return omorphiaLocaleSets.get(matchLocale([tag], omorphiaLocales, 'en-x-placeholder'))
|
||||||
};
|
}
|
||||||
})();
|
})()
|
||||||
|
|
||||||
for await (const localeDir of globIterate("src/locales/*/", { posix: true })) {
|
for await (const localeDir of globIterate('src/locales/*/', { posix: true })) {
|
||||||
const tag = basename(localeDir);
|
const tag = basename(localeDir)
|
||||||
if (isProduction && !enabledLocales.includes(tag) && opts.defaultLocale !== tag) continue;
|
if (isProduction && !enabledLocales.includes(tag) && opts.defaultLocale !== tag) continue
|
||||||
|
|
||||||
const locale =
|
const locale =
|
||||||
opts.locales.find((locale) => locale.tag === tag) ??
|
opts.locales.find((locale) => locale.tag === tag) ??
|
||||||
opts.locales[opts.locales.push({ tag }) - 1]!;
|
opts.locales[opts.locales.push({ tag }) - 1]!
|
||||||
|
|
||||||
const localeFiles = (locale.files ??= []);
|
const localeFiles = (locale.files ??= [])
|
||||||
|
|
||||||
for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) {
|
for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) {
|
||||||
const fileName = basename(localeFile);
|
const fileName = basename(localeFile)
|
||||||
if (fileName === "index.json") {
|
if (fileName === 'index.json') {
|
||||||
localeFiles.push({
|
localeFiles.push({
|
||||||
from: `./${relative("./src", localeFile)}`,
|
from: `./${relative('./src', localeFile)}`,
|
||||||
format: "crowdin",
|
format: 'crowdin',
|
||||||
});
|
})
|
||||||
} else if (fileName === "meta.json") {
|
} else if (fileName === 'meta.json') {
|
||||||
const meta: Record<string, { message: string }> = await fs
|
const meta: Record<string, { message: string }> = await fs
|
||||||
.readFile(localeFile, "utf8")
|
.readFile(localeFile, 'utf8')
|
||||||
.then((date) => JSON.parse(date));
|
.then((date) => JSON.parse(date))
|
||||||
const localeMeta = (locale.meta ??= {});
|
const localeMeta = (locale.meta ??= {})
|
||||||
for (const key in meta) {
|
for (const key in meta) {
|
||||||
const value = meta[key];
|
const value = meta[key]
|
||||||
if (value === undefined) continue;
|
if (value === undefined) continue
|
||||||
localeMeta[key] = value.message;
|
localeMeta[key] = value.message
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(locale.resources ??= {})[fileName] = `./${relative("./src", localeFile)}`;
|
;(locale.resources ??= {})[fileName] = `./${relative('./src', localeFile)}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoryOverride = localesCategoriesOverrides[tag];
|
const categoryOverride = localesCategoriesOverrides[tag]
|
||||||
if (categoryOverride != null) {
|
if (categoryOverride != null) {
|
||||||
(locale.meta ??= {}).category = categoryOverride;
|
;(locale.meta ??= {}).category = categoryOverride
|
||||||
}
|
}
|
||||||
|
|
||||||
const omorphiaLocaleData = resolveOmorphiaLocaleImport(tag);
|
const omorphiaLocaleData = resolveOmorphiaLocaleImport(tag)
|
||||||
if (omorphiaLocaleData != null) {
|
if (omorphiaLocaleData != null) {
|
||||||
localeFiles.push(...omorphiaLocaleData.files);
|
localeFiles.push(...omorphiaLocaleData.files)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cnDataImport = resolveCompactNumberDataImport(tag);
|
const cnDataImport = resolveCompactNumberDataImport(tag)
|
||||||
if (cnDataImport != null) {
|
if (cnDataImport != null) {
|
||||||
(locale.additionalImports ??= []).push({
|
;(locale.additionalImports ??= []).push({
|
||||||
from: cnDataImport,
|
from: cnDataImport,
|
||||||
resolve: false,
|
resolve: false,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
apiBaseUrl: process.env.BASE_URL ?? globalThis.BASE_URL ?? getApiUrl(),
|
apiBaseUrl: process.env.BASE_URL ?? globalThis.BASE_URL ?? getApiUrl(),
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
rateLimitKey: process.env.RATE_LIMIT_IGNORE_KEY ?? globalThis.RATE_LIMIT_IGNORE_KEY,
|
rateLimitKey: process.env.RATE_LIMIT_IGNORE_KEY ?? globalThis.RATE_LIMIT_IGNORE_KEY,
|
||||||
pyroBaseUrl: process.env.PYRO_BASE_URL,
|
pyroBaseUrl: process.env.PYRO_BASE_URL,
|
||||||
public: {
|
public: {
|
||||||
@ -348,25 +349,26 @@ export default defineNuxtConfig({
|
|||||||
production: isProduction(),
|
production: isProduction(),
|
||||||
featureFlagOverrides: getFeatureFlagOverrides(),
|
featureFlagOverrides: getFeatureFlagOverrides(),
|
||||||
|
|
||||||
owner: process.env.VERCEL_GIT_REPO_OWNER || "modrinth",
|
owner: process.env.VERCEL_GIT_REPO_OWNER || 'modrinth',
|
||||||
slug: process.env.VERCEL_GIT_REPO_SLUG || "code",
|
slug: process.env.VERCEL_GIT_REPO_SLUG || 'code',
|
||||||
branch:
|
branch:
|
||||||
process.env.VERCEL_GIT_COMMIT_REF ||
|
process.env.VERCEL_GIT_COMMIT_REF ||
|
||||||
process.env.CF_PAGES_BRANCH ||
|
process.env.CF_PAGES_BRANCH ||
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
globalThis.CF_PAGES_BRANCH ||
|
globalThis.CF_PAGES_BRANCH ||
|
||||||
"master",
|
'master',
|
||||||
hash:
|
hash:
|
||||||
process.env.VERCEL_GIT_COMMIT_SHA ||
|
process.env.VERCEL_GIT_COMMIT_SHA ||
|
||||||
process.env.CF_PAGES_COMMIT_SHA ||
|
process.env.CF_PAGES_COMMIT_SHA ||
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
globalThis.CF_PAGES_COMMIT_SHA ||
|
globalThis.CF_PAGES_COMMIT_SHA ||
|
||||||
"unknown",
|
'unknown',
|
||||||
|
|
||||||
stripePublishableKey:
|
stripePublishableKey:
|
||||||
process.env.STRIPE_PUBLISHABLE_KEY ||
|
process.env.STRIPE_PUBLISHABLE_KEY ||
|
||||||
|
// @ts-expect-error
|
||||||
globalThis.STRIPE_PUBLISHABLE_KEY ||
|
globalThis.STRIPE_PUBLISHABLE_KEY ||
|
||||||
"pk_test_51JbFxJJygY5LJFfKV50mnXzz3YLvBVe2Gd1jn7ljWAkaBlRz3VQdxN9mXcPSrFbSqxwAb0svte9yhnsmm7qHfcWn00R611Ce7b",
|
'pk_test_51JbFxJJygY5LJFfKV50mnXzz3YLvBVe2Gd1jn7ljWAkaBlRz3VQdxN9mXcPSrFbSqxwAb0svte9yhnsmm7qHfcWn00R611Ce7b',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
typescript: {
|
typescript: {
|
||||||
@ -375,62 +377,62 @@ export default defineNuxtConfig({
|
|||||||
typeCheck: false,
|
typeCheck: false,
|
||||||
tsConfig: {
|
tsConfig: {
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
moduleResolution: "bundler",
|
moduleResolution: 'bundler',
|
||||||
allowImportingTsExtensions: true,
|
allowImportingTsExtensions: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modules: ["@vintl/nuxt", "@pinia/nuxt"],
|
modules: ['@vintl/nuxt', '@pinia/nuxt'],
|
||||||
vintl: {
|
vintl: {
|
||||||
defaultLocale: "en-US",
|
defaultLocale: 'en-US',
|
||||||
locales: [
|
locales: [
|
||||||
{
|
{
|
||||||
tag: "en-US",
|
tag: 'en-US',
|
||||||
meta: {
|
meta: {
|
||||||
static: {
|
static: {
|
||||||
iso: "en",
|
iso: 'en',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
storage: "cookie",
|
storage: 'cookie',
|
||||||
parserless: "only-prod",
|
parserless: 'only-prod',
|
||||||
seo: {
|
seo: {
|
||||||
defaultLocaleHasParameter: false,
|
defaultLocaleHasParameter: false,
|
||||||
},
|
},
|
||||||
onParseError({ error, message, messageId, moduleId, parseMessage, parserOptions }) {
|
onParseError({ error, message, messageId, moduleId, parseMessage, parserOptions }) {
|
||||||
const errorMessage = String(error);
|
const errorMessage = String(error)
|
||||||
const modulePath = relative(__dirname, moduleId);
|
const modulePath = relative(__dirname, moduleId)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fallback = parseMessage(message, { ...parserOptions, ignoreTag: true });
|
const fallback = parseMessage(message, { ...parserOptions, ignoreTag: true })
|
||||||
|
|
||||||
consola.warn(
|
consola.warn(
|
||||||
`[i18n] ${messageId} in ${modulePath} cannot be parsed normally due to ${errorMessage}. The tags will will not be parsed.`,
|
`[i18n] ${messageId} in ${modulePath} cannot be parsed normally due to ${errorMessage}. The tags will will not be parsed.`,
|
||||||
);
|
)
|
||||||
|
|
||||||
return fallback;
|
return fallback
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const secondaryErrorMessage = String(err);
|
const secondaryErrorMessage = String(err)
|
||||||
|
|
||||||
const reason =
|
const reason =
|
||||||
errorMessage === secondaryErrorMessage
|
errorMessage === secondaryErrorMessage
|
||||||
? errorMessage
|
? errorMessage
|
||||||
: `${errorMessage} and ${secondaryErrorMessage}`;
|
: `${errorMessage} and ${secondaryErrorMessage}`
|
||||||
|
|
||||||
consola.warn(
|
consola.warn(
|
||||||
`[i18n] ${messageId} in ${modulePath} cannot be parsed due to ${reason}. It will be skipped.`,
|
`[i18n] ${messageId} in ${modulePath} cannot be parsed due to ${reason}. It will be skipped.`,
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nitro: {
|
nitro: {
|
||||||
moduleSideEffects: ["@vintl/compact-number/locale-data"],
|
moduleSideEffects: ['@vintl/compact-number/locale-data'],
|
||||||
},
|
},
|
||||||
devtools: {
|
devtools: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
css: ["~/assets/styles/tailwind.css"],
|
css: ['~/assets/styles/tailwind.css'],
|
||||||
postcss: {
|
postcss: {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
@ -438,50 +440,50 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
routeRules: {
|
routeRules: {
|
||||||
"/**": {
|
'/**': {
|
||||||
headers: {
|
headers: {
|
||||||
"Accept-CH": "Sec-CH-Prefers-Color-Scheme",
|
'Accept-CH': 'Sec-CH-Prefers-Color-Scheme',
|
||||||
"Critical-CH": "Sec-CH-Prefers-Color-Scheme",
|
'Critical-CH': 'Sec-CH-Prefers-Color-Scheme',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
compatibilityDate: "2024-07-03",
|
compatibilityDate: '2024-07-03',
|
||||||
telemetry: false,
|
telemetry: false,
|
||||||
});
|
})
|
||||||
|
|
||||||
function getApiUrl() {
|
function getApiUrl() {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
return process.env.BROWSER_BASE_URL ?? globalThis.BROWSER_BASE_URL ?? STAGING_API_URL;
|
return process.env.BROWSER_BASE_URL ?? globalThis.BROWSER_BASE_URL ?? STAGING_API_URL
|
||||||
}
|
}
|
||||||
|
|
||||||
function isProduction() {
|
function isProduction() {
|
||||||
return process.env.NODE_ENV === "production";
|
return process.env.NODE_ENV === 'production'
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFeatureFlagOverrides() {
|
function getFeatureFlagOverrides() {
|
||||||
return JSON.parse(process.env.FLAG_OVERRIDES ?? "{}");
|
return JSON.parse(process.env.FLAG_OVERRIDES ?? '{}')
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDomain() {
|
function getDomain() {
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
if (process.env.SITE_URL) {
|
if (process.env.SITE_URL) {
|
||||||
return process.env.SITE_URL;
|
return process.env.SITE_URL
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
else if (process.env.CF_PAGES_URL || globalThis.CF_PAGES_URL) {
|
else if (process.env.CF_PAGES_URL || globalThis.CF_PAGES_URL) {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
return process.env.CF_PAGES_URL ?? globalThis.CF_PAGES_URL;
|
return process.env.CF_PAGES_URL ?? globalThis.CF_PAGES_URL
|
||||||
} else if (process.env.HEROKU_APP_NAME) {
|
} else if (process.env.HEROKU_APP_NAME) {
|
||||||
return `https://${process.env.HEROKU_APP_NAME}.herokuapp.com`;
|
return `https://${process.env.HEROKU_APP_NAME}.herokuapp.com`
|
||||||
} else if (process.env.VERCEL_URL) {
|
} else if (process.env.VERCEL_URL) {
|
||||||
return `https://${process.env.VERCEL_URL}`;
|
return `https://${process.env.VERCEL_URL}`
|
||||||
} else if (getApiUrl() === STAGING_API_URL) {
|
} else if (getApiUrl() === STAGING_API_URL) {
|
||||||
return "https://staging.modrinth.com";
|
return 'https://staging.modrinth.com'
|
||||||
} else {
|
} else {
|
||||||
return "https://modrinth.com";
|
return 'https://modrinth.com'
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const port = process.env.PORT || 3000;
|
const port = process.env.PORT || 3000
|
||||||
return `http://localhost:${port}`;
|
return `http://localhost:${port}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,7 +49,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { InfoIcon, ClientIcon, GlobeIcon, ServerIcon } from "@modrinth/assets";
|
import { ClientIcon, GlobeIcon, InfoIcon, ServerIcon } from "@modrinth/assets";
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
type: {
|
type: {
|
||||||
|
|||||||
@ -84,8 +84,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { NewModal, ButtonStyled, DropdownSelect } from "@modrinth/ui";
|
import { PlusIcon,XIcon } from "@modrinth/assets";
|
||||||
import { XIcon, PlusIcon } from "@modrinth/assets";
|
import { ButtonStyled, DropdownSelect,NewModal } from "@modrinth/ui";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const app = useNuxtApp();
|
const app = useNuxtApp();
|
||||||
|
|||||||
@ -35,7 +35,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, onMounted } from "vue";
|
import { computed, onMounted,ref, watch } from "vue";
|
||||||
|
|
||||||
const route = useNativeRoute();
|
const route = useNativeRoute();
|
||||||
|
|
||||||
|
|||||||
@ -1,42 +1,44 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonStyled } from "@modrinth/ui";
|
import { CheckIcon, MailIcon } from '@modrinth/assets'
|
||||||
import { MailIcon, CheckIcon } from "@modrinth/assets";
|
import { ButtonStyled } from '@modrinth/ui'
|
||||||
import { ref } from "vue";
|
import { ref } from 'vue'
|
||||||
import { useBaseFetch } from "~/composables/fetch.js";
|
|
||||||
|
|
||||||
const auth = await useAuth();
|
import { useBaseFetch } from '~/composables/fetch.js'
|
||||||
const showSubscriptionConfirmation = ref(false);
|
|
||||||
|
const auth = await useAuth()
|
||||||
|
const showSubscriptionConfirmation = ref(false)
|
||||||
const showSubscribeButton = useAsyncData(
|
const showSubscribeButton = useAsyncData(
|
||||||
async () => {
|
async () => {
|
||||||
if (auth.value?.user) {
|
if (auth.value?.user) {
|
||||||
try {
|
try {
|
||||||
const { subscribed } = await useBaseFetch("auth/email/subscribe", {
|
const { subscribed } = await useBaseFetch('auth/email/subscribe', {
|
||||||
method: "GET",
|
method: 'GET',
|
||||||
});
|
})
|
||||||
return !subscribed;
|
return !subscribed
|
||||||
} catch {
|
} catch {
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ watch: [auth], server: false },
|
{ watch: [auth], server: false },
|
||||||
);
|
)
|
||||||
|
|
||||||
async function subscribe() {
|
async function subscribe() {
|
||||||
try {
|
try {
|
||||||
await useBaseFetch("auth/email/subscribe", {
|
await useBaseFetch('auth/email/subscribe', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
});
|
})
|
||||||
showSubscriptionConfirmation.value = true;
|
showSubscriptionConfirmation.value = true
|
||||||
} catch {
|
} catch {
|
||||||
|
// TODO: Use addNotification when DI pr is merged.
|
||||||
} finally {
|
} finally {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showSubscriptionConfirmation.value = false;
|
showSubscriptionConfirmation.value = false
|
||||||
showSubscribeButton.status.value = "success";
|
showSubscribeButton.status.value = 'success'
|
||||||
showSubscribeButton.data.value = false;
|
showSubscribeButton.data.value = false
|
||||||
}, 2500);
|
}, 2500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -319,26 +319,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { renderString } from "@modrinth/utils";
|
|
||||||
import {
|
import {
|
||||||
UserPlusIcon,
|
|
||||||
ScaleIcon,
|
|
||||||
BellIcon,
|
BellIcon,
|
||||||
CheckCircleIcon,
|
|
||||||
CalendarIcon,
|
CalendarIcon,
|
||||||
VersionIcon,
|
CheckCircleIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
XIcon,
|
|
||||||
ExternalIcon,
|
ExternalIcon,
|
||||||
|
ScaleIcon,
|
||||||
|
UserPlusIcon,
|
||||||
|
VersionIcon,
|
||||||
|
XIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { Avatar, ProjectStatusBadge, CopyCode, useRelativeTime } from "@modrinth/ui";
|
import { Avatar, CopyCode, ProjectStatusBadge, useRelativeTime } from "@modrinth/ui";
|
||||||
import ThreadSummary from "~/components/ui/thread/ThreadSummary.vue";
|
import { renderString } from "@modrinth/utils";
|
||||||
import { getProjectLink, getVersionLink } from "~/helpers/projects.js";
|
|
||||||
import { getUserLink } from "~/helpers/users.js";
|
|
||||||
import { acceptTeamInvite, removeSelfFromTeam } from "~/helpers/teams.js";
|
|
||||||
import { markAsRead } from "~/helpers/notifications.ts";
|
|
||||||
import DoubleIcon from "~/components/ui/DoubleIcon.vue";
|
import DoubleIcon from "~/components/ui/DoubleIcon.vue";
|
||||||
import Categories from "~/components/ui/search/Categories.vue";
|
import Categories from "~/components/ui/search/Categories.vue";
|
||||||
|
import ThreadSummary from "~/components/ui/thread/ThreadSummary.vue";
|
||||||
|
import { markAsRead } from "~/helpers/notifications.ts";
|
||||||
|
import { getProjectLink, getVersionLink } from "~/helpers/projects.js";
|
||||||
|
import { acceptTeamInvite, removeSelfFromTeam } from "~/helpers/teams.js";
|
||||||
|
import { getUserLink } from "~/helpers/users.js";
|
||||||
|
|
||||||
const app = useNuxtApp();
|
const app = useNuxtApp();
|
||||||
const emit = defineEmits(["update:notifications"]);
|
const emit = defineEmits(["update:notifications"]);
|
||||||
|
|||||||
@ -74,76 +74,77 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ButtonStyled } from "@modrinth/ui";
|
|
||||||
import {
|
import {
|
||||||
XCircleIcon,
|
|
||||||
CheckCircleIcon,
|
CheckCircleIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
|
CopyIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
IssuesIcon,
|
IssuesIcon,
|
||||||
|
XCircleIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
CopyIcon,
|
} from '@modrinth/assets'
|
||||||
} from "@modrinth/assets";
|
import { ButtonStyled } from '@modrinth/ui'
|
||||||
const notifications = useNotifications();
|
const notifications = useNotifications()
|
||||||
const { isVisible: moveNotificationsRight } = useNotificationRightwards();
|
const { isVisible: moveNotificationsRight } = useNotificationRightwards()
|
||||||
|
|
||||||
const isIntercomPresent = ref(false);
|
const isIntercomPresent = ref(false)
|
||||||
|
|
||||||
function stopTimer(notif) {
|
function stopTimer(notif) {
|
||||||
clearTimeout(notif.timer);
|
clearTimeout(notif.timer)
|
||||||
}
|
}
|
||||||
|
|
||||||
const copied = ref({});
|
const copied = ref({})
|
||||||
|
|
||||||
const createNotifText = (notif) => {
|
const createNotifText = (notif) => {
|
||||||
let text = "";
|
let text = ''
|
||||||
if (notif.title) {
|
if (notif.title) {
|
||||||
text += notif.title;
|
text += notif.title
|
||||||
}
|
}
|
||||||
if (notif.text) {
|
if (notif.text) {
|
||||||
if (text.length > 0) {
|
if (text.length > 0) {
|
||||||
text += "\n";
|
text += '\n'
|
||||||
}
|
}
|
||||||
text += notif.text;
|
text += notif.text
|
||||||
}
|
}
|
||||||
if (notif.errorCode) {
|
if (notif.errorCode) {
|
||||||
if (text.length > 0) {
|
if (text.length > 0) {
|
||||||
text += "\n";
|
text += '\n'
|
||||||
}
|
}
|
||||||
text += notif.errorCode;
|
text += notif.errorCode
|
||||||
|
}
|
||||||
|
return text
|
||||||
}
|
}
|
||||||
return text;
|
|
||||||
};
|
|
||||||
|
|
||||||
function checkIntercomPresence() {
|
function checkIntercomPresence() {
|
||||||
isIntercomPresent.value = !!document.querySelector(".intercom-lightweight-app");
|
isIntercomPresent.value = !!document.querySelector('.intercom-lightweight-app')
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkIntercomPresence();
|
checkIntercomPresence()
|
||||||
|
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
checkIntercomPresence();
|
checkIntercomPresence()
|
||||||
});
|
})
|
||||||
|
|
||||||
observer.observe(document.body, {
|
observer.observe(document.body, {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
});
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
observer.disconnect();
|
observer.disconnect()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
function copyToClipboard(notif) {
|
function copyToClipboard(notif) {
|
||||||
const text = createNotifText(notif);
|
const text = createNotifText(notif)
|
||||||
|
|
||||||
copied.value[text] = true;
|
copied.value[text] = true
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
delete copied.value[text];
|
const { [text]: _, ...newCopied } = copied.value
|
||||||
}, 2000);
|
copied.value = newCopied
|
||||||
|
}, 2000)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -31,7 +31,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T">
|
<script setup lang="ts" generic="T">
|
||||||
import { ref, computed, onMounted } from "vue";
|
import { computed, onMounted,ref } from "vue";
|
||||||
|
|
||||||
const modelValue = defineModel<T>({ required: true });
|
const modelValue = defineModel<T>({ required: true });
|
||||||
|
|
||||||
|
|||||||
@ -71,7 +71,7 @@
|
|||||||
</NewModal>
|
</NewModal>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { XIcon, PlusIcon } from "@modrinth/assets";
|
import { PlusIcon,XIcon } from "@modrinth/assets";
|
||||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
|
|
||||||
const router = useNativeRouter();
|
const router = useNativeRouter();
|
||||||
|
|||||||
@ -110,7 +110,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { BoxIcon, SettingsIcon, TransferIcon, XIcon } from "@modrinth/assets";
|
import { BoxIcon, SettingsIcon, TransferIcon, XIcon } from "@modrinth/assets";
|
||||||
import { Button, Modal, Checkbox, CopyCode, Avatar } from "@modrinth/ui";
|
import { Avatar,Button, Checkbox, CopyCode, Modal } from "@modrinth/ui";
|
||||||
import { formatProjectType } from "@modrinth/utils";
|
import { formatProjectType } from "@modrinth/utils";
|
||||||
|
|
||||||
const modalOpen = ref(null);
|
const modalOpen = ref(null);
|
||||||
|
|||||||
@ -90,10 +90,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { CalendarIcon, UpdatedIcon, DownloadIcon, HeartIcon } from "@modrinth/assets";
|
import { CalendarIcon, DownloadIcon, HeartIcon,UpdatedIcon } from "@modrinth/assets";
|
||||||
import { Avatar, ProjectStatusBadge, useRelativeTime } from "@modrinth/ui";
|
import { Avatar, ProjectStatusBadge, useRelativeTime } from "@modrinth/ui";
|
||||||
import Categories from "~/components/ui/search/Categories.vue";
|
|
||||||
import EnvironmentIndicator from "~/components/ui/EnvironmentIndicator.vue";
|
import EnvironmentIndicator from "~/components/ui/EnvironmentIndicator.vue";
|
||||||
|
import Categories from "~/components/ui/search/Categories.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@ -109,16 +109,17 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
ChevronRightIcon,
|
|
||||||
CheckIcon,
|
|
||||||
XIcon,
|
|
||||||
AsteriskIcon,
|
AsteriskIcon,
|
||||||
LightBulbIcon,
|
CheckIcon,
|
||||||
SendIcon,
|
ChevronRightIcon,
|
||||||
ScaleIcon,
|
|
||||||
DropdownIcon,
|
DropdownIcon,
|
||||||
|
LightBulbIcon,
|
||||||
|
ScaleIcon,
|
||||||
|
SendIcon,
|
||||||
|
XIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { formatProjectType } from "@modrinth/utils";
|
import { formatProjectType } from "@modrinth/utils";
|
||||||
|
|
||||||
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
|
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -244,7 +245,7 @@ const nags = computed(() => [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
condition: props.project.gallery.length === 0 || !featuredGalleryImage,
|
condition: props.project.gallery.length === 0 || !featuredGalleryImage.value,
|
||||||
title: "Feature a gallery image",
|
title: "Feature a gallery image",
|
||||||
id: "feature-gallery-image",
|
id: "feature-gallery-image",
|
||||||
description: "Featured gallery images may be the first impression of many users.",
|
description: "Featured gallery images may be the first impression of many users.",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { formatMoney,formatNumber } from "@modrinth/utils";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { formatNumber, formatMoney } from "@modrinth/utils";
|
|
||||||
import VueApexCharts from "vue3-apexcharts";
|
import VueApexCharts from "vue3-apexcharts";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -304,17 +304,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { DownloadIcon,UpdatedIcon } from "@modrinth/assets";
|
||||||
import { Button, Card, DropdownSelect } from "@modrinth/ui";
|
import { Button, Card, DropdownSelect } from "@modrinth/ui";
|
||||||
import { formatMoney, formatNumber, formatCategoryHeader } from "@modrinth/utils";
|
import { formatCategoryHeader,formatMoney, formatNumber } from "@modrinth/utils";
|
||||||
import { UpdatedIcon, DownloadIcon } from "@modrinth/assets";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
import { analyticsSetToCSVString, intToRgba } from "~/utils/analytics.js";
|
import { UiChartsChart as Chart,UiChartsCompactChart as CompactChart } from "#components";
|
||||||
|
|
||||||
import { UiChartsCompactChart as CompactChart, UiChartsChart as Chart } from "#components";
|
|
||||||
|
|
||||||
import PaletteIcon from "~/assets/icons/palette.svg?component";
|
import PaletteIcon from "~/assets/icons/palette.svg?component";
|
||||||
|
import { analyticsSetToCSVString, intToRgba } from "~/utils/analytics.js";
|
||||||
|
|
||||||
const router = useNativeRouter();
|
const router = useNativeRouter();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|||||||
@ -118,22 +118,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import dayjs from "dayjs";
|
|
||||||
import {
|
import {
|
||||||
Avatar,
|
|
||||||
useRelativeTime,
|
|
||||||
OverflowMenu,
|
|
||||||
type OverflowMenuOption,
|
|
||||||
ButtonStyled,
|
|
||||||
} from "@modrinth/ui";
|
|
||||||
import {
|
|
||||||
EllipsisVerticalIcon,
|
|
||||||
OrganizationIcon,
|
|
||||||
EyeIcon,
|
|
||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
|
EllipsisVerticalIcon,
|
||||||
|
EyeIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
|
OrganizationIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import type { ExtendedDelphiReport } from "@modrinth/moderation";
|
import type { ExtendedDelphiReport } from "@modrinth/moderation";
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
ButtonStyled,
|
||||||
|
OverflowMenu,
|
||||||
|
type OverflowMenuOption,
|
||||||
|
useRelativeTime,
|
||||||
|
} from "@modrinth/ui";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
report: ExtendedDelphiReport;
|
report: ExtendedDelphiReport;
|
||||||
|
|||||||
@ -75,10 +75,10 @@
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<span class="hidden sm:inline">{{
|
<span class="hidden sm:inline">{{
|
||||||
props.queueEntry.project.project_types.map(formatProjectType).join(", ")
|
props.queueEntry.project.project_types.map(formatProjectType).join(', ')
|
||||||
}}</span>
|
}}</span>
|
||||||
<span class="sm:hidden">{{
|
<span class="sm:hidden">{{
|
||||||
formatProjectType(props.queueEntry.project.project_type ?? "project").substring(0, 3)
|
formatProjectType(props.queueEntry.project.project_type ?? 'project').substring(0, 3)
|
||||||
}}</span>
|
}}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@ -105,7 +105,7 @@
|
|||||||
>
|
>
|
||||||
<span class="hidden sm:inline">{{ getSubmittedTime(queueEntry) }}</span>
|
<span class="hidden sm:inline">{{ getSubmittedTime(queueEntry) }}</span>
|
||||||
<span class="sm:hidden">{{
|
<span class="sm:hidden">{{
|
||||||
getSubmittedTime(queueEntry).replace("Submitted ", "")
|
getSubmittedTime(queueEntry).replace('Submitted ', '')
|
||||||
}}</span>
|
}}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -127,39 +127,35 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import dayjs from "dayjs";
|
|
||||||
import {
|
import {
|
||||||
EyeIcon,
|
|
||||||
PaintbrushIcon,
|
|
||||||
ScaleIcon,
|
|
||||||
BoxIcon,
|
BoxIcon,
|
||||||
GlassesIcon,
|
|
||||||
PlugIcon,
|
|
||||||
PackageOpenIcon,
|
|
||||||
BracesIcon,
|
BracesIcon,
|
||||||
} from "@modrinth/assets";
|
EyeIcon,
|
||||||
import { useRelativeTime, Avatar, ButtonStyled, Badge } from "@modrinth/ui";
|
GlassesIcon,
|
||||||
import {
|
PackageOpenIcon,
|
||||||
formatProjectType,
|
PaintbrushIcon,
|
||||||
type Organization,
|
PlugIcon,
|
||||||
type Project,
|
ScaleIcon,
|
||||||
type TeamMember,
|
} from '@modrinth/assets'
|
||||||
} from "@modrinth/utils";
|
import { Avatar, Badge, ButtonStyled, useRelativeTime } from '@modrinth/ui'
|
||||||
import { computed } from "vue";
|
import { formatProjectType } from '@modrinth/utils'
|
||||||
import { useModerationStore } from "~/store/moderation.ts";
|
import dayjs from 'dayjs'
|
||||||
import type { ModerationProject } from "~/helpers/moderation";
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const formatRelativeTime = useRelativeTime();
|
import type { ModerationProject } from '~/helpers/moderation'
|
||||||
const moderationStore = useModerationStore();
|
import { useModerationStore } from '~/store/moderation.ts'
|
||||||
|
|
||||||
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const moderationStore = useModerationStore()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
queueEntry: ModerationProject;
|
queueEntry: ModerationProject
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
function getDaysQueued(date: Date): number {
|
function getDaysQueued(date: Date): number {
|
||||||
const now = new Date();
|
const now = new Date()
|
||||||
const diff = now.getTime() - date.getTime();
|
const diff = now.getTime() - date.getTime()
|
||||||
return Math.floor(diff / (1000 * 60 * 60 * 24));
|
return Math.floor(diff / (1000 * 60 * 60 * 24))
|
||||||
}
|
}
|
||||||
|
|
||||||
const queuedDate = computed(() => {
|
const queuedDate = computed(() => {
|
||||||
@ -167,38 +163,38 @@ const queuedDate = computed(() => {
|
|||||||
props.queueEntry.project.queued ||
|
props.queueEntry.project.queued ||
|
||||||
props.queueEntry.project.created ||
|
props.queueEntry.project.created ||
|
||||||
props.queueEntry.project.updated,
|
props.queueEntry.project.updated,
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
|
|
||||||
const daysInQueue = computed(() => {
|
const daysInQueue = computed(() => {
|
||||||
return getDaysQueued(queuedDate.value.toDate());
|
return getDaysQueued(queuedDate.value.toDate())
|
||||||
});
|
})
|
||||||
|
|
||||||
function openProjectForReview() {
|
function openProjectForReview() {
|
||||||
moderationStore.setSingleProject(props.queueEntry.project.id);
|
moderationStore.setSingleProject(props.queueEntry.project.id)
|
||||||
navigateTo({
|
navigateTo({
|
||||||
name: "type-id",
|
name: 'type-id',
|
||||||
params: {
|
params: {
|
||||||
type: "project",
|
type: 'project',
|
||||||
id: props.queueEntry.project.id,
|
id: props.queueEntry.project.id,
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
showChecklist: true,
|
showChecklist: true,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSubmittedTime(project: any): string {
|
function getSubmittedTime(): string {
|
||||||
const date =
|
const date =
|
||||||
props.queueEntry.project.queued ||
|
props.queueEntry.project.queued ||
|
||||||
props.queueEntry.project.created ||
|
props.queueEntry.project.created ||
|
||||||
props.queueEntry.project.updated;
|
props.queueEntry.project.updated
|
||||||
if (!date) return "Unknown";
|
if (!date) return 'Unknown'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return `Submitted ${formatRelativeTime(dayjs(date).toISOString())}`;
|
return `Submitted ${formatRelativeTime(dayjs(date).toISOString())}`
|
||||||
} catch {
|
} catch {
|
||||||
return "Unknown";
|
return 'Unknown'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -87,7 +87,7 @@
|
|||||||
v-if="report.target.type === 'organization'"
|
v-if="report.target.type === 'organization'"
|
||||||
class="align-middle"
|
class="align-middle"
|
||||||
/>
|
/>
|
||||||
{{ report.target.name || "Unknown User" }}
|
{{ report.target.name || 'Unknown User' }}
|
||||||
</span>
|
</span>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
@ -102,7 +102,7 @@
|
|||||||
class="max-w-[200px] truncate font-mono text-xs sm:max-w-none"
|
class="max-w-[200px] truncate font-mono text-xs sm:max-w-none"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
report.version.files.find((file) => file.primary)?.filename || "Unknown Version"
|
report.version.files.find((file) => file.primary)?.filename || 'Unknown Version'
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -120,7 +120,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CollapsibleRegion class="my-4" ref="collapsibleRegion">
|
<CollapsibleRegion ref="collapsibleRegion" class="my-4">
|
||||||
<ReportThread
|
<ReportThread
|
||||||
v-if="report.thread"
|
v-if="report.thread"
|
||||||
ref="reportThread"
|
ref="reportThread"
|
||||||
@ -135,81 +135,82 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
Avatar,
|
|
||||||
useRelativeTime,
|
|
||||||
OverflowMenu,
|
|
||||||
type OverflowMenuOption,
|
|
||||||
CollapsibleRegion,
|
|
||||||
ButtonStyled,
|
|
||||||
} from "@modrinth/ui";
|
|
||||||
import {
|
|
||||||
EllipsisVerticalIcon,
|
|
||||||
OrganizationIcon,
|
|
||||||
EyeIcon,
|
|
||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
|
EllipsisVerticalIcon,
|
||||||
|
EyeIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
} from "@modrinth/assets";
|
OrganizationIcon,
|
||||||
|
} from '@modrinth/assets'
|
||||||
import {
|
import {
|
||||||
type ExtendedReport,
|
type ExtendedReport,
|
||||||
reportQuickReplies,
|
reportQuickReplies,
|
||||||
type ReportQuickReply,
|
type ReportQuickReply,
|
||||||
} from "@modrinth/moderation";
|
} from '@modrinth/moderation'
|
||||||
import ChevronDownIcon from "../servers/icons/ChevronDownIcon.vue";
|
import {
|
||||||
import ReportThread from "../thread/ReportThread.vue";
|
Avatar,
|
||||||
|
ButtonStyled,
|
||||||
|
CollapsibleRegion,
|
||||||
|
OverflowMenu,
|
||||||
|
type OverflowMenuOption,
|
||||||
|
useRelativeTime,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
|
import ChevronDownIcon from '../servers/icons/ChevronDownIcon.vue'
|
||||||
|
import ReportThread from '../thread/ReportThread.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
report: ExtendedReport;
|
report: ExtendedReport
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const reportThread = ref<InstanceType<typeof ReportThread> | null>(null);
|
const reportThread = ref<InstanceType<typeof ReportThread> | null>(null)
|
||||||
const collapsibleRegion = ref<InstanceType<typeof CollapsibleRegion> | null>(null);
|
const collapsibleRegion = ref<InstanceType<typeof CollapsibleRegion> | null>(null)
|
||||||
|
|
||||||
const formatRelativeTime = useRelativeTime();
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
|
||||||
function updateThread(newThread: any) {
|
function updateThread(newThread: any) {
|
||||||
if (props.report.thread) {
|
if (props.report.thread) {
|
||||||
Object.assign(props.report.thread, newThread);
|
Object.assign(props.report.thread, newThread)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const quickActions: OverflowMenuOption[] = [
|
const quickActions: OverflowMenuOption[] = [
|
||||||
{
|
{
|
||||||
id: "copy-link",
|
id: 'copy-link',
|
||||||
action: () => {
|
action: () => {
|
||||||
const base = window.location.origin;
|
const base = window.location.origin
|
||||||
const reportUrl = `${base}/moderation/reports/${props.report.id}`;
|
const reportUrl = `${base}/moderation/reports/${props.report.id}`
|
||||||
navigator.clipboard.writeText(reportUrl).then(() => {
|
navigator.clipboard.writeText(reportUrl).then(() => {
|
||||||
addNotification({
|
addNotification({
|
||||||
type: "success",
|
type: 'success',
|
||||||
title: "Report link copied",
|
title: 'Report link copied',
|
||||||
text: "The link to this report has been copied to your clipboard.",
|
text: 'The link to this report has been copied to your clipboard.',
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "copy-id",
|
id: 'copy-id',
|
||||||
action: () => {
|
action: () => {
|
||||||
navigator.clipboard.writeText(props.report.id).then(() => {
|
navigator.clipboard.writeText(props.report.id).then(() => {
|
||||||
addNotification({
|
addNotification({
|
||||||
type: "success",
|
type: 'success',
|
||||||
title: "Report ID copied",
|
title: 'Report ID copied',
|
||||||
text: "The ID of this report has been copied to your clipboard.",
|
text: 'The ID of this report has been copied to your clipboard.',
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
|
|
||||||
const visibleQuickReplies = computed<OverflowMenuOption[]>(() => {
|
const visibleQuickReplies = computed<OverflowMenuOption[]>(() => {
|
||||||
return reportQuickReplies
|
return reportQuickReplies
|
||||||
.filter((reply) => {
|
.filter((reply) => {
|
||||||
if (reply.shouldShow === undefined) return true;
|
if (reply.shouldShow === undefined) return true
|
||||||
if (typeof reply.shouldShow === "function") {
|
if (typeof reply.shouldShow === 'function') {
|
||||||
return reply.shouldShow(props.report);
|
return reply.shouldShow(props.report)
|
||||||
}
|
}
|
||||||
|
|
||||||
return reply.shouldShow;
|
return reply.shouldShow
|
||||||
})
|
})
|
||||||
.map(
|
.map(
|
||||||
(reply) =>
|
(reply) =>
|
||||||
@ -217,59 +218,61 @@ const visibleQuickReplies = computed<OverflowMenuOption[]>(() => {
|
|||||||
id: reply.label,
|
id: reply.label,
|
||||||
action: () => handleQuickReply(reply),
|
action: () => handleQuickReply(reply),
|
||||||
}) as OverflowMenuOption,
|
}) as OverflowMenuOption,
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
|
|
||||||
async function handleQuickReply(reply: ReportQuickReply) {
|
async function handleQuickReply(reply: ReportQuickReply) {
|
||||||
const message =
|
const message =
|
||||||
typeof reply.message === "function" ? await reply.message(props.report) : reply.message;
|
typeof reply.message === 'function' ? await reply.message(props.report) : reply.message
|
||||||
|
|
||||||
collapsibleRegion.value?.setCollapsed(false);
|
collapsibleRegion.value?.setCollapsed(false)
|
||||||
await nextTick();
|
await nextTick()
|
||||||
reportThread.value?.setReplyContent(message);
|
reportThread.value?.setReplyContent(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
const reportItemAvatarUrl = computed(() => {
|
const reportItemAvatarUrl = computed(() => {
|
||||||
switch (props.report.item_type) {
|
switch (props.report.item_type) {
|
||||||
case "project":
|
case 'project':
|
||||||
case "version":
|
case 'version':
|
||||||
return props.report.project?.icon_url || "";
|
return props.report.project?.icon_url || ''
|
||||||
case "user":
|
case 'user':
|
||||||
return props.report.user?.avatar_url || "";
|
return props.report.user?.avatar_url || ''
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
const reportItemTitle = computed(() => {
|
const reportItemTitle = computed(() => {
|
||||||
if (props.report.item_type === "user") return props.report.user?.username || "Unknown User";
|
if (props.report.item_type === 'user') return props.report.user?.username || 'Unknown User'
|
||||||
|
|
||||||
return props.report.project?.title || "Unknown Project";
|
return props.report.project?.title || 'Unknown Project'
|
||||||
});
|
})
|
||||||
|
|
||||||
const reportItemUrl = computed(() => {
|
const reportItemUrl = computed(() => {
|
||||||
switch (props.report.item_type) {
|
switch (props.report.item_type) {
|
||||||
case "user":
|
case 'user':
|
||||||
return `/user/${props.report.user?.username}`;
|
return `/user/${props.report.user?.username}`
|
||||||
case "project":
|
case 'project':
|
||||||
return `/${props.report.project?.project_type}/${props.report.project?.slug}`;
|
return `/${props.report.project?.project_type}/${props.report.project?.slug}`
|
||||||
case "version":
|
case 'version':
|
||||||
return `/${props.report.project?.project_type}/${props.report.project?.slug}/versions/${props.report.version?.id}`;
|
return `/${props.report.project?.project_type}/${props.report.project?.slug}/versions/${props.report.version?.id}`
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
const formattedItemType = computed(() => {
|
const formattedItemType = computed(() => {
|
||||||
const itemType = props.report.item_type;
|
const itemType = props.report.item_type
|
||||||
return itemType.charAt(0).toUpperCase() + itemType.slice(1);
|
return itemType.charAt(0).toUpperCase() + itemType.slice(1)
|
||||||
});
|
})
|
||||||
|
|
||||||
const formattedReportType = computed(() => {
|
const formattedReportType = computed(() => {
|
||||||
const reportType = props.report.report_type;
|
const reportType = props.report.report_type
|
||||||
|
|
||||||
// some are split by -, some are split by " "
|
// some are split by -, some are split by " "
|
||||||
const words = reportType.includes("-") ? reportType.split("-") : reportType.split(" ");
|
const words = reportType.includes('-') ? reportType.split('-') : reportType.split(' ')
|
||||||
return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
|||||||
@ -29,9 +29,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { type KeybindListener, keybinds, normalizeKeybind } from "@modrinth/moderation";
|
||||||
import NewModal from "@modrinth/ui/src/components/modal/NewModal.vue";
|
import NewModal from "@modrinth/ui/src/components/modal/NewModal.vue";
|
||||||
import { keybinds, type KeybindListener, normalizeKeybind } from "@modrinth/moderation";
|
import { ref } from "vue";
|
||||||
|
|
||||||
const modal = ref<InstanceType<typeof NewModal>>();
|
const modal = ref<InstanceType<typeof NewModal>>();
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -146,18 +146,18 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { LeftArrowIcon, RightArrowIcon } from "@modrinth/assets";
|
import { LeftArrowIcon, RightArrowIcon } from "@modrinth/assets";
|
||||||
|
import { ButtonStyled } from "@modrinth/ui";
|
||||||
import type {
|
import type {
|
||||||
|
ModerationFlameModpackItem,
|
||||||
ModerationJudgements,
|
ModerationJudgements,
|
||||||
ModerationModpackItem,
|
ModerationModpackItem,
|
||||||
ModerationModpackResponse,
|
|
||||||
ModerationUnknownModpackItem,
|
|
||||||
ModerationFlameModpackItem,
|
|
||||||
ModerationModpackPermissionApprovalType,
|
ModerationModpackPermissionApprovalType,
|
||||||
|
ModerationModpackResponse,
|
||||||
ModerationPermissionType,
|
ModerationPermissionType,
|
||||||
|
ModerationUnknownModpackItem,
|
||||||
} from "@modrinth/utils";
|
} from "@modrinth/utils";
|
||||||
import { ButtonStyled } from "@modrinth/ui";
|
|
||||||
import { ref, computed, watch, onMounted } from "vue";
|
|
||||||
import { useLocalStorage, useSessionStorage } from "@vueuse/core";
|
import { useLocalStorage, useSessionStorage } from "@vueuse/core";
|
||||||
|
import { computed, onMounted,ref, watch } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { computed, defineComponent, h, onBeforeUnmount, ref, watch } from "vue";
|
import { computed, defineComponent, h, onBeforeUnmount, ref, watch } from "vue";
|
||||||
|
|
||||||
import { startLoading, stopLoading, useNuxtApp } from "#imports";
|
import { startLoading, stopLoading, useNuxtApp } from "#imports";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
import { NewspaperIcon } from "@modrinth/assets";
|
import { NewspaperIcon } from "@modrinth/assets";
|
||||||
import { articles as rawArticles } from "@modrinth/blog";
|
import { articles as rawArticles } from "@modrinth/blog";
|
||||||
import { ButtonStyled, NewsArticleCard } from "@modrinth/ui";
|
import { ButtonStyled, NewsArticleCard } from "@modrinth/ui";
|
||||||
import { ref, computed } from "vue";
|
import { computed,ref } from "vue";
|
||||||
|
|
||||||
const articles = ref(
|
const articles = ref(
|
||||||
rawArticles
|
rawArticles
|
||||||
|
|||||||
@ -106,8 +106,9 @@
|
|||||||
import { ReportIcon, UnknownIcon, VersionIcon } from "@modrinth/assets";
|
import { ReportIcon, UnknownIcon, VersionIcon } from "@modrinth/assets";
|
||||||
import { Avatar, Badge, CopyCode, useRelativeTime } from "@modrinth/ui";
|
import { Avatar, Badge, CopyCode, useRelativeTime } from "@modrinth/ui";
|
||||||
import { formatProjectType } from "@modrinth/utils";
|
import { formatProjectType } from "@modrinth/utils";
|
||||||
import { renderHighlightedString } from "~/helpers/highlight.js";
|
|
||||||
import ThreadSummary from "~/components/ui/thread/ThreadSummary.vue";
|
import ThreadSummary from "~/components/ui/thread/ThreadSummary.vue";
|
||||||
|
import { renderHighlightedString } from "~/helpers/highlight.js";
|
||||||
import { getProjectTypeForUrl } from "~/helpers/projects.js";
|
import { getProjectTypeForUrl } from "~/helpers/projects.js";
|
||||||
|
|
||||||
const formatRelativeTime = useRelativeTime();
|
const formatRelativeTime = useRelativeTime();
|
||||||
|
|||||||
@ -22,8 +22,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
|
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
|
||||||
import ConversationThread from "~/components/ui/thread/ConversationThread.vue";
|
|
||||||
import ReportInfo from "~/components/ui/report/ReportInfo.vue";
|
import ReportInfo from "~/components/ui/report/ReportInfo.vue";
|
||||||
|
import ConversationThread from "~/components/ui/thread/ConversationThread.vue";
|
||||||
import { addReportMessage } from "~/helpers/threads.js";
|
import { addReportMessage } from "~/helpers/threads.js";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Chips } from "@modrinth/ui";
|
import { Chips } from "@modrinth/ui";
|
||||||
|
|
||||||
import ReportInfo from "~/components/ui/report/ReportInfo.vue";
|
import ReportInfo from "~/components/ui/report/ReportInfo.vue";
|
||||||
import { addReportMessage } from "~/helpers/threads.js";
|
import { addReportMessage } from "~/helpers/threads.js";
|
||||||
import { asEncodedJsonArray, fetchSegmented } from "~/utils/fetch-helpers.ts";
|
import { asEncodedJsonArray, fetchSegmented } from "~/utils/fetch-helpers.ts";
|
||||||
|
|||||||
@ -42,11 +42,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, nextTick, computed } from "vue";
|
|
||||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
|
||||||
import { IssuesIcon, PlusIcon, XIcon } from "@modrinth/assets";
|
import { IssuesIcon, PlusIcon, XIcon } from "@modrinth/assets";
|
||||||
|
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
import { ModrinthServersFetchError, type ServerBackup } from "@modrinth/utils";
|
import { ModrinthServersFetchError, type ServerBackup } from "@modrinth/utils";
|
||||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
import { computed,nextTick, ref } from "vue";
|
||||||
|
|
||||||
|
import type { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: ModrinthServer;
|
server: ModrinthServer;
|
||||||
|
|||||||
@ -18,9 +18,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
|
||||||
import { ConfirmModal } from "@modrinth/ui";
|
import { ConfirmModal } from "@modrinth/ui";
|
||||||
import type { Backup } from "@modrinth/utils";
|
import type { Backup } from "@modrinth/utils";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import BackupItem from "~/components/ui/servers/BackupItem.vue";
|
import BackupItem from "~/components/ui/servers/BackupItem.vue";
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import dayjs from "dayjs";
|
|
||||||
import {
|
import {
|
||||||
MoreVerticalIcon,
|
|
||||||
HistoryIcon,
|
|
||||||
DownloadIcon,
|
|
||||||
SpinnerIcon,
|
|
||||||
EditIcon,
|
|
||||||
LockIcon,
|
|
||||||
TrashIcon,
|
|
||||||
FolderArchiveIcon,
|
|
||||||
BotIcon,
|
BotIcon,
|
||||||
XIcon,
|
DownloadIcon,
|
||||||
|
EditIcon,
|
||||||
|
FolderArchiveIcon,
|
||||||
|
HistoryIcon,
|
||||||
|
LockIcon,
|
||||||
LockOpenIcon,
|
LockOpenIcon,
|
||||||
|
MoreVerticalIcon,
|
||||||
RotateCounterClockwiseIcon,
|
RotateCounterClockwiseIcon,
|
||||||
|
SpinnerIcon,
|
||||||
|
TrashIcon,
|
||||||
|
XIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { ButtonStyled, commonMessages, OverflowMenu, ProgressBar } from "@modrinth/ui";
|
import { ButtonStyled, commonMessages, OverflowMenu, ProgressBar } from "@modrinth/ui";
|
||||||
import { defineMessages, useVIntl } from "@vintl/vintl";
|
|
||||||
import { ref, computed } from "vue";
|
|
||||||
import type { Backup } from "@modrinth/utils";
|
import type { Backup } from "@modrinth/utils";
|
||||||
|
import { defineMessages, useVIntl } from "@vintl/vintl";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { computed,ref } from "vue";
|
||||||
|
|
||||||
const flags = useFeatureFlags();
|
const flags = useFeatureFlags();
|
||||||
const { formatMessage } = useVIntl();
|
const { formatMessage } = useVIntl();
|
||||||
|
|||||||
@ -45,11 +45,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, nextTick, computed } from "vue";
|
import { IssuesIcon,SaveIcon, SpinnerIcon, XIcon } from "@modrinth/assets";
|
||||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
import { SpinnerIcon, SaveIcon, XIcon, IssuesIcon } from "@modrinth/assets";
|
|
||||||
import type { Backup } from "@modrinth/utils";
|
import type { Backup } from "@modrinth/utils";
|
||||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
import { computed,nextTick, ref } from "vue";
|
||||||
|
|
||||||
|
import type { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: ModrinthServer;
|
server: ModrinthServer;
|
||||||
|
|||||||
@ -17,11 +17,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import type { NewModal } from "@modrinth/ui";
|
||||||
import { ConfirmModal, NewModal } from "@modrinth/ui";
|
import { ConfirmModal } from "@modrinth/ui";
|
||||||
import type { Backup } from "@modrinth/utils";
|
import type { Backup } from "@modrinth/utils";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import BackupItem from "~/components/ui/servers/BackupItem.vue";
|
import BackupItem from "~/components/ui/servers/BackupItem.vue";
|
||||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
import type { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: ModrinthServer;
|
server: ModrinthServer;
|
||||||
|
|||||||
@ -56,10 +56,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SaveIcon,XIcon } from "@modrinth/assets";
|
||||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
import { XIcon, SaveIcon } from "@modrinth/assets";
|
import { computed,ref } from "vue";
|
||||||
import { ref, computed } from "vue";
|
|
||||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
import type { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: ModrinthServer;
|
server: ModrinthServer;
|
||||||
|
|||||||
@ -229,17 +229,18 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
DropdownIcon,
|
|
||||||
XIcon,
|
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
LockOpenIcon,
|
DropdownIcon,
|
||||||
GameIcon,
|
|
||||||
ExternalIcon,
|
ExternalIcon,
|
||||||
|
GameIcon,
|
||||||
|
LockOpenIcon,
|
||||||
|
XIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { Admonition, Avatar, ButtonStyled, CopyCode, NewModal } from "@modrinth/ui";
|
import { Admonition, Avatar, ButtonStyled, CopyCode, NewModal } from "@modrinth/ui";
|
||||||
import TagItem from "@modrinth/ui/src/components/base/TagItem.vue";
|
import TagItem from "@modrinth/ui/src/components/base/TagItem.vue";
|
||||||
import { ref, computed } from "vue";
|
|
||||||
import { formatCategory, formatVersionsForDisplay, type Mod, type Version } from "@modrinth/utils";
|
import { formatCategory, formatVersionsForDisplay, type Mod, type Version } from "@modrinth/utils";
|
||||||
|
import { computed,ref } from "vue";
|
||||||
|
|
||||||
import Accordion from "~/components/ui/Accordion.vue";
|
import Accordion from "~/components/ui/Accordion.vue";
|
||||||
import Checkbox from "~/components/ui/Checkbox.vue";
|
import Checkbox from "~/components/ui/Checkbox.vue";
|
||||||
import ContentVersionFilter, {
|
import ContentVersionFilter, {
|
||||||
|
|||||||
@ -58,11 +58,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FilterIcon } from "@modrinth/assets";
|
import { FilterIcon } from "@modrinth/assets";
|
||||||
import { type Version, formatCategory, type GameVersionTag } from "@modrinth/utils";
|
|
||||||
import { ref, computed } from "vue";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
import ManySelect from "@modrinth/ui/src/components/base/ManySelect.vue";
|
|
||||||
import Checkbox from "@modrinth/ui/src/components/base/Checkbox.vue";
|
import Checkbox from "@modrinth/ui/src/components/base/Checkbox.vue";
|
||||||
|
import ManySelect from "@modrinth/ui/src/components/base/ManySelect.vue";
|
||||||
|
import { formatCategory, type GameVersionTag,type Version } from "@modrinth/utils";
|
||||||
|
import { computed,ref } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
export type ListedGameVersion = {
|
export type ListedGameVersion = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@ -65,27 +65,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonStyled } from "@modrinth/ui";
|
|
||||||
import {
|
import {
|
||||||
MoreHorizontalIcon,
|
|
||||||
EditIcon,
|
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
TrashIcon,
|
EditIcon,
|
||||||
FolderOpenIcon,
|
|
||||||
FileIcon,
|
|
||||||
RightArrowIcon,
|
|
||||||
PackageOpenIcon,
|
|
||||||
FileArchiveIcon,
|
FileArchiveIcon,
|
||||||
|
FileIcon,
|
||||||
|
FolderOpenIcon,
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
PackageOpenIcon,
|
||||||
|
RightArrowIcon,
|
||||||
|
TrashIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { computed, shallowRef, ref } from "vue";
|
import { ButtonStyled } from "@modrinth/ui";
|
||||||
|
import { computed, ref,shallowRef } from "vue";
|
||||||
import { renderToString } from "vue/server-renderer";
|
import { renderToString } from "vue/server-renderer";
|
||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRoute,useRouter } from "vue-router";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
UiServersIconsCodeFileIcon,
|
||||||
UiServersIconsCogFolderIcon,
|
UiServersIconsCogFolderIcon,
|
||||||
UiServersIconsEarthIcon,
|
UiServersIconsEarthIcon,
|
||||||
UiServersIconsCodeFileIcon,
|
|
||||||
UiServersIconsTextFileIcon,
|
|
||||||
UiServersIconsImageFileIcon,
|
UiServersIconsImageFileIcon,
|
||||||
|
UiServersIconsTextFileIcon,
|
||||||
} from "#components";
|
} from "#components";
|
||||||
import PaletteIcon from "~/assets/icons/palette.svg?component";
|
import PaletteIcon from "~/assets/icons/palette.svg?component";
|
||||||
|
|
||||||
|
|||||||
@ -25,16 +25,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FileIcon, HomeIcon } from "@modrinth/assets";
|
import { FileIcon, HomeIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled } from "@modrinth/ui";
|
import { ButtonStyled } from '@modrinth/ui'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
title: string;
|
title: string
|
||||||
message: string;
|
message: string
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(e: "refetch"): void;
|
(e: 'refetch' | 'home'): void
|
||||||
(e: "home"): void;
|
}>()
|
||||||
}>();
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
import { computed, onMounted, onUnmounted,ref } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
items: any[];
|
items: any[];
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
}"
|
}"
|
||||||
@click="$emit('navigate', index)"
|
@click="$emit('navigate', index)"
|
||||||
>
|
>
|
||||||
{{ segment || "" }}
|
{{ segment || '' }}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
@ -154,60 +154,59 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
LinkIcon,
|
|
||||||
CurseForgeIcon,
|
|
||||||
FileArchiveIcon,
|
|
||||||
BoxIcon,
|
BoxIcon,
|
||||||
PlusIcon,
|
|
||||||
UploadIcon,
|
|
||||||
DropdownIcon,
|
|
||||||
FolderOpenIcon,
|
|
||||||
SearchIcon,
|
|
||||||
HomeIcon,
|
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
|
CurseForgeIcon,
|
||||||
|
DropdownIcon,
|
||||||
|
FileArchiveIcon,
|
||||||
FilterIcon,
|
FilterIcon,
|
||||||
} from "@modrinth/assets";
|
FolderOpenIcon,
|
||||||
import { ButtonStyled, OverflowMenu } from "@modrinth/ui";
|
HomeIcon,
|
||||||
import { ref, computed } from "vue";
|
LinkIcon,
|
||||||
import { useIntersectionObserver } from "@vueuse/core";
|
PlusIcon,
|
||||||
|
SearchIcon,
|
||||||
|
UploadIcon,
|
||||||
|
} from '@modrinth/assets'
|
||||||
|
import { ButtonStyled, OverflowMenu } from '@modrinth/ui'
|
||||||
|
import { useIntersectionObserver } from '@vueuse/core'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
breadcrumbSegments: string[];
|
breadcrumbSegments: string[]
|
||||||
searchQuery: string;
|
searchQuery: string
|
||||||
currentFilter: string;
|
currentFilter: string
|
||||||
baseId: string;
|
baseId: string
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(e: "navigate", index: number): void;
|
(e: 'navigate', index: number): void
|
||||||
(e: "create", type: "file" | "directory"): void;
|
(e: 'create', type: 'file' | 'directory'): void
|
||||||
(e: "upload" | "upload-zip"): void;
|
(e: 'upload' | 'upload-zip'): void
|
||||||
(e: "unzip-from-url", cf: boolean): void;
|
(e: 'unzip-from-url', cf: boolean): void
|
||||||
(e: "update:searchQuery", value: string): void;
|
(e: 'update:searchQuery' | 'filter', value: string): void
|
||||||
(e: "filter", type: string): void;
|
}>()
|
||||||
}>();
|
|
||||||
|
|
||||||
const pyroFilesSentinel = ref<HTMLElement | null>(null);
|
const pyroFilesSentinel = ref<HTMLElement | null>(null)
|
||||||
const isStuck = ref(false);
|
const isStuck = ref(false)
|
||||||
|
|
||||||
useIntersectionObserver(
|
useIntersectionObserver(
|
||||||
pyroFilesSentinel,
|
pyroFilesSentinel,
|
||||||
([{ isIntersecting }]) => {
|
([{ isIntersecting }]) => {
|
||||||
isStuck.value = !isIntersecting;
|
isStuck.value = !isIntersecting
|
||||||
},
|
},
|
||||||
{ threshold: [0, 1] },
|
{ threshold: [0, 1] },
|
||||||
);
|
)
|
||||||
|
|
||||||
const filterLabel = computed(() => {
|
const filterLabel = computed(() => {
|
||||||
switch (props.currentFilter) {
|
switch (props.currentFilter) {
|
||||||
case "filesOnly":
|
case 'filesOnly':
|
||||||
return "Files only";
|
return 'Files only'
|
||||||
case "foldersOnly":
|
case 'foldersOnly':
|
||||||
return "Folders only";
|
return 'Folders only'
|
||||||
default:
|
default:
|
||||||
return "Show all";
|
return 'Show all'
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -55,33 +55,30 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { EditIcon, DownloadIcon, TrashIcon, RightArrowIcon } from "@modrinth/assets";
|
import { DownloadIcon, EditIcon, RightArrowIcon, TrashIcon } from '@modrinth/assets'
|
||||||
|
|
||||||
interface FileItem {
|
interface FileItem {
|
||||||
type: string;
|
type: string
|
||||||
name: string;
|
name: string
|
||||||
[key: string]: any;
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
item: FileItem | null;
|
item: FileItem | null
|
||||||
x: number;
|
x: number
|
||||||
y: number;
|
y: number
|
||||||
isAtBottom: boolean;
|
isAtBottom: boolean
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const ctxRef = ref<HTMLElement | null>(null);
|
const ctxRef = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(e: "rename", item: FileItem): void;
|
(e: 'rename' | 'move' | 'download' | 'delete', item: FileItem): void
|
||||||
(e: "move", item: FileItem): void;
|
}>()
|
||||||
(e: "download", item: FileItem): void;
|
|
||||||
(e: "delete", item: FileItem): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
ctxRef,
|
ctxRef,
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -35,7 +35,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PlusIcon, XIcon } from "@modrinth/assets";
|
import { PlusIcon, XIcon } from "@modrinth/assets";
|
||||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
import { ref, computed, nextTick } from "vue";
|
import { computed, nextTick,ref } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
type: "file" | "directory";
|
type: "file" | "directory";
|
||||||
|
|||||||
@ -42,8 +42,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
|
||||||
import { FileIcon, FolderOpenIcon, TrashIcon, XIcon } from "@modrinth/assets";
|
import { FileIcon, FolderOpenIcon, TrashIcon, XIcon } from "@modrinth/assets";
|
||||||
|
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
item: {
|
item: {
|
||||||
|
|||||||
@ -39,7 +39,7 @@
|
|||||||
:class="{ '!text-contrast': index === breadcrumbSegments.length - 1 }"
|
:class="{ '!text-contrast': index === breadcrumbSegments.length - 1 }"
|
||||||
@click="$emit('navigate', index)"
|
@click="$emit('navigate', index)"
|
||||||
>
|
>
|
||||||
{{ segment || "" }}
|
{{ segment || '' }}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
@ -105,36 +105,32 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DropdownIcon, SaveIcon, ShareIcon, HomeIcon, ChevronRightIcon } from "@modrinth/assets";
|
import { ChevronRightIcon, DropdownIcon, HomeIcon, SaveIcon, ShareIcon } from '@modrinth/assets'
|
||||||
import { Button, ButtonStyled } from "@modrinth/ui";
|
import { Button, ButtonStyled } from '@modrinth/ui'
|
||||||
import { computed } from "vue";
|
import { computed } from 'vue'
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
breadcrumbSegments: string[];
|
breadcrumbSegments: string[]
|
||||||
fileName?: string;
|
fileName?: string
|
||||||
isImage: boolean;
|
isImage: boolean
|
||||||
filePath?: string;
|
filePath?: string
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const isLogFile = computed(() => {
|
const isLogFile = computed(() => {
|
||||||
return props.filePath?.startsWith("logs") || props.filePath?.endsWith(".log");
|
return props.filePath?.startsWith('logs') || props.filePath?.endsWith('.log')
|
||||||
});
|
})
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute()
|
||||||
const router = useRouter();
|
const router = useRouter()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "cancel"): void;
|
(e: 'cancel' | 'save' | 'save-as' | 'save-restart' | 'share'): void
|
||||||
(e: "save"): void;
|
(e: 'navigate', index: number): void
|
||||||
(e: "save-as"): void;
|
}>()
|
||||||
(e: "save-restart"): void;
|
|
||||||
(e: "share"): void;
|
|
||||||
(e: "navigate", index: number): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const goHome = () => {
|
const goHome = () => {
|
||||||
emit("cancel");
|
emit('cancel')
|
||||||
router.push({ path: "/servers/manage/" + route.params.id + "/files" });
|
router.push({ path: '/servers/manage/' + route.params.id + '/files' })
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -53,9 +53,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
|
|
||||||
import { ZoomInIcon, ZoomOutIcon } from "@modrinth/assets";
|
import { ZoomInIcon, ZoomOutIcon } from "@modrinth/assets";
|
||||||
import { ButtonStyled } from "@modrinth/ui";
|
import { ButtonStyled } from "@modrinth/ui";
|
||||||
|
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
||||||
|
|
||||||
const ZOOM_MIN = 0.1;
|
const ZOOM_MIN = 0.1;
|
||||||
const ZOOM_MAX = 5;
|
const ZOOM_MAX = 5;
|
||||||
|
|||||||
@ -39,7 +39,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ArrowBigUpDashIcon, XIcon } from "@modrinth/assets";
|
import { ArrowBigUpDashIcon, XIcon } from "@modrinth/assets";
|
||||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
import { ref, nextTick, computed } from "vue";
|
import { computed,nextTick, ref } from "vue";
|
||||||
|
|
||||||
const destinationInput = ref<HTMLInputElement | null>(null);
|
const destinationInput = ref<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { EditIcon, XIcon } from "@modrinth/assets";
|
import { EditIcon, XIcon } from "@modrinth/assets";
|
||||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
import { ref, computed, nextTick } from "vue";
|
import { computed, nextTick,ref } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: { name: string; type: string } | null;
|
item: { name: string; type: string } | null;
|
||||||
|
|||||||
@ -27,9 +27,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { CheckIcon,XIcon } from "@modrinth/assets";
|
||||||
import { ConfirmModal } from "@modrinth/ui";
|
import { ConfirmModal } from "@modrinth/ui";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { XIcon, CheckIcon } from "@modrinth/assets";
|
|
||||||
|
|
||||||
const path = ref("");
|
const path = ref("");
|
||||||
const files = ref<string[]>([]);
|
const files = ref<string[]>([]);
|
||||||
|
|||||||
@ -101,10 +101,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FolderOpenIcon, CheckCircleIcon, XCircleIcon } from "@modrinth/assets";
|
import { CheckCircleIcon, FolderOpenIcon, XCircleIcon } from "@modrinth/assets";
|
||||||
import { ButtonStyled } from "@modrinth/ui";
|
import { ButtonStyled } from "@modrinth/ui";
|
||||||
import { ref, computed, watch, nextTick } from "vue";
|
import { computed, nextTick,ref, watch } from "vue";
|
||||||
import { FSModule } from "~/composables/servers/modules/fs.ts";
|
|
||||||
|
import type { FSModule } from "~/composables/servers/modules/fs.ts";
|
||||||
|
|
||||||
interface UploadItem {
|
interface UploadItem {
|
||||||
file: File;
|
file: File;
|
||||||
@ -152,7 +153,7 @@ const activeUploads = computed(() =>
|
|||||||
const onUploadStatusEnter = (el: Element) => {
|
const onUploadStatusEnter = (el: Element) => {
|
||||||
const height = (el as HTMLElement).scrollHeight + (props.marginBottom || 0);
|
const height = (el as HTMLElement).scrollHeight + (props.marginBottom || 0);
|
||||||
(el as HTMLElement).style.height = "0";
|
(el as HTMLElement).style.height = "0";
|
||||||
// eslint-disable-next-line no-void
|
|
||||||
void (el as HTMLElement).offsetHeight;
|
void (el as HTMLElement).offsetHeight;
|
||||||
(el as HTMLElement).style.height = `${height}px`;
|
(el as HTMLElement).style.height = `${height}px`;
|
||||||
};
|
};
|
||||||
@ -160,7 +161,7 @@ const onUploadStatusEnter = (el: Element) => {
|
|||||||
const onUploadStatusLeave = (el: Element) => {
|
const onUploadStatusLeave = (el: Element) => {
|
||||||
const height = (el as HTMLElement).scrollHeight + (props.marginBottom || 0);
|
const height = (el as HTMLElement).scrollHeight + (props.marginBottom || 0);
|
||||||
(el as HTMLElement).style.height = `${height}px`;
|
(el as HTMLElement).style.height = `${height}px`;
|
||||||
// eslint-disable-next-line no-void
|
|
||||||
void (el as HTMLElement).offsetHeight;
|
void (el as HTMLElement).offsetHeight;
|
||||||
(el as HTMLElement).style.height = "0";
|
(el as HTMLElement).style.height = "0";
|
||||||
};
|
};
|
||||||
|
|||||||
@ -73,11 +73,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ExternalIcon, SpinnerIcon, DownloadIcon, XIcon } from "@modrinth/assets";
|
import { DownloadIcon, ExternalIcon, SpinnerIcon, XIcon } from "@modrinth/assets";
|
||||||
import { BackupWarning, ButtonStyled, NewModal } from "@modrinth/ui";
|
import { BackupWarning, ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
import { ModrinthServersFetchError } from "@modrinth/utils";
|
import { ModrinthServersFetchError } from "@modrinth/utils";
|
||||||
import { ref, computed, nextTick } from "vue";
|
import { computed, nextTick,ref } from "vue";
|
||||||
import { handleError, ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
|
||||||
|
import type { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||||
|
import { handleError } from "~/composables/servers/modrinth-servers.ts";
|
||||||
|
|
||||||
const cf = ref(false);
|
const cf = ref(false);
|
||||||
|
|
||||||
|
|||||||
@ -44,7 +44,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { onMounted, onUnmounted,ref } from "vue";
|
||||||
|
|
||||||
const container = ref(null);
|
const container = ref(null);
|
||||||
const showLabels = ref(false);
|
const showLabels = ref(false);
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { onMounted, onUnmounted,ref } from "vue";
|
||||||
|
|
||||||
const msgs = [
|
const msgs = [
|
||||||
"Organizing files...",
|
"Organizing files...",
|
||||||
|
|||||||
@ -19,9 +19,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
|
||||||
import Convert from "ansi-to-html";
|
import Convert from "ansi-to-html";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
|
import { computed, onMounted, onUnmounted,ref } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
log: string;
|
log: string;
|
||||||
|
|||||||
@ -104,23 +104,23 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from "vue";
|
|
||||||
import {
|
import {
|
||||||
PlayIcon,
|
|
||||||
UpdatedIcon,
|
|
||||||
StopCircleIcon,
|
|
||||||
SlashIcon,
|
|
||||||
XIcon,
|
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
ServerIcon,
|
ClipboardCopyIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
MoreVerticalIcon,
|
MoreVerticalIcon,
|
||||||
ClipboardCopyIcon,
|
PlayIcon,
|
||||||
|
ServerIcon,
|
||||||
|
SlashIcon,
|
||||||
|
StopCircleIcon,
|
||||||
|
UpdatedIcon,
|
||||||
|
XIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
import { useStorage } from "@vueuse/core";
|
|
||||||
import type { PowerAction as ServerPowerAction, ServerState } from "@modrinth/utils";
|
import type { PowerAction as ServerPowerAction, ServerState } from "@modrinth/utils";
|
||||||
|
import { useStorage } from "@vueuse/core";
|
||||||
|
import { computed,ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
const flags = useFeatureFlags();
|
const flags = useFeatureFlags();
|
||||||
|
|
||||||
|
|||||||
@ -39,8 +39,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
|
||||||
import type { ServerState } from "@modrinth/utils";
|
import type { ServerState } from "@modrinth/utils";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
const STATUS_CLASSES = {
|
const STATUS_CLASSES = {
|
||||||
running: { main: "bg-brand", bg: "bg-bg-green" },
|
running: { main: "bg-brand", bg: "bg-bg-green" },
|
||||||
|
|||||||
@ -295,12 +295,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RightArrowIcon, CopyIcon, XIcon, SearchIcon, EyeIcon } from "@modrinth/assets";
|
import { CopyIcon, EyeIcon,RightArrowIcon, SearchIcon, XIcon } from "@modrinth/assets";
|
||||||
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from "vue";
|
|
||||||
import { useDebounceFn } from "@vueuse/core";
|
|
||||||
import { NewModal } from "@modrinth/ui";
|
import { NewModal } from "@modrinth/ui";
|
||||||
import ButtonStyled from "@modrinth/ui/src/components/base/ButtonStyled.vue";
|
import ButtonStyled from "@modrinth/ui/src/components/base/ButtonStyled.vue";
|
||||||
|
import { useDebounceFn } from "@vueuse/core";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
|
import { computed, nextTick,onMounted, onUnmounted, ref, watch } from "vue";
|
||||||
|
|
||||||
import { useModrinthServersConsole } from "~/store/console.ts";
|
import { useModrinthServersConsole } from "~/store/console.ts";
|
||||||
|
|
||||||
const { $cosmetics } = useNuxtApp();
|
const { $cosmetics } = useNuxtApp();
|
||||||
|
|||||||
@ -67,10 +67,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
|
||||||
import { DownloadIcon, XIcon } from "@modrinth/assets";
|
import { DownloadIcon, XIcon } from "@modrinth/assets";
|
||||||
|
import { ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
import { ModrinthServersFetchError } from "@modrinth/utils";
|
import { ModrinthServersFetchError } from "@modrinth/utils";
|
||||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
|
||||||
|
import type { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: ModrinthServer;
|
server: ModrinthServer;
|
||||||
|
|||||||
@ -144,18 +144,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { BackupWarning, ButtonStyled, NewModal } from "@modrinth/ui";
|
|
||||||
import {
|
import {
|
||||||
UploadIcon,
|
|
||||||
RightArrowIcon,
|
|
||||||
XIcon,
|
|
||||||
ServerIcon,
|
|
||||||
ArrowBigRightDashIcon,
|
ArrowBigRightDashIcon,
|
||||||
|
RightArrowIcon,
|
||||||
|
ServerIcon,
|
||||||
|
UploadIcon,
|
||||||
|
XIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
|
import { BackupWarning, ButtonStyled, NewModal } from "@modrinth/ui";
|
||||||
import { formatBytes, ModrinthServersFetchError } from "@modrinth/utils";
|
import { formatBytes, ModrinthServersFetchError } from "@modrinth/utils";
|
||||||
import { onMounted, onUnmounted } from "vue";
|
import { onMounted, onUnmounted } from "vue";
|
||||||
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
|
||||||
import type { ModrinthServer } from "~/composables/servers/modrinth-servers";
|
import type { ModrinthServer } from "~/composables/servers/modrinth-servers";
|
||||||
|
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
||||||
|
|
||||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||||
if (isLoading.value) {
|
if (isLoading.value) {
|
||||||
|
|||||||
@ -197,12 +197,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { BackupWarning, ButtonStyled, NewModal, Toggle } from "@modrinth/ui";
|
|
||||||
import { DropdownIcon, RightArrowIcon, ServerIcon, XIcon } from "@modrinth/assets";
|
import { DropdownIcon, RightArrowIcon, ServerIcon, XIcon } from "@modrinth/assets";
|
||||||
import { $fetch } from "ofetch";
|
import { BackupWarning, ButtonStyled, NewModal, Toggle } from "@modrinth/ui";
|
||||||
import { type Loaders, ModrinthServersFetchError } from "@modrinth/utils";
|
import { type Loaders, ModrinthServersFetchError } from "@modrinth/utils";
|
||||||
|
import { $fetch } from "ofetch";
|
||||||
|
|
||||||
|
import type { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||||
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
||||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
|
||||||
|
|
||||||
const { formatMessage } = useVIntl();
|
const { formatMessage } = useVIntl();
|
||||||
|
|
||||||
@ -337,7 +338,7 @@ const selectedLoaderVersions = computed<string[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const backwardsCompatibleVersion = loaderVersions.value[apiLoader]?.find(
|
const backwardsCompatibleVersion = loaderVersions.value[apiLoader]?.find(
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
|
||||||
(x) => x.id === "${modrinth.gameVersion}",
|
(x) => x.id === "${modrinth.gameVersion}",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonStyled } from "@modrinth/ui";
|
import { ButtonStyled } from "@modrinth/ui";
|
||||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
|
||||||
|
import type { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
isUpdating: boolean;
|
isUpdating: boolean;
|
||||||
|
|||||||
@ -158,31 +158,32 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonStyled, NewProjectCard } from "@modrinth/ui";
|
import { CompassIcon, InfoIcon, SettingsIcon, TransferIcon, UploadIcon } from '@modrinth/assets'
|
||||||
import { TransferIcon, UploadIcon, InfoIcon, CompassIcon, SettingsIcon } from "@modrinth/assets";
|
import { ButtonStyled, NewProjectCard } from '@modrinth/ui'
|
||||||
import type { Loaders } from "@modrinth/utils";
|
import type { Loaders } from '@modrinth/utils'
|
||||||
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
|
||||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
|
||||||
|
|
||||||
const { formatMessage } = useVIntl();
|
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
|
||||||
|
import type { BackupInProgressReason } from '~/pages/servers/manage/[id].vue'
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
server: ModrinthServer;
|
server: ModrinthServer
|
||||||
ignoreCurrentInstallation?: boolean;
|
ignoreCurrentInstallation?: boolean
|
||||||
backupInProgress?: BackupInProgressReason;
|
backupInProgress?: BackupInProgressReason
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
reinstall: [any?];
|
reinstall: [any?]
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const isInstalling = computed(() => props.server.general?.status === "installing");
|
const isInstalling = computed(() => props.server.general?.status === 'installing')
|
||||||
|
|
||||||
const versionSelectModal = ref();
|
const versionSelectModal = ref()
|
||||||
const mrpackModal = ref();
|
const mrpackModal = ref()
|
||||||
const modpackVersionModal = ref();
|
const modpackVersionModal = ref()
|
||||||
|
|
||||||
const data = computed(() => props.server.general);
|
const data = computed(() => props.server.general)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: versions,
|
data: versions,
|
||||||
@ -191,17 +192,17 @@ const {
|
|||||||
} = await useAsyncData(
|
} = await useAsyncData(
|
||||||
`content-loader-versions-${data.value?.upstream?.project_id}`,
|
`content-loader-versions-${data.value?.upstream?.project_id}`,
|
||||||
async () => {
|
async () => {
|
||||||
if (!data.value?.upstream?.project_id) return [];
|
if (!data.value?.upstream?.project_id) return []
|
||||||
try {
|
try {
|
||||||
const result = await useBaseFetch(`project/${data.value.upstream.project_id}/version`);
|
const result = await useBaseFetch(`project/${data.value.upstream.project_id}/version`)
|
||||||
return result || [];
|
return result || []
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("couldnt fetch all versions:", e);
|
console.error('couldnt fetch all versions:', e)
|
||||||
throw new Error("Failed to load modpack versions.");
|
throw new Error('Failed to load modpack versions.')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ default: () => [] },
|
{ default: () => [] },
|
||||||
);
|
)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: currentVersion,
|
data: currentVersion,
|
||||||
@ -210,17 +211,17 @@ const {
|
|||||||
} = await useAsyncData(
|
} = await useAsyncData(
|
||||||
`content-loader-version-${data.value?.upstream?.version_id}`,
|
`content-loader-version-${data.value?.upstream?.version_id}`,
|
||||||
async () => {
|
async () => {
|
||||||
if (!data.value?.upstream?.version_id) return null;
|
if (!data.value?.upstream?.version_id) return null
|
||||||
try {
|
try {
|
||||||
const result = await useBaseFetch(`version/${data.value.upstream.version_id}`);
|
const result = await useBaseFetch(`version/${data.value.upstream.version_id}`)
|
||||||
return result || null;
|
return result || null
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("couldnt fetch version:", e);
|
console.error('couldnt fetch version:', e)
|
||||||
throw new Error("Failed to load modpack version.");
|
throw new Error('Failed to load modpack version.')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ default: () => null },
|
{ default: () => null },
|
||||||
);
|
)
|
||||||
|
|
||||||
const projectCardData = computed(() => ({
|
const projectCardData = computed(() => ({
|
||||||
icon_url: data.value?.project?.icon_url,
|
icon_url: data.value?.project?.icon_url,
|
||||||
@ -228,43 +229,43 @@ const projectCardData = computed(() => ({
|
|||||||
description: data.value?.project?.description,
|
description: data.value?.project?.description,
|
||||||
downloads: data.value?.project?.downloads,
|
downloads: data.value?.project?.downloads,
|
||||||
follows: data.value?.project?.followers,
|
follows: data.value?.project?.followers,
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
date_modified: currentVersion.value?.date_published || data.value?.project?.updated,
|
date_modified: currentVersion.value?.date_published || data.value?.project?.updated,
|
||||||
}));
|
}))
|
||||||
|
|
||||||
const selectLoader = (loader: string) => {
|
const selectLoader = (loader: string) => {
|
||||||
versionSelectModal.value?.show(loader as Loaders);
|
versionSelectModal.value?.show(loader as Loaders)
|
||||||
};
|
}
|
||||||
|
|
||||||
const refreshData = async () => {
|
const refreshData = async () => {
|
||||||
await Promise.all([refreshVersions(), refreshCurrentVersion()]);
|
await Promise.all([refreshVersions(), refreshCurrentVersion()])
|
||||||
};
|
}
|
||||||
|
|
||||||
const updateAvailable = computed(() => {
|
const updateAvailable = computed(() => {
|
||||||
// so sorry
|
// so sorry
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
if (!data.value?.upstream || !versions.value?.length || !currentVersion.value) {
|
if (!data.value?.upstream || !versions.value?.length || !currentVersion.value) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const latestVersion = versions.value[0];
|
const latestVersion = versions.value[0]
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
return latestVersion.id !== currentVersion.value.id;
|
return latestVersion.id !== currentVersion.value.id
|
||||||
});
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.server.general?.status,
|
() => props.server.general?.status,
|
||||||
async (newStatus, oldStatus) => {
|
async (newStatus, oldStatus) => {
|
||||||
if (oldStatus === "installing" && newStatus === "available") {
|
if (oldStatus === 'installing' && newStatus === 'available') {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
refreshVersions(),
|
refreshVersions(),
|
||||||
refreshCurrentVersion(),
|
refreshCurrentVersion(),
|
||||||
props.server.refresh(["general"]),
|
props.server.refresh(['general']),
|
||||||
]);
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -102,9 +102,10 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ChevronRightIcon, LockIcon, SparklesIcon } from "@modrinth/assets";
|
import { ChevronRightIcon, LockIcon, SparklesIcon } from "@modrinth/assets";
|
||||||
import type { Project, Server } from "@modrinth/utils";
|
|
||||||
import { useModrinthServers } from "~/composables/servers/modrinth-servers.ts";
|
|
||||||
import { Avatar, CopyCode } from "@modrinth/ui";
|
import { Avatar, CopyCode } from "@modrinth/ui";
|
||||||
|
import type { Project, Server } from "@modrinth/utils";
|
||||||
|
|
||||||
|
import { useModrinthServers } from "~/composables/servers/modrinth-servers.ts";
|
||||||
|
|
||||||
const props = defineProps<Partial<Server>>();
|
const props = defineProps<Partial<Server>>();
|
||||||
|
|
||||||
|
|||||||
@ -37,8 +37,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RightArrowIcon } from "@modrinth/assets";
|
import { RightArrowIcon } from "@modrinth/assets";
|
||||||
import type { RouteLocationNormalized } from "vue-router";
|
import type { RouteLocationNormalized } from "vue-router";
|
||||||
|
|
||||||
|
import type { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
||||||
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
|
||||||
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
|
|
||||||
|
|
||||||
const emit = defineEmits(["reinstall"]);
|
const emit = defineEmits(["reinstall"]);
|
||||||
|
|
||||||
|
|||||||
@ -67,10 +67,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, shallowRef } from "vue";
|
import { CpuIcon, DatabaseIcon, FolderOpenIcon, IssuesIcon } from "@modrinth/assets";
|
||||||
import { FolderOpenIcon, CpuIcon, DatabaseIcon, IssuesIcon } from "@modrinth/assets";
|
|
||||||
import { useStorage } from "@vueuse/core";
|
|
||||||
import type { Stats } from "@modrinth/utils";
|
import type { Stats } from "@modrinth/utils";
|
||||||
|
import { useStorage } from "@vueuse/core";
|
||||||
|
import { computed, ref, shallowRef } from "vue";
|
||||||
|
|
||||||
const flags = useFeatureFlags();
|
const flags = useFeatureFlags();
|
||||||
const route = useNativeRoute();
|
const route = useNativeRoute();
|
||||||
|
|||||||
@ -86,24 +86,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DropdownIcon } from "@modrinth/assets";
|
import { DropdownIcon } from '@modrinth/assets'
|
||||||
import { computed, ref, watch, onMounted, onUnmounted, nextTick } from "vue";
|
import type { CSSProperties } from 'vue'
|
||||||
import type { CSSProperties } from "vue";
|
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
const ITEM_HEIGHT = 44;
|
const ITEM_HEIGHT = 44
|
||||||
const BUFFER_ITEMS = 5;
|
const BUFFER_ITEMS = 5
|
||||||
|
|
||||||
type OptionValue = string | number | Record<string, any>;
|
type OptionValue = string | number | Record<string, any>
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
options: OptionValue[];
|
options: OptionValue[]
|
||||||
name: string;
|
name: string
|
||||||
defaultValue?: OptionValue | null;
|
defaultValue?: OptionValue | null
|
||||||
placeholder?: string | number | null;
|
placeholder?: string | number | null
|
||||||
modelValue?: OptionValue | null;
|
modelValue?: OptionValue | null
|
||||||
renderUp?: boolean;
|
renderUp?: boolean
|
||||||
disabled?: boolean;
|
disabled?: boolean
|
||||||
displayName?: (option: OptionValue) => string;
|
displayName?: (option: OptionValue) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@ -113,347 +113,346 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
renderUp: false,
|
renderUp: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
displayName: (option: OptionValue) => String(option),
|
displayName: (option: OptionValue) => String(option),
|
||||||
});
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "input", value: OptionValue): void;
|
(e: 'input' | 'update:modelValue', value: OptionValue): void
|
||||||
(e: "change", value: { option: OptionValue; index: number }): void;
|
(e: 'change', value: { option: OptionValue; index: number }): void
|
||||||
(e: "update:modelValue", value: OptionValue): void;
|
}>()
|
||||||
}>();
|
|
||||||
|
|
||||||
const dropdownVisible = ref(false);
|
const dropdownVisible = ref(false)
|
||||||
const selectedValue = ref<OptionValue | null>(props.modelValue || props.defaultValue);
|
const selectedValue = ref<OptionValue | null>(props.modelValue || props.defaultValue)
|
||||||
const focusedOptionIndex = ref<number | null>(null);
|
const focusedOptionIndex = ref<number | null>(null)
|
||||||
const optionsContainer = ref<HTMLElement | null>(null);
|
const optionsContainer = ref<HTMLElement | null>(null)
|
||||||
const scrollTop = ref(0);
|
const scrollTop = ref(0)
|
||||||
const isRenderingUp = ref(false);
|
const isRenderingUp = ref(false)
|
||||||
const virtualListHeight = ref(300);
|
const virtualListHeight = ref(300)
|
||||||
const isOpen = ref(false);
|
const isOpen = ref(false)
|
||||||
const openDropdownCount = ref(0);
|
const openDropdownCount = ref(0)
|
||||||
const listboxId = `pyro-listbox-${Math.random().toString(36).substring(2, 11)}`;
|
const listboxId = `pyro-listbox-${Math.random().toString(36).substring(2, 11)}`
|
||||||
const triggerRef = ref<HTMLButtonElement | null>(null);
|
const triggerRef = ref<HTMLButtonElement | null>(null)
|
||||||
|
|
||||||
const positionStyle = ref<CSSProperties>({
|
const positionStyle = ref<CSSProperties>({
|
||||||
position: "fixed",
|
position: 'fixed',
|
||||||
top: "0px",
|
top: '0px',
|
||||||
left: "0px",
|
left: '0px',
|
||||||
width: "0px",
|
width: '0px',
|
||||||
zIndex: 999,
|
zIndex: 999,
|
||||||
});
|
})
|
||||||
|
|
||||||
const totalHeight = computed(() => props.options.length * ITEM_HEIGHT);
|
const totalHeight = computed(() => props.options.length * ITEM_HEIGHT)
|
||||||
|
|
||||||
const visibleOptions = computed(() => {
|
const visibleOptions = computed(() => {
|
||||||
const startIndex = Math.floor(scrollTop.value / ITEM_HEIGHT) - BUFFER_ITEMS;
|
const startIndex = Math.floor(scrollTop.value / ITEM_HEIGHT) - BUFFER_ITEMS
|
||||||
const visibleCount = Math.ceil(virtualListHeight.value / ITEM_HEIGHT) + 2 * BUFFER_ITEMS;
|
const visibleCount = Math.ceil(virtualListHeight.value / ITEM_HEIGHT) + 2 * BUFFER_ITEMS
|
||||||
|
|
||||||
return Array.from({ length: visibleCount }, (_, i) => {
|
return Array.from({ length: visibleCount }, (_, i) => {
|
||||||
const index = startIndex + i;
|
const index = startIndex + i
|
||||||
if (index >= 0 && index < props.options.length) {
|
if (index >= 0 && index < props.options.length) {
|
||||||
return {
|
return {
|
||||||
index,
|
index,
|
||||||
option: props.options[index],
|
option: props.options[index],
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return null;
|
}
|
||||||
}).filter((item): item is { index: number; option: OptionValue } => item !== null);
|
return null
|
||||||
});
|
}).filter((item): item is { index: number; option: OptionValue } => item !== null)
|
||||||
|
})
|
||||||
|
|
||||||
const selectedOption = computed(() => {
|
const selectedOption = computed(() => {
|
||||||
if (selectedValue.value !== null && selectedValue.value !== undefined) {
|
if (selectedValue.value !== null && selectedValue.value !== undefined) {
|
||||||
return props.displayName(selectedValue.value as OptionValue);
|
return props.displayName(selectedValue.value as OptionValue)
|
||||||
}
|
}
|
||||||
return props.placeholder || "Select an option";
|
return props.placeholder || 'Select an option'
|
||||||
});
|
})
|
||||||
|
|
||||||
const radioValue = computed<OptionValue>({
|
const radioValue = computed<OptionValue>({
|
||||||
get() {
|
get() {
|
||||||
return props.modelValue ?? selectedValue.value ?? "";
|
return props.modelValue ?? selectedValue.value ?? ''
|
||||||
},
|
},
|
||||||
set(newValue: OptionValue) {
|
set(newValue: OptionValue) {
|
||||||
emit("update:modelValue", newValue);
|
emit('update:modelValue', newValue)
|
||||||
selectedValue.value = newValue;
|
selectedValue.value = newValue
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
const triggerClasses = computed(() => ({
|
const triggerClasses = computed(() => ({
|
||||||
"!cursor-not-allowed opacity-50 grayscale": props.disabled,
|
'!cursor-not-allowed opacity-50 grayscale': props.disabled,
|
||||||
"rounded-b-none": dropdownVisible.value && !isRenderingUp.value && !props.disabled,
|
'rounded-b-none': dropdownVisible.value && !isRenderingUp.value && !props.disabled,
|
||||||
"rounded-t-none": dropdownVisible.value && isRenderingUp.value && !props.disabled,
|
'rounded-t-none': dropdownVisible.value && isRenderingUp.value && !props.disabled,
|
||||||
}));
|
}))
|
||||||
|
|
||||||
const updatePosition = async () => {
|
const updatePosition = async () => {
|
||||||
if (!triggerRef.value) return;
|
if (!triggerRef.value) return
|
||||||
|
|
||||||
await nextTick();
|
await nextTick()
|
||||||
const triggerRect = triggerRef.value.getBoundingClientRect();
|
const triggerRect = triggerRef.value.getBoundingClientRect()
|
||||||
const viewportHeight = window.innerHeight;
|
const viewportHeight = window.innerHeight
|
||||||
const margin = 8;
|
const margin = 8
|
||||||
|
|
||||||
const contentHeight = props.options.length * ITEM_HEIGHT;
|
const contentHeight = props.options.length * ITEM_HEIGHT
|
||||||
const preferredHeight = Math.min(contentHeight, 300);
|
const preferredHeight = Math.min(contentHeight, 300)
|
||||||
|
|
||||||
const spaceBelow = viewportHeight - triggerRect.bottom;
|
const spaceBelow = viewportHeight - triggerRect.bottom
|
||||||
const spaceAbove = triggerRect.top;
|
const spaceAbove = triggerRect.top
|
||||||
|
|
||||||
isRenderingUp.value = spaceBelow < preferredHeight && spaceAbove > spaceBelow;
|
isRenderingUp.value = spaceBelow < preferredHeight && spaceAbove > spaceBelow
|
||||||
|
|
||||||
virtualListHeight.value = isRenderingUp.value
|
virtualListHeight.value = isRenderingUp.value
|
||||||
? Math.min(spaceAbove - margin, preferredHeight)
|
? Math.min(spaceAbove - margin, preferredHeight)
|
||||||
: Math.min(spaceBelow - margin, preferredHeight);
|
: Math.min(spaceBelow - margin, preferredHeight)
|
||||||
|
|
||||||
positionStyle.value = {
|
positionStyle.value = {
|
||||||
position: "fixed",
|
position: 'fixed',
|
||||||
left: `${triggerRect.left}px`,
|
left: `${triggerRect.left}px`,
|
||||||
width: `${triggerRect.width}px`,
|
width: `${triggerRect.width}px`,
|
||||||
zIndex: 999,
|
zIndex: 999,
|
||||||
...(isRenderingUp.value
|
...(isRenderingUp.value
|
||||||
? { bottom: `${viewportHeight - triggerRect.top}px`, top: "auto" }
|
? { bottom: `${viewportHeight - triggerRect.top}px`, top: 'auto' }
|
||||||
: { top: `${triggerRect.bottom}px`, bottom: "auto" }),
|
: { top: `${triggerRect.bottom}px`, bottom: 'auto' }),
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const toggleDropdown = () => {
|
const toggleDropdown = () => {
|
||||||
if (!props.disabled) {
|
if (!props.disabled) {
|
||||||
if (dropdownVisible.value) {
|
if (dropdownVisible.value) {
|
||||||
closeDropdown();
|
closeDropdown()
|
||||||
} else {
|
} else {
|
||||||
openDropdown();
|
openDropdown()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (dropdownVisible.value) {
|
if (dropdownVisible.value) {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
updatePosition();
|
updatePosition()
|
||||||
});
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleScroll = (event: Event) => {
|
const handleScroll = (event: Event) => {
|
||||||
const target = event.target as HTMLElement;
|
const target = event.target as HTMLElement
|
||||||
scrollTop.value = target.scrollTop;
|
scrollTop.value = target.scrollTop
|
||||||
};
|
}
|
||||||
|
|
||||||
const closeAllDropdowns = () => {
|
const closeAllDropdowns = () => {
|
||||||
const event = new CustomEvent("close-all-dropdowns");
|
const event = new CustomEvent('close-all-dropdowns')
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event)
|
||||||
};
|
}
|
||||||
|
|
||||||
const selectOption = (option: OptionValue, index: number) => {
|
const selectOption = (option: OptionValue, index: number) => {
|
||||||
radioValue.value = option;
|
radioValue.value = option
|
||||||
emit("change", { option, index });
|
emit('change', { option, index })
|
||||||
closeDropdown();
|
closeDropdown()
|
||||||
};
|
}
|
||||||
|
|
||||||
const focusNextOption = () => {
|
const focusNextOption = () => {
|
||||||
if (focusedOptionIndex.value === null) {
|
if (focusedOptionIndex.value === null) {
|
||||||
focusedOptionIndex.value = 0;
|
focusedOptionIndex.value = 0
|
||||||
} else {
|
} else {
|
||||||
focusedOptionIndex.value = (focusedOptionIndex.value + 1) % props.options.length;
|
focusedOptionIndex.value = (focusedOptionIndex.value + 1) % props.options.length
|
||||||
|
}
|
||||||
|
scrollToFocused()
|
||||||
}
|
}
|
||||||
scrollToFocused();
|
|
||||||
};
|
|
||||||
|
|
||||||
const focusPreviousOption = () => {
|
const focusPreviousOption = () => {
|
||||||
if (focusedOptionIndex.value === null) {
|
if (focusedOptionIndex.value === null) {
|
||||||
focusedOptionIndex.value = props.options.length - 1;
|
focusedOptionIndex.value = props.options.length - 1
|
||||||
} else {
|
} else {
|
||||||
focusedOptionIndex.value =
|
focusedOptionIndex.value =
|
||||||
(focusedOptionIndex.value - 1 + props.options.length) % props.options.length;
|
(focusedOptionIndex.value - 1 + props.options.length) % props.options.length
|
||||||
|
}
|
||||||
|
scrollToFocused()
|
||||||
}
|
}
|
||||||
scrollToFocused();
|
|
||||||
};
|
|
||||||
|
|
||||||
const scrollToFocused = () => {
|
const scrollToFocused = () => {
|
||||||
if (focusedOptionIndex.value === null) return;
|
if (focusedOptionIndex.value === null) return
|
||||||
|
|
||||||
const optionsElement = optionsContainer.value?.querySelector(".overflow-y-auto");
|
const optionsElement = optionsContainer.value?.querySelector('.overflow-y-auto')
|
||||||
if (!optionsElement) return;
|
if (!optionsElement) return
|
||||||
|
|
||||||
const targetScrollTop = focusedOptionIndex.value * ITEM_HEIGHT;
|
const targetScrollTop = focusedOptionIndex.value * ITEM_HEIGHT
|
||||||
const scrollBottom = optionsElement.clientHeight;
|
const scrollBottom = optionsElement.clientHeight
|
||||||
|
|
||||||
if (targetScrollTop < optionsElement.scrollTop) {
|
if (targetScrollTop < optionsElement.scrollTop) {
|
||||||
optionsElement.scrollTop = targetScrollTop;
|
optionsElement.scrollTop = targetScrollTop
|
||||||
} else if (targetScrollTop + ITEM_HEIGHT > optionsElement.scrollTop + scrollBottom) {
|
} else if (targetScrollTop + ITEM_HEIGHT > optionsElement.scrollTop + scrollBottom) {
|
||||||
optionsElement.scrollTop = targetScrollTop - scrollBottom + ITEM_HEIGHT;
|
optionsElement.scrollTop = targetScrollTop - scrollBottom + ITEM_HEIGHT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const openDropdown = async () => {
|
const openDropdown = async () => {
|
||||||
if (!props.disabled) {
|
if (!props.disabled) {
|
||||||
closeAllDropdowns();
|
closeAllDropdowns()
|
||||||
dropdownVisible.value = true;
|
dropdownVisible.value = true
|
||||||
isOpen.value = true;
|
isOpen.value = true
|
||||||
openDropdownCount.value++;
|
openDropdownCount.value++
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = 'hidden'
|
||||||
await updatePosition();
|
await updatePosition()
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
optionsContainer.value?.focus();
|
optionsContainer.value?.focus()
|
||||||
});
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const closeDropdown = () => {
|
const closeDropdown = () => {
|
||||||
if (isOpen.value) {
|
if (isOpen.value) {
|
||||||
dropdownVisible.value = false;
|
dropdownVisible.value = false
|
||||||
isOpen.value = false;
|
isOpen.value = false
|
||||||
openDropdownCount.value--;
|
openDropdownCount.value--
|
||||||
if (openDropdownCount.value === 0) {
|
if (openDropdownCount.value === 0) {
|
||||||
document.body.style.overflow = "";
|
document.body.style.overflow = ''
|
||||||
|
}
|
||||||
|
focusedOptionIndex.value = null
|
||||||
|
triggerRef.value?.focus()
|
||||||
}
|
}
|
||||||
focusedOptionIndex.value = null;
|
|
||||||
triggerRef.value?.focus();
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleTriggerKeyDown = (event: KeyboardEvent) => {
|
const handleTriggerKeyDown = (event: KeyboardEvent) => {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case "ArrowDown":
|
case 'ArrowDown':
|
||||||
case "ArrowUp":
|
case 'ArrowUp':
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
if (!dropdownVisible.value) {
|
if (!dropdownVisible.value) {
|
||||||
openDropdown();
|
openDropdown()
|
||||||
focusedOptionIndex.value = event.key === "ArrowUp" ? props.options.length - 1 : 0;
|
focusedOptionIndex.value = event.key === 'ArrowUp' ? props.options.length - 1 : 0
|
||||||
} else if (event.key === "ArrowDown") {
|
} else if (event.key === 'ArrowDown') {
|
||||||
focusNextOption();
|
focusNextOption()
|
||||||
} else {
|
} else {
|
||||||
focusPreviousOption();
|
focusPreviousOption()
|
||||||
}
|
}
|
||||||
break;
|
break
|
||||||
case "Enter":
|
case 'Enter':
|
||||||
case " ":
|
case ' ':
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
if (!dropdownVisible.value) {
|
if (!dropdownVisible.value) {
|
||||||
openDropdown();
|
openDropdown()
|
||||||
focusedOptionIndex.value = 0;
|
focusedOptionIndex.value = 0
|
||||||
} else if (focusedOptionIndex.value !== null) {
|
} else if (focusedOptionIndex.value !== null) {
|
||||||
selectOption(props.options[focusedOptionIndex.value], focusedOptionIndex.value);
|
selectOption(props.options[focusedOptionIndex.value], focusedOptionIndex.value)
|
||||||
}
|
}
|
||||||
break;
|
break
|
||||||
case "Escape":
|
case 'Escape':
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
closeDropdown();
|
closeDropdown()
|
||||||
break;
|
break
|
||||||
case "Tab":
|
case 'Tab':
|
||||||
if (dropdownVisible.value) {
|
if (dropdownVisible.value) {
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleListboxKeyDown = (event: KeyboardEvent) => {
|
const handleListboxKeyDown = (event: KeyboardEvent) => {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case "Enter":
|
case 'Enter':
|
||||||
case " ":
|
case ' ':
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
if (focusedOptionIndex.value !== null) {
|
if (focusedOptionIndex.value !== null) {
|
||||||
selectOption(props.options[focusedOptionIndex.value], focusedOptionIndex.value);
|
selectOption(props.options[focusedOptionIndex.value], focusedOptionIndex.value)
|
||||||
}
|
}
|
||||||
break;
|
break
|
||||||
case "ArrowDown":
|
case 'ArrowDown':
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
focusNextOption();
|
focusNextOption()
|
||||||
break;
|
break
|
||||||
case "ArrowUp":
|
case 'ArrowUp':
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
focusPreviousOption();
|
focusPreviousOption()
|
||||||
break;
|
break
|
||||||
case "Escape":
|
case 'Escape':
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
closeDropdown();
|
closeDropdown()
|
||||||
break;
|
break
|
||||||
case "Tab":
|
case 'Tab':
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
break;
|
break
|
||||||
case "Home":
|
case 'Home':
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
focusedOptionIndex.value = 0;
|
focusedOptionIndex.value = 0
|
||||||
scrollToFocused();
|
scrollToFocused()
|
||||||
break;
|
break
|
||||||
case "End":
|
case 'End':
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
focusedOptionIndex.value = props.options.length - 1;
|
focusedOptionIndex.value = props.options.length - 1
|
||||||
scrollToFocused();
|
scrollToFocused()
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
if (event.key.length === 1) {
|
if (event.key.length === 1) {
|
||||||
const char = event.key.toLowerCase();
|
const char = event.key.toLowerCase()
|
||||||
const index = props.options.findIndex((option) =>
|
const index = props.options.findIndex((option) =>
|
||||||
props.displayName(option).toLowerCase().startsWith(char),
|
props.displayName(option).toLowerCase().startsWith(char),
|
||||||
);
|
)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
focusedOptionIndex.value = index;
|
focusedOptionIndex.value = index
|
||||||
scrollToFocused();
|
scrollToFocused()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener('resize', handleResize)
|
||||||
window.addEventListener("scroll", handleResize, true);
|
window.addEventListener('scroll', handleResize, true)
|
||||||
window.addEventListener("click", (event) => {
|
window.addEventListener('click', (event) => {
|
||||||
if (!isChildOfDropdown(event.target as HTMLElement)) {
|
if (!isChildOfDropdown(event.target as HTMLElement)) {
|
||||||
closeDropdown();
|
closeDropdown()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
window.addEventListener("close-all-dropdowns", closeDropdown);
|
window.addEventListener('close-all-dropdowns', closeDropdown)
|
||||||
|
|
||||||
if (selectedValue.value) {
|
if (selectedValue.value) {
|
||||||
focusedOptionIndex.value = props.options.findIndex((option) => option === selectedValue.value);
|
focusedOptionIndex.value = props.options.findIndex((option) => option === selectedValue.value)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener("resize", handleResize);
|
window.removeEventListener('resize', handleResize)
|
||||||
window.removeEventListener("scroll", handleResize, true);
|
window.removeEventListener('scroll', handleResize, true)
|
||||||
window.removeEventListener("click", (event) => {
|
window.removeEventListener('click', (event) => {
|
||||||
if (!isChildOfDropdown(event.target as HTMLElement)) {
|
if (!isChildOfDropdown(event.target as HTMLElement)) {
|
||||||
closeDropdown();
|
closeDropdown()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
window.removeEventListener("close-all-dropdowns", closeDropdown);
|
window.removeEventListener('close-all-dropdowns', closeDropdown)
|
||||||
|
|
||||||
if (isOpen.value) {
|
if (isOpen.value) {
|
||||||
openDropdownCount.value--;
|
openDropdownCount.value--
|
||||||
if (openDropdownCount.value === 0) {
|
if (openDropdownCount.value === 0) {
|
||||||
document.body.style.overflow = "";
|
document.body.style.overflow = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
selectedValue.value = newValue;
|
selectedValue.value = newValue
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
|
||||||
watch(dropdownVisible, async (newValue) => {
|
watch(dropdownVisible, async (newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
await updatePosition();
|
await updatePosition()
|
||||||
scrollTop.value = 0;
|
scrollTop.value = 0
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
const activeDescendant = computed(() =>
|
const activeDescendant = computed(() =>
|
||||||
focusedOptionIndex.value !== null ? `${listboxId}-option-${focusedOptionIndex.value}` : undefined,
|
focusedOptionIndex.value !== null ? `${listboxId}-option-${focusedOptionIndex.value}` : undefined,
|
||||||
);
|
)
|
||||||
|
|
||||||
const isChildOfDropdown = (element: HTMLElement | null): boolean => {
|
const isChildOfDropdown = (element: HTMLElement | null): boolean => {
|
||||||
let currentNode: HTMLElement | null = element;
|
let currentNode: HTMLElement | null = element
|
||||||
while (currentNode) {
|
while (currentNode) {
|
||||||
if (currentNode === triggerRef.value || currentNode === optionsContainer.value) {
|
if (currentNode === triggerRef.value || currentNode === optionsContainer.value) {
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
currentNode = currentNode.parentElement;
|
currentNode = currentNode.parentElement
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -102,8 +102,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonStyled } from "@modrinth/ui";
|
import { ButtonStyled } from "@modrinth/ui";
|
||||||
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from "vue";
|
|
||||||
import { onClickOutside, useElementHover } from "@vueuse/core";
|
import { onClickOutside, useElementHover } from "@vueuse/core";
|
||||||
|
import { computed,nextTick, onMounted, onUnmounted, ref, watch } from "vue";
|
||||||
|
|
||||||
interface Option {
|
interface Option {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonStyled, ServersSpecs } from "@modrinth/ui";
|
import { ButtonStyled, ServersSpecs } from "@modrinth/ui";
|
||||||
import type { MessageDescriptor } from "@vintl/vintl";
|
|
||||||
import { formatPrice } from "@modrinth/utils";
|
import { formatPrice } from "@modrinth/utils";
|
||||||
|
import type { MessageDescriptor } from "@vintl/vintl";
|
||||||
|
|
||||||
const { formatMessage, locale } = useVIntl();
|
const { formatMessage, locale } = useVIntl();
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Accordion, ButtonStyled, NewModal, ServerNotice, TagItem } from "@modrinth/ui";
|
|
||||||
import { PlusIcon, XIcon } from "@modrinth/assets";
|
import { PlusIcon, XIcon } from "@modrinth/assets";
|
||||||
|
import { Accordion, ButtonStyled, NewModal, ServerNotice, TagItem } from "@modrinth/ui";
|
||||||
import type { ServerNotice as ServerNoticeType } from "@modrinth/utils";
|
import type { ServerNotice as ServerNoticeType } from "@modrinth/utils";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
||||||
|
|
||||||
const app = useNuxtApp() as unknown as { $notify: any };
|
const app = useNuxtApp() as unknown as { $notify: any };
|
||||||
|
|||||||
@ -1,17 +1,25 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import dayjs from "dayjs";
|
import { EditIcon, SettingsIcon, TrashIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, commonMessages, CopyCode, ServerNotice, TagItem } from "@modrinth/ui";
|
import {
|
||||||
import { EditIcon, SettingsIcon, TrashIcon } from "@modrinth/assets";
|
ButtonStyled,
|
||||||
import type { ServerNotice as ServerNoticeType } from "@modrinth/utils";
|
commonMessages,
|
||||||
import { useRelativeTime, getDismissableMetadata, NOTICE_LEVELS } from "@modrinth/ui";
|
CopyCode,
|
||||||
import { useVIntl } from "@vintl/vintl";
|
getDismissableMetadata,
|
||||||
|
NOTICE_LEVELS,
|
||||||
|
ServerNotice,
|
||||||
|
TagItem,
|
||||||
|
useRelativeTime,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
import type { ServerNotice as ServerNoticeType } from '@modrinth/utils'
|
||||||
|
import { useVIntl } from '@vintl/vintl'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl();
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime();
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
notice: ServerNoticeType;
|
notice: ServerNoticeType
|
||||||
}>();
|
}>()
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="col-span-full grid grid-cols-subgrid gap-4 rounded-2xl bg-bg-raised p-4">
|
<div class="col-span-full grid grid-cols-subgrid gap-4 rounded-2xl bg-bg-raised p-4">
|
||||||
@ -21,7 +29,7 @@ const props = defineProps<{
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<span v-if="notice.announce_at">
|
<span v-if="notice.announce_at">
|
||||||
{{ dayjs(notice.announce_at).format("MMM D, YYYY [at] h:mm A") }} ({{
|
{{ dayjs(notice.announce_at).format('MMM D, YYYY [at] h:mm A') }} ({{
|
||||||
formatRelativeTime(notice.announce_at)
|
formatRelativeTime(notice.announce_at)
|
||||||
}})
|
}})
|
||||||
</span>
|
</span>
|
||||||
@ -91,16 +99,16 @@ const props = defineProps<{
|
|||||||
>
|
>
|
||||||
<span v-else-if="!notice.assigned.some((n) => n.kind === 'server')">
|
<span v-else-if="!notice.assigned.some((n) => n.kind === 'server')">
|
||||||
Assigned to
|
Assigned to
|
||||||
{{ notice.assigned.filter((n) => n.kind === "node").length }} nodes
|
{{ notice.assigned.filter((n) => n.kind === 'node').length }} nodes
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="!notice.assigned.some((n) => n.kind === 'node')">
|
<span v-else-if="!notice.assigned.some((n) => n.kind === 'node')">
|
||||||
Assigned to
|
Assigned to
|
||||||
{{ notice.assigned.filter((n) => n.kind === "server").length }} servers
|
{{ notice.assigned.filter((n) => n.kind === 'server').length }} servers
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
Assigned to
|
Assigned to
|
||||||
{{ notice.assigned.filter((n) => n.kind === "server").length }} servers and
|
{{ notice.assigned.filter((n) => n.kind === 'server').length }} servers and
|
||||||
{{ notice.assigned.filter((n) => n.kind === "node").length }} nodes
|
{{ notice.assigned.filter((n) => n.kind === 'node').length }} nodes
|
||||||
</span>
|
</span>
|
||||||
•
|
•
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -212,7 +212,7 @@
|
|||||||
id: 'withhold-reply',
|
id: 'withhold-reply',
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
action: () => {
|
action: () => {
|
||||||
sendReply('withheld');
|
sendReply('withheld')
|
||||||
},
|
},
|
||||||
hoverFilled: true,
|
hoverFilled: true,
|
||||||
disabled: project.status === 'withheld',
|
disabled: project.status === 'withheld',
|
||||||
@ -223,7 +223,7 @@
|
|||||||
id: 'withhold',
|
id: 'withhold',
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
action: () => {
|
action: () => {
|
||||||
setStatus('withheld');
|
setStatus('withheld')
|
||||||
},
|
},
|
||||||
hoverFilled: true,
|
hoverFilled: true,
|
||||||
disabled: project.status === 'withheld',
|
disabled: project.status === 'withheld',
|
||||||
@ -251,23 +251,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { CopyCode, OverflowMenu, MarkdownEditor } from "@modrinth/ui";
|
|
||||||
import {
|
import {
|
||||||
DropdownIcon,
|
|
||||||
ReplyIcon,
|
|
||||||
SendIcon,
|
|
||||||
CheckCircleIcon,
|
CheckCircleIcon,
|
||||||
XIcon,
|
|
||||||
EyeOffIcon,
|
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
|
DropdownIcon,
|
||||||
|
EyeOffIcon,
|
||||||
|
ReplyIcon,
|
||||||
ScaleIcon,
|
ScaleIcon,
|
||||||
} from "@modrinth/assets";
|
SendIcon,
|
||||||
import { useImageUpload } from "~/composables/image-upload.ts";
|
XIcon,
|
||||||
import ThreadMessage from "~/components/ui/thread/ThreadMessage.vue";
|
} from '@modrinth/assets'
|
||||||
import { isStaff } from "~/helpers/users.js";
|
import { CopyCode, MarkdownEditor, OverflowMenu } from '@modrinth/ui'
|
||||||
import { isApproved, isRejected } from "~/helpers/projects.js";
|
|
||||||
import Modal from "~/components/ui/Modal.vue";
|
import Checkbox from '~/components/ui/Checkbox.vue'
|
||||||
import Checkbox from "~/components/ui/Checkbox.vue";
|
import Modal from '~/components/ui/Modal.vue'
|
||||||
|
import ThreadMessage from '~/components/ui/thread/ThreadMessage.vue'
|
||||||
|
import { useImageUpload } from '~/composables/image-upload.ts'
|
||||||
|
import { isApproved, isRejected } from '~/helpers/projects.js'
|
||||||
|
import { isStaff } from '~/helpers/users.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
thread: {
|
thread: {
|
||||||
@ -292,178 +293,178 @@ const props = defineProps({
|
|||||||
currentMember: {
|
currentMember: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default() {
|
default() {
|
||||||
return null;
|
return null
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
const emit = defineEmits(["update-thread"]);
|
const emit = defineEmits(['update-thread'])
|
||||||
|
|
||||||
const app = useNuxtApp();
|
const app = useNuxtApp()
|
||||||
const flags = useFeatureFlags();
|
const flags = useFeatureFlags()
|
||||||
|
|
||||||
const members = computed(() => {
|
const members = computed(() => {
|
||||||
const members = {};
|
const members = {}
|
||||||
for (const member of props.thread.members) {
|
for (const member of props.thread.members) {
|
||||||
members[member.id] = member;
|
members[member.id] = member
|
||||||
}
|
}
|
||||||
return members;
|
return members
|
||||||
});
|
})
|
||||||
|
|
||||||
const replyBody = ref("");
|
const replyBody = ref('')
|
||||||
|
|
||||||
const sortedMessages = computed(() => {
|
const sortedMessages = computed(() => {
|
||||||
if (props.thread !== null) {
|
if (props.thread !== null) {
|
||||||
return props.thread.messages
|
return props.thread.messages
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => app.$dayjs(a.created) - app.$dayjs(b.created));
|
.sort((a, b) => app.$dayjs(a.created) - app.$dayjs(b.created))
|
||||||
}
|
}
|
||||||
return [];
|
return []
|
||||||
});
|
})
|
||||||
|
|
||||||
const modalSubmit = ref(null);
|
const modalSubmit = ref(null)
|
||||||
const modalReply = ref(null);
|
const modalReply = ref(null)
|
||||||
|
|
||||||
async function updateThreadLocal() {
|
async function updateThreadLocal() {
|
||||||
let threadId = null;
|
let threadId = null
|
||||||
if (props.project) {
|
if (props.project) {
|
||||||
threadId = props.project.thread_id;
|
threadId = props.project.thread_id
|
||||||
} else if (props.report) {
|
} else if (props.report) {
|
||||||
threadId = props.report.thread_id;
|
threadId = props.report.thread_id
|
||||||
}
|
}
|
||||||
let thread = null;
|
let thread = null
|
||||||
if (threadId) {
|
if (threadId) {
|
||||||
thread = await useBaseFetch(`thread/${threadId}`);
|
thread = await useBaseFetch(`thread/${threadId}`)
|
||||||
}
|
}
|
||||||
emit("update-thread", thread);
|
emit('update-thread', thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageIDs = ref([]);
|
const imageIDs = ref([])
|
||||||
|
|
||||||
async function onUploadImage(file) {
|
async function onUploadImage(file) {
|
||||||
const response = await useImageUpload(file, { context: "thread_message" });
|
const response = await useImageUpload(file, { context: 'thread_message' })
|
||||||
|
|
||||||
imageIDs.value.push(response.id);
|
imageIDs.value.push(response.id)
|
||||||
// Keep the last 10 entries of image IDs
|
// Keep the last 10 entries of image IDs
|
||||||
imageIDs.value = imageIDs.value.slice(-10);
|
imageIDs.value = imageIDs.value.slice(-10)
|
||||||
|
|
||||||
return response.url;
|
return response.url
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendReplyFromModal(status = null, privateMessage = false) {
|
async function sendReplyFromModal(status = null, privateMessage = false) {
|
||||||
modalReply.value.hide();
|
modalReply.value.hide()
|
||||||
await sendReply(status, privateMessage);
|
await sendReply(status, privateMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendReply(status = null, privateMessage = false) {
|
async function sendReply(status = null, privateMessage = false) {
|
||||||
try {
|
try {
|
||||||
const body = {
|
const body = {
|
||||||
body: {
|
body: {
|
||||||
type: "text",
|
type: 'text',
|
||||||
body: replyBody.value,
|
body: replyBody.value,
|
||||||
private: privateMessage,
|
private: privateMessage,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
if (imageIDs.value.length > 0) {
|
if (imageIDs.value.length > 0) {
|
||||||
body.body = {
|
body.body = {
|
||||||
...body.body,
|
...body.body,
|
||||||
uploaded_images: imageIDs.value,
|
uploaded_images: imageIDs.value,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await useBaseFetch(`thread/${props.thread.id}`, {
|
await useBaseFetch(`thread/${props.thread.id}`, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
body,
|
body,
|
||||||
});
|
})
|
||||||
|
|
||||||
replyBody.value = "";
|
replyBody.value = ''
|
||||||
|
|
||||||
await updateThreadLocal();
|
await updateThreadLocal()
|
||||||
if (status !== null) {
|
if (status !== null) {
|
||||||
props.setStatus(status);
|
props.setStatus(status)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
app.$notify({
|
app.$notify({
|
||||||
group: "main",
|
group: 'main',
|
||||||
title: "Error sending message",
|
title: 'Error sending message',
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: "error",
|
type: 'error',
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function closeReport(reply) {
|
async function closeReport(reply) {
|
||||||
if (reply) {
|
if (reply) {
|
||||||
await sendReply();
|
await sendReply()
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await useBaseFetch(`report/${props.report.id}`, {
|
await useBaseFetch(`report/${props.report.id}`, {
|
||||||
method: "PATCH",
|
method: 'PATCH',
|
||||||
body: {
|
body: {
|
||||||
closed: true,
|
closed: true,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
await updateThreadLocal();
|
await updateThreadLocal()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
app.$notify({
|
app.$notify({
|
||||||
group: "main",
|
group: 'main',
|
||||||
title: "Error closing report",
|
title: 'Error closing report',
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: "error",
|
type: 'error',
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reopenReport() {
|
async function reopenReport() {
|
||||||
try {
|
try {
|
||||||
await useBaseFetch(`report/${props.report.id}`, {
|
await useBaseFetch(`report/${props.report.id}`, {
|
||||||
method: "PATCH",
|
method: 'PATCH',
|
||||||
body: {
|
body: {
|
||||||
closed: false,
|
closed: false,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
await updateThreadLocal();
|
await updateThreadLocal()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
app.$notify({
|
app.$notify({
|
||||||
group: "main",
|
group: 'main',
|
||||||
title: "Error reopening report",
|
title: 'Error reopening report',
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: "error",
|
type: 'error',
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const replyWithSubmission = ref(false);
|
const replyWithSubmission = ref(false)
|
||||||
const submissionConfirmation = ref(false);
|
const submissionConfirmation = ref(false)
|
||||||
const replyConfirmation = ref(false);
|
const replyConfirmation = ref(false)
|
||||||
|
|
||||||
function openResubmitModal(reply) {
|
function openResubmitModal(reply) {
|
||||||
submissionConfirmation.value = false;
|
submissionConfirmation.value = false
|
||||||
replyWithSubmission.value = reply;
|
replyWithSubmission.value = reply
|
||||||
modalSubmit.value.show();
|
modalSubmit.value.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
function openReplyModal(reply) {
|
function openReplyModal() {
|
||||||
replyConfirmation.value = false;
|
replyConfirmation.value = false
|
||||||
modalReply.value.show();
|
modalReply.value.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resubmit() {
|
async function resubmit() {
|
||||||
if (replyWithSubmission.value) {
|
if (replyWithSubmission.value) {
|
||||||
await sendReply("processing");
|
await sendReply('processing')
|
||||||
} else {
|
} else {
|
||||||
await props.setStatus("processing");
|
await props.setStatus('processing')
|
||||||
}
|
}
|
||||||
modalSubmit.value.hide();
|
modalSubmit.value.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestedStatus = computed(() => props.project.requested_status ?? "approved");
|
const requestedStatus = computed(() => props.project.requested_status ?? 'approved')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -110,55 +110,57 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CopyCode, MarkdownEditor, ButtonStyled } from "@modrinth/ui";
|
import { CheckCircleIcon, ReplyIcon, ScaleIcon, SendIcon } from '@modrinth/assets'
|
||||||
import { ReplyIcon, SendIcon, CheckCircleIcon, ScaleIcon } from "@modrinth/assets";
|
import { ButtonStyled, CopyCode, MarkdownEditor } from '@modrinth/ui'
|
||||||
import type { Thread, Report, User, ThreadMessage as TypeThreadMessage } from "@modrinth/utils";
|
import type { Report, Thread, ThreadMessage as TypeThreadMessage, User } from '@modrinth/utils'
|
||||||
import dayjs from "dayjs";
|
import dayjs from 'dayjs'
|
||||||
import ThreadMessage from "./ThreadMessage.vue";
|
|
||||||
import { useImageUpload } from "~/composables/image-upload.ts";
|
import { useImageUpload } from '~/composables/image-upload.ts'
|
||||||
import { isStaff } from "~/helpers/users.js";
|
import { isStaff } from '~/helpers/users.js'
|
||||||
|
|
||||||
|
import ThreadMessage from './ThreadMessage.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
thread: Thread;
|
thread: Thread
|
||||||
reporter: User;
|
reporter: User
|
||||||
report: Report;
|
report: Report
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const auth = await useAuth();
|
defineExpose({
|
||||||
|
setReplyContent,
|
||||||
|
})
|
||||||
|
|
||||||
|
const auth = await useAuth()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
updateThread: [thread: Thread];
|
updateThread: [thread: Thread]
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const flags = useFeatureFlags();
|
const flags = useFeatureFlags()
|
||||||
|
|
||||||
const members = computed(() => {
|
const members = computed(() => {
|
||||||
const membersMap: Record<string, User> = {
|
const membersMap: Record<string, User> = {
|
||||||
[props.reporter.id]: props.reporter,
|
[props.reporter.id]: props.reporter,
|
||||||
};
|
}
|
||||||
for (const member of props.thread.members) {
|
for (const member of props.thread.members) {
|
||||||
membersMap[member.id] = member;
|
membersMap[member.id] = member
|
||||||
}
|
}
|
||||||
return membersMap;
|
return membersMap
|
||||||
});
|
})
|
||||||
|
|
||||||
const replyBody = ref("");
|
const replyBody = ref('')
|
||||||
function setReplyContent(content: string) {
|
function setReplyContent(content: string) {
|
||||||
replyBody.value = content;
|
replyBody.value = content
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
setReplyContent,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sortedMessages = computed(() => {
|
const sortedMessages = computed(() => {
|
||||||
const messages: TypeThreadMessage[] = [
|
const messages: TypeThreadMessage[] = [
|
||||||
{
|
{
|
||||||
id: null,
|
id: null,
|
||||||
author_id: props.reporter.id,
|
author_id: props.reporter.id,
|
||||||
body: {
|
body: {
|
||||||
type: "text",
|
type: 'text',
|
||||||
body: props.report.body || "Report opened.",
|
body: props.report.body || 'Report opened.',
|
||||||
private: false,
|
private: false,
|
||||||
replying_to: null,
|
replying_to: null,
|
||||||
associated_images: [],
|
associated_images: [],
|
||||||
@ -166,117 +168,117 @@ const sortedMessages = computed(() => {
|
|||||||
created: props.report.created,
|
created: props.report.created,
|
||||||
hide_identity: false,
|
hide_identity: false,
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
if (props.thread) {
|
if (props.thread) {
|
||||||
messages.push(
|
messages.push(
|
||||||
...[...props.thread.messages].sort(
|
...[...props.thread.messages].sort(
|
||||||
(a, b) => dayjs(a.created).toDate().getTime() - dayjs(b.created).toDate().getTime(),
|
(a, b) => dayjs(a.created).toDate().getTime() - dayjs(b.created).toDate().getTime(),
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages;
|
return messages
|
||||||
});
|
})
|
||||||
|
|
||||||
async function updateThreadLocal() {
|
async function updateThreadLocal() {
|
||||||
const threadId = props.report.thread_id;
|
const threadId = props.report.thread_id
|
||||||
if (threadId) {
|
if (threadId) {
|
||||||
try {
|
try {
|
||||||
const thread = (await useBaseFetch(`thread/${threadId}`)) as Thread;
|
const thread = (await useBaseFetch(`thread/${threadId}`)) as Thread
|
||||||
emit("updateThread", thread);
|
emit('updateThread', thread)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update thread:", error);
|
console.error('Failed to update thread:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageIDs = ref<string[]>([]);
|
const imageIDs = ref<string[]>([])
|
||||||
|
|
||||||
async function onUploadImage(file: File) {
|
async function onUploadImage(file: File) {
|
||||||
const response = await useImageUpload(file, { context: "thread_message" });
|
const response = await useImageUpload(file, { context: 'thread_message' })
|
||||||
|
|
||||||
imageIDs.value.push(response.id);
|
imageIDs.value.push(response.id)
|
||||||
imageIDs.value = imageIDs.value.slice(-10);
|
imageIDs.value = imageIDs.value.slice(-10)
|
||||||
|
|
||||||
return response.url;
|
return response.url
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendReply(privateMessage = false) {
|
async function sendReply(privateMessage = false) {
|
||||||
try {
|
try {
|
||||||
const body: any = {
|
const body: any = {
|
||||||
body: {
|
body: {
|
||||||
type: "text",
|
type: 'text',
|
||||||
body: replyBody.value,
|
body: replyBody.value,
|
||||||
private: privateMessage,
|
private: privateMessage,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
if (imageIDs.value.length > 0) {
|
if (imageIDs.value.length > 0) {
|
||||||
body.body = {
|
body.body = {
|
||||||
...body.body,
|
...body.body,
|
||||||
uploaded_images: imageIDs.value,
|
uploaded_images: imageIDs.value,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await useBaseFetch(`thread/${props.thread.id}`, {
|
await useBaseFetch(`thread/${props.thread.id}`, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
body,
|
body,
|
||||||
});
|
})
|
||||||
|
|
||||||
replyBody.value = "";
|
replyBody.value = ''
|
||||||
await updateThreadLocal();
|
await updateThreadLocal()
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: "Error sending message",
|
title: 'Error sending message',
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: "error",
|
type: 'error',
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const didCloseReport = ref(false);
|
const didCloseReport = ref(false)
|
||||||
const reportClosed = computed(() => {
|
const reportClosed = computed(() => {
|
||||||
return didCloseReport.value || (props.report && props.report.closed);
|
return didCloseReport.value || (props.report && props.report.closed)
|
||||||
});
|
})
|
||||||
|
|
||||||
async function closeReport(reply = false) {
|
async function closeReport(reply = false) {
|
||||||
if (reply) {
|
if (reply) {
|
||||||
await sendReply();
|
await sendReply()
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await useBaseFetch(`report/${props.report.id}`, {
|
await useBaseFetch(`report/${props.report.id}`, {
|
||||||
method: "PATCH",
|
method: 'PATCH',
|
||||||
body: {
|
body: {
|
||||||
closed: true,
|
closed: true,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
await updateThreadLocal();
|
await updateThreadLocal()
|
||||||
didCloseReport.value = true;
|
didCloseReport.value = true
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: "Error closing report",
|
title: 'Error closing report',
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: "error",
|
type: 'error',
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reopenReport() {
|
async function reopenReport() {
|
||||||
try {
|
try {
|
||||||
await useBaseFetch(`report/${props.report.id}`, {
|
await useBaseFetch(`report/${props.report.id}`, {
|
||||||
method: "PATCH",
|
method: 'PATCH',
|
||||||
body: {
|
body: {
|
||||||
closed: false,
|
closed: false,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
await updateThreadLocal();
|
await updateThreadLocal()
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: "Error reopening report",
|
title: 'Error reopening report',
|
||||||
text: err.data ? err.data.description : err,
|
text: err.data ? err.data.description : err,
|
||||||
type: "error",
|
type: 'error',
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -96,15 +96,16 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
MoreHorizontalIcon,
|
|
||||||
TrashIcon,
|
|
||||||
MicrophoneIcon,
|
|
||||||
LockIcon,
|
LockIcon,
|
||||||
|
MicrophoneIcon,
|
||||||
ModrinthIcon,
|
ModrinthIcon,
|
||||||
|
MoreHorizontalIcon,
|
||||||
ScaleIcon,
|
ScaleIcon,
|
||||||
|
TrashIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { AutoLink, Avatar, Badge, OverflowMenu, useRelativeTime } from "@modrinth/ui";
|
import { AutoLink, Avatar, Badge, OverflowMenu, useRelativeTime } from "@modrinth/ui";
|
||||||
import { renderString } from "@modrinth/utils";
|
import { renderString } from "@modrinth/utils";
|
||||||
|
|
||||||
import { isStaff } from "~/helpers/users.js";
|
import { isStaff } from "~/helpers/users.js";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ChevronRightIcon } from "@modrinth/assets";
|
import { ChevronRightIcon } from "@modrinth/assets";
|
||||||
|
|
||||||
import ThreadMessage from "~/components/ui/thread/ThreadMessage.vue";
|
import ThreadMessage from "~/components/ui/thread/ThreadMessage.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useRequestHeaders } from "#imports";
|
import { useRequestHeaders,useState } from "#imports";
|
||||||
|
|
||||||
export const useUserCountry = () => {
|
export const useUserCountry = () => {
|
||||||
const country = useState<string>("userCountry", () => "US");
|
const country = useState<string>("userCountry", () => "US");
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export function createDisplayNames(
|
|||||||
of(tag: string) {
|
of(tag: string) {
|
||||||
let attempt = 0;
|
let attempt = 0;
|
||||||
|
|
||||||
// eslint-disable-next-line no-labels
|
|
||||||
lookupLoop: do {
|
lookupLoop: do {
|
||||||
let lookup: string;
|
let lookup: string;
|
||||||
switch (attempt) {
|
switch (attempt) {
|
||||||
@ -49,7 +49,7 @@ export function createDisplayNames(
|
|||||||
lookup = safeTagFor(tag);
|
lookup = safeTagFor(tag);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// eslint-disable-next-line no-labels
|
|
||||||
break lookupLoop;
|
break lookupLoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import { ModrinthServerError } from "@modrinth/utils";
|
|
||||||
import type { JWTAuth, ModuleError, ModuleName } from "@modrinth/utils";
|
import type { JWTAuth, ModuleError, ModuleName } from "@modrinth/utils";
|
||||||
import { useServersFetch } from "./servers-fetch.ts";
|
import { ModrinthServerError } from "@modrinth/utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GeneralModule,
|
|
||||||
ContentModule,
|
|
||||||
BackupsModule,
|
BackupsModule,
|
||||||
|
ContentModule,
|
||||||
|
FSModule,
|
||||||
|
GeneralModule,
|
||||||
NetworkModule,
|
NetworkModule,
|
||||||
StartupModule,
|
StartupModule,
|
||||||
WSModule,
|
WSModule,
|
||||||
FSModule,
|
|
||||||
} from "./modules/index.ts";
|
} from "./modules/index.ts";
|
||||||
|
import { useServersFetch } from "./servers-fetch.ts";
|
||||||
|
|
||||||
export function handleError(err: any) {
|
export function handleError(err: any) {
|
||||||
if (err instanceof ModrinthServerError && err.v1Error) {
|
if (err instanceof ModrinthServerError && err.v1Error) {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { Backup, AutoBackupSettings } from "@modrinth/utils";
|
import type { AutoBackupSettings,Backup } from "@modrinth/utils";
|
||||||
|
|
||||||
import { useServersFetch } from "../servers-fetch.ts";
|
import { useServersFetch } from "../servers-fetch.ts";
|
||||||
import { ServerModule } from "./base.ts";
|
import { ServerModule } from "./base.ts";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { Mod, ContentType } from "@modrinth/utils";
|
import type { ContentType,Mod } from "@modrinth/utils";
|
||||||
|
|
||||||
import { useServersFetch } from "../servers-fetch.ts";
|
import { useServersFetch } from "../servers-fetch.ts";
|
||||||
import { ServerModule } from "./base.ts";
|
import { ServerModule } from "./base.ts";
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import type {
|
import type {
|
||||||
FileUploadQuery,
|
|
||||||
JWTAuth,
|
|
||||||
DirectoryResponse,
|
DirectoryResponse,
|
||||||
FilesystemOp,
|
FilesystemOp,
|
||||||
|
FileUploadQuery,
|
||||||
FSQueuedOp,
|
FSQueuedOp,
|
||||||
|
JWTAuth,
|
||||||
} from "@modrinth/utils";
|
} from "@modrinth/utils";
|
||||||
import { ModrinthServerError } from "@modrinth/utils";
|
import { ModrinthServerError } from "@modrinth/utils";
|
||||||
|
|
||||||
import { useServersFetch } from "../servers-fetch.ts";
|
import { useServersFetch } from "../servers-fetch.ts";
|
||||||
import { ServerModule } from "./base.ts";
|
import { ServerModule } from "./base.ts";
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
|
import type { JWTAuth,PowerAction, Project, ServerGeneral } from "@modrinth/utils";
|
||||||
import { $fetch } from "ofetch";
|
import { $fetch } from "ofetch";
|
||||||
import type { ServerGeneral, Project, PowerAction, JWTAuth } from "@modrinth/utils";
|
|
||||||
import { useServersFetch } from "../servers-fetch.ts";
|
import { useServersFetch } from "../servers-fetch.ts";
|
||||||
import { ServerModule } from "./base.ts";
|
import { ServerModule } from "./base.ts";
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export * from "./base.ts";
|
|
||||||
export * from "./backups.ts";
|
export * from "./backups.ts";
|
||||||
|
export * from "./base.ts";
|
||||||
export * from "./content.ts";
|
export * from "./content.ts";
|
||||||
export * from "./fs.ts";
|
export * from "./fs.ts";
|
||||||
export * from "./general.ts";
|
export * from "./general.ts";
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { Allocation } from "@modrinth/utils";
|
import type { Allocation } from "@modrinth/utils";
|
||||||
|
|
||||||
import { useServersFetch } from "../servers-fetch.ts";
|
import { useServersFetch } from "../servers-fetch.ts";
|
||||||
import { ServerModule } from "./base.ts";
|
import { ServerModule } from "./base.ts";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { Startup, JDKVersion, JDKBuild } from "@modrinth/utils";
|
import type { JDKBuild,JDKVersion, Startup } from "@modrinth/utils";
|
||||||
|
|
||||||
import { useServersFetch } from "../servers-fetch.ts";
|
import { useServersFetch } from "../servers-fetch.ts";
|
||||||
import { ServerModule } from "./base.ts";
|
import { ServerModule } from "./base.ts";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { JWTAuth } from "@modrinth/utils";
|
import type { JWTAuth } from "@modrinth/utils";
|
||||||
|
|
||||||
import { useServersFetch } from "../servers-fetch.ts";
|
import { useServersFetch } from "../servers-fetch.ts";
|
||||||
import { ServerModule } from "./base.ts";
|
import { ServerModule } from "./base.ts";
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { $fetch, FetchError } from "ofetch";
|
|
||||||
import { ModrinthServerError, ModrinthServersFetchError } from "@modrinth/utils";
|
|
||||||
import type { V1ErrorInfo } from "@modrinth/utils";
|
import type { V1ErrorInfo } from "@modrinth/utils";
|
||||||
|
import { ModrinthServerError, ModrinthServersFetchError } from "@modrinth/utils";
|
||||||
|
import { $fetch, FetchError } from "ofetch";
|
||||||
|
|
||||||
export interface ServersFetchOptions {
|
export interface ServersFetchOptions {
|
||||||
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
||||||
|
|||||||
@ -49,8 +49,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineMessage, useVIntl } from "@vintl/vintl";
|
|
||||||
import { SadRinthbot } from "@modrinth/assets";
|
import { SadRinthbot } from "@modrinth/assets";
|
||||||
|
import { defineMessage, useVIntl } from "@vintl/vintl";
|
||||||
|
|
||||||
import Logo404 from "~/assets/images/404.svg";
|
import Logo404 from "~/assets/images/404.svg";
|
||||||
|
|
||||||
const { formatMessage } = useVIntl();
|
const { formatMessage } = useVIntl();
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
import hljs from "highlight.js/lib/core";
|
|
||||||
// Scripting
|
|
||||||
import javascript from "highlight.js/lib/languages/javascript";
|
|
||||||
import lua from "highlight.js/lib/languages/lua";
|
|
||||||
import python from "highlight.js/lib/languages/python";
|
|
||||||
// Coding
|
|
||||||
import groovy from "highlight.js/lib/languages/groovy";
|
|
||||||
import java from "highlight.js/lib/languages/java";
|
|
||||||
import kotlin from "highlight.js/lib/languages/kotlin";
|
|
||||||
import scala from "highlight.js/lib/languages/scala";
|
|
||||||
// Configs
|
// Configs
|
||||||
import { configuredXss, md } from "@modrinth/utils";
|
import { configuredXss, md } from "@modrinth/utils";
|
||||||
|
import hljs from "highlight.js/lib/core";
|
||||||
import gradle from "highlight.js/lib/languages/gradle";
|
import gradle from "highlight.js/lib/languages/gradle";
|
||||||
|
// Coding
|
||||||
|
import groovy from "highlight.js/lib/languages/groovy";
|
||||||
import ini from "highlight.js/lib/languages/ini";
|
import ini from "highlight.js/lib/languages/ini";
|
||||||
|
import java from "highlight.js/lib/languages/java";
|
||||||
|
// Scripting
|
||||||
|
import javascript from "highlight.js/lib/languages/javascript";
|
||||||
import json from "highlight.js/lib/languages/json";
|
import json from "highlight.js/lib/languages/json";
|
||||||
|
import kotlin from "highlight.js/lib/languages/kotlin";
|
||||||
|
import lua from "highlight.js/lib/languages/lua";
|
||||||
import properties from "highlight.js/lib/languages/properties";
|
import properties from "highlight.js/lib/languages/properties";
|
||||||
|
import python from "highlight.js/lib/languages/python";
|
||||||
|
import scala from "highlight.js/lib/languages/scala";
|
||||||
import xml from "highlight.js/lib/languages/xml";
|
import xml from "highlight.js/lib/languages/xml";
|
||||||
import yaml from "highlight.js/lib/languages/yaml";
|
import yaml from "highlight.js/lib/languages/yaml";
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { parse as parseTOML } from "@ltd/j-toml";
|
import { parse as parseTOML } from "@ltd/j-toml";
|
||||||
import JSZip from "jszip";
|
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
|
import JSZip from "jszip";
|
||||||
import { satisfies } from "semver";
|
import { satisfies } from "semver";
|
||||||
|
|
||||||
export const inferVersionInfo = async function (rawFile, project, gameVersions) {
|
export const inferVersionInfo = async function (rawFile, project, gameVersions) {
|
||||||
@ -139,7 +139,7 @@ export const inferVersionInfo = async function (rawFile, project, gameVersions)
|
|||||||
// ${file.jarVersion} -> Implementation-Version from manifest
|
// ${file.jarVersion} -> Implementation-Version from manifest
|
||||||
const manifestFile = zip.file("META-INF/MANIFEST.MF");
|
const manifestFile = zip.file("META-INF/MANIFEST.MF");
|
||||||
if (
|
if (
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
|
||||||
metadata.mods[0].version.includes("${file.jarVersion}") &&
|
metadata.mods[0].version.includes("${file.jarVersion}") &&
|
||||||
manifestFile !== null
|
manifestFile !== null
|
||||||
) {
|
) {
|
||||||
@ -147,7 +147,7 @@ export const inferVersionInfo = async function (rawFile, project, gameVersions)
|
|||||||
const regex = /Implementation-Version: (.*)$/m;
|
const regex = /Implementation-Version: (.*)$/m;
|
||||||
const match = manifestText.match(regex);
|
const match = manifestText.match(regex);
|
||||||
if (match) {
|
if (match) {
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
|
||||||
versionNum = versionNum.replace("${file.jarVersion}", match[1]);
|
versionNum = versionNum.replace("${file.jarVersion}", match[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import type { ExtendedReport, OwnershipTarget } from "@modrinth/moderation";
|
import type { ExtendedReport, OwnershipTarget } from "@modrinth/moderation";
|
||||||
import type {
|
import type {
|
||||||
Thread,
|
|
||||||
Version,
|
|
||||||
User,
|
|
||||||
Project,
|
|
||||||
TeamMember,
|
|
||||||
Organization,
|
Organization,
|
||||||
|
Project,
|
||||||
Report,
|
Report,
|
||||||
|
TeamMember,
|
||||||
|
Thread,
|
||||||
|
User,
|
||||||
|
Version,
|
||||||
} from "@modrinth/utils";
|
} from "@modrinth/utils";
|
||||||
|
|
||||||
export const useModerationCache = () => ({
|
export const useModerationCache = () => ({
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import JSZip from "jszip";
|
|
||||||
import TOML from "@ltd/j-toml";
|
import TOML from "@ltd/j-toml";
|
||||||
|
import JSZip from "jszip";
|
||||||
|
|
||||||
export const createDataPackVersion = async function (
|
export const createDataPackVersion = async function (
|
||||||
project,
|
project,
|
||||||
@ -141,12 +141,12 @@ export const createDataPackVersion = async function (
|
|||||||
primaryZipReader.file("quilt.mod.json", JSON.stringify(quiltModJson));
|
primaryZipReader.file("quilt.mod.json", JSON.stringify(quiltModJson));
|
||||||
}
|
}
|
||||||
if (loaders.includes("forge")) {
|
if (loaders.includes("forge")) {
|
||||||
primaryZipReader.file("META-INF/mods.toml", TOML.stringify(forgeModsToml, { newline: "\n" })); // eslint-disable-line import/no-named-as-default-member
|
primaryZipReader.file("META-INF/mods.toml", TOML.stringify(forgeModsToml, { newline: "\n" }));
|
||||||
}
|
}
|
||||||
if (loaders.includes("neoforge")) {
|
if (loaders.includes("neoforge")) {
|
||||||
primaryZipReader.file(
|
primaryZipReader.file(
|
||||||
"META-INF/neoforge.mods.toml",
|
"META-INF/neoforge.mods.toml",
|
||||||
TOML.stringify(neoModsToml, { newline: "\n" }), // eslint-disable-line import/no-named-as-default-member
|
TOML.stringify(neoModsToml, { newline: "\n" }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -666,61 +666,61 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
ModrinthIcon,
|
|
||||||
ArrowBigUpDashIcon,
|
ArrowBigUpDashIcon,
|
||||||
BookmarkIcon,
|
|
||||||
ServerIcon,
|
|
||||||
LogInIcon,
|
|
||||||
DownloadIcon,
|
|
||||||
LibraryIcon,
|
|
||||||
XIcon,
|
|
||||||
IssuesIcon,
|
|
||||||
ReportIcon,
|
|
||||||
CompassIcon,
|
|
||||||
HamburgerIcon,
|
|
||||||
SearchIcon,
|
|
||||||
BellIcon,
|
BellIcon,
|
||||||
SettingsIcon,
|
BlueskyIcon,
|
||||||
|
BookmarkIcon,
|
||||||
|
BoxIcon,
|
||||||
|
BracesIcon,
|
||||||
|
ChartIcon,
|
||||||
|
CollectionIcon,
|
||||||
|
CompassIcon,
|
||||||
|
CurrencyIcon,
|
||||||
|
DiscordIcon,
|
||||||
|
DownloadIcon,
|
||||||
|
DropdownIcon,
|
||||||
|
GithubIcon,
|
||||||
|
GlassesIcon,
|
||||||
|
HamburgerIcon,
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
|
IssuesIcon,
|
||||||
|
LibraryIcon,
|
||||||
|
LogInIcon,
|
||||||
|
LogOutIcon,
|
||||||
|
MastodonIcon,
|
||||||
|
ModrinthIcon,
|
||||||
MoonIcon,
|
MoonIcon,
|
||||||
SunIcon,
|
OrganizationIcon,
|
||||||
|
PackageOpenIcon,
|
||||||
|
PaintbrushIcon,
|
||||||
PlugIcon,
|
PlugIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
DropdownIcon,
|
ReportIcon,
|
||||||
LogOutIcon,
|
|
||||||
ChartIcon,
|
|
||||||
BoxIcon,
|
|
||||||
CollectionIcon,
|
|
||||||
OrganizationIcon,
|
|
||||||
UserIcon,
|
|
||||||
CurrencyIcon,
|
|
||||||
BracesIcon,
|
|
||||||
GlassesIcon,
|
|
||||||
PaintbrushIcon,
|
|
||||||
PackageOpenIcon,
|
|
||||||
DiscordIcon,
|
|
||||||
BlueskyIcon,
|
|
||||||
TwitterIcon,
|
|
||||||
MastodonIcon,
|
|
||||||
GithubIcon,
|
|
||||||
ScaleIcon,
|
ScaleIcon,
|
||||||
|
SearchIcon,
|
||||||
|
ServerIcon,
|
||||||
|
SettingsIcon,
|
||||||
|
SunIcon,
|
||||||
|
TwitterIcon,
|
||||||
|
UserIcon,
|
||||||
|
XIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import {
|
import {
|
||||||
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
PagewideBanner,
|
PagewideBanner,
|
||||||
Avatar,
|
|
||||||
commonMessages,
|
|
||||||
} from "@modrinth/ui";
|
} from "@modrinth/ui";
|
||||||
import { isAdmin, isStaff } from "@modrinth/utils";
|
import { isAdmin, isStaff } from "@modrinth/utils";
|
||||||
import { errors as generatedStateErrors } from "~/generated/state.json";
|
|
||||||
|
|
||||||
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
|
||||||
import { getProjectTypeMessage } from "~/utils/i18n-project-type.ts";
|
|
||||||
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
|
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
|
||||||
|
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
||||||
import OrganizationCreateModal from "~/components/ui/OrganizationCreateModal.vue";
|
import OrganizationCreateModal from "~/components/ui/OrganizationCreateModal.vue";
|
||||||
import TeleportOverflowMenu from "~/components/ui/servers/TeleportOverflowMenu.vue";
|
import TeleportOverflowMenu from "~/components/ui/servers/TeleportOverflowMenu.vue";
|
||||||
|
import { errors as generatedStateErrors } from "~/generated/state.json";
|
||||||
|
import { getProjectTypeMessage } from "~/utils/i18n-project-type.ts";
|
||||||
|
|
||||||
const { formatMessage } = useVIntl();
|
const { formatMessage } = useVIntl();
|
||||||
|
|
||||||
|
|||||||
@ -891,6 +891,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
|
AlignLeftIcon as DescriptionIcon,
|
||||||
BookmarkIcon,
|
BookmarkIcon,
|
||||||
BookTextIcon,
|
BookTextIcon,
|
||||||
CalendarIcon,
|
CalendarIcon,
|
||||||
@ -898,14 +899,14 @@ import {
|
|||||||
CheckIcon,
|
CheckIcon,
|
||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
CopyrightIcon,
|
CopyrightIcon,
|
||||||
AlignLeftIcon as DescriptionIcon,
|
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
ExternalIcon,
|
ExternalIcon,
|
||||||
ImageIcon as GalleryIcon,
|
|
||||||
GameIcon,
|
GameIcon,
|
||||||
HeartIcon,
|
HeartIcon,
|
||||||
|
ImageIcon as GalleryIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
LinkIcon as LinksIcon,
|
LinkIcon as LinksIcon,
|
||||||
|
ModrinthIcon,
|
||||||
MoreVerticalIcon,
|
MoreVerticalIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
ReportIcon,
|
ReportIcon,
|
||||||
@ -917,7 +918,6 @@ import {
|
|||||||
UsersIcon,
|
UsersIcon,
|
||||||
VersionIcon,
|
VersionIcon,
|
||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
ModrinthIcon,
|
|
||||||
XIcon,
|
XIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import {
|
import {
|
||||||
@ -935,15 +935,16 @@ import {
|
|||||||
ProjectSidebarLinks,
|
ProjectSidebarLinks,
|
||||||
ProjectStatusBadge,
|
ProjectStatusBadge,
|
||||||
ScrollablePanel,
|
ScrollablePanel,
|
||||||
TagItem,
|
|
||||||
ServersPromo,
|
ServersPromo,
|
||||||
|
TagItem,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
} from "@modrinth/ui";
|
} from "@modrinth/ui";
|
||||||
import VersionSummary from "@modrinth/ui/src/components/version/VersionSummary.vue";
|
import VersionSummary from "@modrinth/ui/src/components/version/VersionSummary.vue";
|
||||||
import { formatCategory, formatProjectType, renderString } from "@modrinth/utils";
|
import { formatCategory, formatProjectType, renderString } from "@modrinth/utils";
|
||||||
|
import { useLocalStorage } from "@vueuse/core";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { Tooltip } from "floating-vue";
|
import { Tooltip } from "floating-vue";
|
||||||
import { useLocalStorage } from "@vueuse/core";
|
|
||||||
import { navigateTo } from "#app";
|
import { navigateTo } from "#app";
|
||||||
import Accordion from "~/components/ui/Accordion.vue";
|
import Accordion from "~/components/ui/Accordion.vue";
|
||||||
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
||||||
@ -951,15 +952,15 @@ import AutomaticAccordion from "~/components/ui/AutomaticAccordion.vue";
|
|||||||
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
|
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
|
||||||
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
|
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
|
||||||
import MessageBanner from "~/components/ui/MessageBanner.vue";
|
import MessageBanner from "~/components/ui/MessageBanner.vue";
|
||||||
|
import ModerationChecklist from "~/components/ui/moderation/checklist/ModerationChecklist.vue";
|
||||||
import NavStack from "~/components/ui/NavStack.vue";
|
import NavStack from "~/components/ui/NavStack.vue";
|
||||||
import NavStackItem from "~/components/ui/NavStackItem.vue";
|
import NavStackItem from "~/components/ui/NavStackItem.vue";
|
||||||
import NavTabs from "~/components/ui/NavTabs.vue";
|
import NavTabs from "~/components/ui/NavTabs.vue";
|
||||||
import ProjectMemberHeader from "~/components/ui/ProjectMemberHeader.vue";
|
import ProjectMemberHeader from "~/components/ui/ProjectMemberHeader.vue";
|
||||||
import { userCollectProject } from "~/composables/user.js";
|
|
||||||
import { reportProject } from "~/utils/report-helpers.ts";
|
|
||||||
import { saveFeatureFlags } from "~/composables/featureFlags.ts";
|
import { saveFeatureFlags } from "~/composables/featureFlags.ts";
|
||||||
import ModerationChecklist from "~/components/ui/moderation/checklist/ModerationChecklist.vue";
|
import { userCollectProject } from "~/composables/user.js";
|
||||||
import { useModerationStore } from "~/store/moderation.ts";
|
import { useModerationStore } from "~/store/moderation.ts";
|
||||||
|
import { reportProject } from "~/utils/report-helpers.ts";
|
||||||
|
|
||||||
const data = useNuxtApp();
|
const data = useNuxtApp();
|
||||||
const route = useNativeRoute();
|
const route = useNativeRoute();
|
||||||
|
|||||||
@ -73,10 +73,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Pagination } from "@modrinth/ui";
|
|
||||||
import { DownloadIcon } from "@modrinth/assets";
|
import { DownloadIcon } from "@modrinth/assets";
|
||||||
|
import { Pagination } from "@modrinth/ui";
|
||||||
import VersionFilterControl from "@modrinth/ui/src/components/version/VersionFilterControl.vue";
|
import VersionFilterControl from "@modrinth/ui/src/components/version/VersionFilterControl.vue";
|
||||||
|
|
||||||
import { renderHighlightedString } from "~/helpers/highlight.js";
|
import { renderHighlightedString } from "~/helpers/highlight.js";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -278,28 +278,28 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
PlusIcon,
|
|
||||||
CalendarIcon,
|
CalendarIcon,
|
||||||
|
ContractIcon,
|
||||||
EditIcon,
|
EditIcon,
|
||||||
TrashIcon,
|
ExpandIcon,
|
||||||
|
ExternalIcon,
|
||||||
|
ImageIcon,
|
||||||
|
InfoIcon,
|
||||||
|
LeftArrowIcon,
|
||||||
|
PlusIcon,
|
||||||
|
RightArrowIcon,
|
||||||
SaveIcon,
|
SaveIcon,
|
||||||
StarIcon,
|
StarIcon,
|
||||||
XIcon,
|
|
||||||
RightArrowIcon,
|
|
||||||
LeftArrowIcon,
|
|
||||||
ExternalIcon,
|
|
||||||
ExpandIcon,
|
|
||||||
ContractIcon,
|
|
||||||
UploadIcon,
|
|
||||||
InfoIcon,
|
|
||||||
ImageIcon,
|
|
||||||
TransferIcon,
|
TransferIcon,
|
||||||
|
TrashIcon,
|
||||||
|
UploadIcon,
|
||||||
|
XIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { ConfirmModal } from "@modrinth/ui";
|
import { ConfirmModal } from "@modrinth/ui";
|
||||||
import FileInput from "~/components/ui/FileInput.vue";
|
|
||||||
import DropArea from "~/components/ui/DropArea.vue";
|
|
||||||
import Modal from "~/components/ui/Modal.vue";
|
|
||||||
|
|
||||||
|
import DropArea from "~/components/ui/DropArea.vue";
|
||||||
|
import FileInput from "~/components/ui/FileInput.vue";
|
||||||
|
import Modal from "~/components/ui/Modal.vue";
|
||||||
import { isPermission } from "~/utils/permissions.ts";
|
import { isPermission } from "~/utils/permissions.ts";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -99,8 +99,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { XIcon, CheckIcon, IssuesIcon } from "@modrinth/assets";
|
import { CheckIcon, IssuesIcon,XIcon } from "@modrinth/assets";
|
||||||
import { Badge } from "@modrinth/ui";
|
import { Badge } from "@modrinth/ui";
|
||||||
|
|
||||||
import ConversationThread from "~/components/ui/thread/ConversationThread.vue";
|
import ConversationThread from "~/components/ui/thread/ConversationThread.vue";
|
||||||
import {
|
import {
|
||||||
getProjectLink,
|
getProjectLink,
|
||||||
|
|||||||
@ -42,6 +42,7 @@ import { SaveIcon } from "@modrinth/assets";
|
|||||||
import { MarkdownEditor } from "@modrinth/ui";
|
import { MarkdownEditor } from "@modrinth/ui";
|
||||||
import { type Project, type TeamMember, TeamMemberPermission } from "@modrinth/utils";
|
import { type Project, type TeamMember, TeamMemberPermission } from "@modrinth/utils";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
import { useImageUpload } from "~/composables/image-upload.ts";
|
import { useImageUpload } from "~/composables/image-upload.ts";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@ -239,10 +239,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { CheckIcon,IssuesIcon, SaveIcon, TrashIcon, UploadIcon, XIcon } from "@modrinth/assets";
|
||||||
|
import { Avatar,ConfirmModal } from "@modrinth/ui";
|
||||||
import { formatProjectStatus, formatProjectType } from "@modrinth/utils";
|
import { formatProjectStatus, formatProjectType } from "@modrinth/utils";
|
||||||
import { UploadIcon, SaveIcon, TrashIcon, XIcon, IssuesIcon, CheckIcon } from "@modrinth/assets";
|
|
||||||
import { Multiselect } from "vue-multiselect";
|
import { Multiselect } from "vue-multiselect";
|
||||||
import { ConfirmModal, Avatar } from "@modrinth/ui";
|
|
||||||
import FileInput from "~/components/ui/FileInput.vue";
|
import FileInput from "~/components/ui/FileInput.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="adjacent-input" v-if="license.requiresOnlyOrLater">
|
<div v-if="license.requiresOnlyOrLater" class="adjacent-input">
|
||||||
<label for="or-later-checkbox">
|
<label for="or-later-checkbox">
|
||||||
<span class="label__title">Later editions</span>
|
<span class="label__title">Later editions</span>
|
||||||
<span class="label__description">
|
<span class="label__description">
|
||||||
@ -60,11 +60,11 @@
|
|||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label for="license-url">
|
<label for="license-url">
|
||||||
<span class="label__title">License URL</span>
|
<span class="label__title">License URL</span>
|
||||||
<span class="label__description" v-if="license?.friendly !== 'Custom'">
|
<span v-if="license?.friendly !== 'Custom'" class="label__description">
|
||||||
The web location of the full license text. If you don't provide a link, the license text
|
The web location of the full license text. If you don't provide a link, the license text
|
||||||
will be displayed instead.
|
will be displayed instead.
|
||||||
</span>
|
</span>
|
||||||
<span class="label__description" v-else>
|
<span v-else class="label__description">
|
||||||
The web location of the full license text. You have to provide a link since this is a
|
The web location of the full license text. You have to provide a link since this is a
|
||||||
custom license.
|
custom license.
|
||||||
</span>
|
</span>
|
||||||
@ -83,8 +83,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="adjacent-input" v-if="license?.friendly === 'Custom'">
|
<div v-if="license?.friendly === 'Custom'" class="adjacent-input">
|
||||||
<label for="license-spdx" v-if="!nonSpdxLicense">
|
<label v-if="!nonSpdxLicense" for="license-spdx">
|
||||||
<span class="label__title">SPDX identifier</span>
|
<span class="label__title">SPDX identifier</span>
|
||||||
<span class="label__description">
|
<span class="label__description">
|
||||||
If your license does not have an offical
|
If your license does not have an offical
|
||||||
@ -93,7 +93,7 @@
|
|||||||
>, check the box and enter the name of the license instead.
|
>, check the box and enter the name of the license instead.
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label for="license-name" v-else>
|
<label v-else for="license-name">
|
||||||
<span class="label__title">License name</span>
|
<span class="label__title">License name</span>
|
||||||
<span class="label__description"
|
<span class="label__description"
|
||||||
>The full name of the license. If the license has a SPDX identifier, please uncheck the
|
>The full name of the license. If the license has a SPDX identifier, please uncheck the
|
||||||
@ -104,8 +104,8 @@
|
|||||||
<div class="input-stack w-1/2">
|
<div class="input-stack w-1/2">
|
||||||
<input
|
<input
|
||||||
v-if="!nonSpdxLicense"
|
v-if="!nonSpdxLicense"
|
||||||
v-model="license.short"
|
|
||||||
id="license-spdx"
|
id="license-spdx"
|
||||||
|
v-model="license.short"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
type="text"
|
type="text"
|
||||||
maxlength="128"
|
maxlength="128"
|
||||||
@ -114,8 +114,8 @@
|
|||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
v-else
|
v-else
|
||||||
v-model="license.short"
|
|
||||||
id="license-name"
|
id="license-name"
|
||||||
|
v-model="license.short"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
type="text"
|
type="text"
|
||||||
maxlength="128"
|
maxlength="128"
|
||||||
@ -154,22 +154,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Checkbox, DropdownSelect } from "@modrinth/ui";
|
|
||||||
import { SaveIcon } from "@modrinth/assets";
|
import { SaveIcon } from "@modrinth/assets";
|
||||||
|
import { Checkbox, DropdownSelect } from "@modrinth/ui";
|
||||||
import {
|
import {
|
||||||
TeamMemberPermission,
|
type BuiltinLicense,
|
||||||
builtinLicenses,
|
builtinLicenses,
|
||||||
formatProjectType,
|
formatProjectType,
|
||||||
type BuiltinLicense,
|
|
||||||
type Project,
|
type Project,
|
||||||
type TeamMember,
|
type TeamMember,
|
||||||
|
TeamMemberPermission,
|
||||||
} from "@modrinth/utils";
|
} from "@modrinth/utils";
|
||||||
import { computed, ref, type Ref } from "vue";
|
import { computed, type Ref,ref } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
project: Project;
|
project: Project;
|
||||||
currentMember: TeamMember | undefined;
|
currentMember: TeamMember | undefined;
|
||||||
patchProject: (payload: Object, quiet?: boolean) => Object;
|
patchProject: (payload: object, quiet?: boolean) => object;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const licenseUrl = ref(props.project.license.url);
|
const licenseUrl = ref(props.project.license.url);
|
||||||
@ -215,7 +215,7 @@ const licenseId = computed(() => {
|
|||||||
let id = "";
|
let id = "";
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(nonSpdxLicense && license.value.friendly === "Custom") ||
|
(nonSpdxLicense.value && license.value.friendly === "Custom") ||
|
||||||
license.value.short === "All-Rights-Reserved" ||
|
license.value.short === "All-Rights-Reserved" ||
|
||||||
license.value.short === "Unknown"
|
license.value.short === "Unknown"
|
||||||
) {
|
) {
|
||||||
@ -227,7 +227,7 @@ const licenseId = computed(() => {
|
|||||||
id += allowOrLater.value ? "-or-later" : "-only";
|
id += allowOrLater.value ? "-or-later" : "-only";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nonSpdxLicense && license.value.friendly === "Custom") {
|
if (nonSpdxLicense.value && license.value.friendly === "Custom") {
|
||||||
id = id.replaceAll(" ", "-");
|
id = id.replaceAll(" ", "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -122,8 +122,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { DropdownSelect } from "@modrinth/ui";
|
|
||||||
import { SaveIcon } from "@modrinth/assets";
|
import { SaveIcon } from "@modrinth/assets";
|
||||||
|
import { DropdownSelect } from "@modrinth/ui";
|
||||||
|
|
||||||
const tags = useTags();
|
const tags = useTags();
|
||||||
|
|
||||||
|
|||||||
@ -518,19 +518,20 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Multiselect } from "vue-multiselect";
|
|
||||||
import {
|
import {
|
||||||
TransferIcon,
|
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
UsersIcon,
|
|
||||||
DropdownIcon,
|
|
||||||
SaveIcon,
|
|
||||||
UserPlusIcon,
|
|
||||||
UserXIcon,
|
|
||||||
OrganizationIcon,
|
|
||||||
CrownIcon,
|
CrownIcon,
|
||||||
|
DropdownIcon,
|
||||||
|
OrganizationIcon,
|
||||||
|
SaveIcon,
|
||||||
|
TransferIcon,
|
||||||
|
UserPlusIcon,
|
||||||
|
UsersIcon,
|
||||||
|
UserXIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { Avatar, Badge, Card, Checkbox, ConfirmModal } from "@modrinth/ui";
|
import { Avatar, Badge, Card, Checkbox, ConfirmModal } from "@modrinth/ui";
|
||||||
|
import { Multiselect } from "vue-multiselect";
|
||||||
|
|
||||||
import { removeSelfFromTeam } from "~/helpers/teams.js";
|
import { removeSelfFromTeam } from "~/helpers/teams.js";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -113,8 +113,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { StarIcon, SaveIcon } from "@modrinth/assets";
|
import { SaveIcon,StarIcon } from "@modrinth/assets";
|
||||||
import { formatCategory, formatCategoryHeader, formatProjectType } from "@modrinth/utils";
|
import { formatCategory, formatCategoryHeader, formatProjectType } from "@modrinth/utils";
|
||||||
|
|
||||||
import Checkbox from "~/components/ui/Checkbox.vue";
|
import Checkbox from "~/components/ui/Checkbox.vue";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
export default defineNuxtComponent({
|
||||||
|
|||||||
@ -631,45 +631,45 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
Avatar,
|
BoxIcon,
|
||||||
Badge,
|
ChevronRightIcon,
|
||||||
CopyCode,
|
|
||||||
Checkbox,
|
|
||||||
ButtonStyled,
|
|
||||||
ConfirmModal,
|
|
||||||
MarkdownEditor,
|
|
||||||
} from "@modrinth/ui";
|
|
||||||
import {
|
|
||||||
FileIcon,
|
|
||||||
TrashIcon,
|
|
||||||
EditIcon,
|
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
StarIcon,
|
EditIcon,
|
||||||
ReportIcon,
|
FileIcon,
|
||||||
SaveIcon,
|
|
||||||
XIcon,
|
|
||||||
HashIcon,
|
HashIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
TransferIcon,
|
ReportIcon,
|
||||||
UploadIcon,
|
|
||||||
BoxIcon,
|
|
||||||
RightArrowIcon,
|
RightArrowIcon,
|
||||||
ChevronRightIcon,
|
SaveIcon,
|
||||||
|
StarIcon,
|
||||||
|
TransferIcon,
|
||||||
|
TrashIcon,
|
||||||
|
UploadIcon,
|
||||||
|
XIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { Multiselect } from "vue-multiselect";
|
import {
|
||||||
|
Avatar,
|
||||||
|
Badge,
|
||||||
|
ButtonStyled,
|
||||||
|
Checkbox,
|
||||||
|
ConfirmModal,
|
||||||
|
CopyCode,
|
||||||
|
MarkdownEditor,
|
||||||
|
} from "@modrinth/ui";
|
||||||
import { formatBytes, formatCategory } from "@modrinth/utils";
|
import { formatBytes, formatCategory } from "@modrinth/utils";
|
||||||
import { acceptFileFromProjectType } from "~/helpers/fileUtils.js";
|
import { Multiselect } from "vue-multiselect";
|
||||||
import { inferVersionInfo } from "~/helpers/infer.js";
|
|
||||||
import { createDataPackVersion } from "~/helpers/package.js";
|
|
||||||
import { renderHighlightedString } from "~/helpers/highlight.js";
|
|
||||||
import { reportVersion } from "~/utils/report-helpers.ts";
|
|
||||||
import { useImageUpload } from "~/composables/image-upload.ts";
|
|
||||||
|
|
||||||
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
||||||
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
|
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
|
||||||
import Categories from "~/components/ui/search/Categories.vue";
|
|
||||||
import FileInput from "~/components/ui/FileInput.vue";
|
import FileInput from "~/components/ui/FileInput.vue";
|
||||||
import Modal from "~/components/ui/Modal.vue";
|
import Modal from "~/components/ui/Modal.vue";
|
||||||
|
import Categories from "~/components/ui/search/Categories.vue";
|
||||||
|
import { useImageUpload } from "~/composables/image-upload.ts";
|
||||||
|
import { acceptFileFromProjectType } from "~/helpers/fileUtils.js";
|
||||||
|
import { renderHighlightedString } from "~/helpers/highlight.js";
|
||||||
|
import { inferVersionInfo } from "~/helpers/infer.js";
|
||||||
|
import { createDataPackVersion } from "~/helpers/package.js";
|
||||||
|
import { reportVersion } from "~/utils/report-helpers.ts";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
export default defineNuxtComponent({
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user