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