You've already forked AstralRinth
forked from didirus/AstralRinth
Merge remote-tracking branch 'upstream/main' into beta
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['custom/library'],
|
||||
ignorePatterns: ['**/*.scss', '**/*.svg', 'node_modules/', 'dist/', '**/*.gltf'],
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
}
|
||||
@@ -2,210 +2,210 @@ import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
function toPascalCase(str: string): string {
|
||||
return str
|
||||
.split(/[-_.]/)
|
||||
.filter((part) => part.length > 0)
|
||||
.map((word) => {
|
||||
if (/^\d/.test(word)) {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1)
|
||||
}
|
||||
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
||||
})
|
||||
.join('')
|
||||
return str
|
||||
.split(/[-_.]/)
|
||||
.filter((part) => part.length > 0)
|
||||
.map((word) => {
|
||||
if (/^\d/.test(word)) {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1)
|
||||
}
|
||||
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
||||
})
|
||||
.join('')
|
||||
}
|
||||
|
||||
function generateIconExports(): { imports: string; exports: string } {
|
||||
const packageRoot = path.resolve(__dirname, '..')
|
||||
const iconsDir = path.join(packageRoot, 'icons')
|
||||
const packageRoot = path.resolve(__dirname, '..')
|
||||
const iconsDir = path.join(packageRoot, 'icons')
|
||||
|
||||
if (!fs.existsSync(iconsDir)) {
|
||||
throw new Error(`Icons directory not found: ${iconsDir}`)
|
||||
}
|
||||
if (!fs.existsSync(iconsDir)) {
|
||||
throw new Error(`Icons directory not found: ${iconsDir}`)
|
||||
}
|
||||
|
||||
const files = fs
|
||||
.readdirSync(iconsDir)
|
||||
.filter((file) => file.endsWith('.svg'))
|
||||
.sort()
|
||||
const files = fs
|
||||
.readdirSync(iconsDir)
|
||||
.filter((file) => file.endsWith('.svg'))
|
||||
.sort()
|
||||
|
||||
let imports = ''
|
||||
let exports = ''
|
||||
let imports = ''
|
||||
let exports = ''
|
||||
|
||||
files.forEach((file) => {
|
||||
const baseName = path.basename(file, '.svg')
|
||||
let pascalName = toPascalCase(baseName)
|
||||
files.forEach((file) => {
|
||||
const baseName = path.basename(file, '.svg')
|
||||
let pascalName = toPascalCase(baseName)
|
||||
|
||||
if (pascalName === '') {
|
||||
pascalName = 'Unknown'
|
||||
}
|
||||
if (pascalName === '') {
|
||||
pascalName = 'Unknown'
|
||||
}
|
||||
|
||||
if (!pascalName.endsWith('Icon')) {
|
||||
pascalName += 'Icon'
|
||||
}
|
||||
if (!pascalName.endsWith('Icon')) {
|
||||
pascalName += 'Icon'
|
||||
}
|
||||
|
||||
const privateName = `_${pascalName}`
|
||||
const privateName = `_${pascalName}`
|
||||
|
||||
imports += `import ${privateName} from './icons/${file}?component'\n`
|
||||
exports += `export const ${pascalName} = ${privateName}\n`
|
||||
})
|
||||
imports += `import ${privateName} from './icons/${file}?component'\n`
|
||||
exports += `export const ${pascalName} = ${privateName}\n`
|
||||
})
|
||||
|
||||
return { imports, exports }
|
||||
return { imports, exports }
|
||||
}
|
||||
|
||||
function runTests(): void {
|
||||
console.log('🧪 Running conversion tests...\n')
|
||||
console.log('🧪 Running conversion tests...\n')
|
||||
|
||||
const testCases: Array<{ input: string; expected: string }> = [
|
||||
{ input: 'align-left', expected: 'AlignLeftIcon' },
|
||||
{ input: 'arrow-big-up-dash', expected: 'ArrowBigUpDashIcon' },
|
||||
{ input: 'check-check', expected: 'CheckCheckIcon' },
|
||||
{ input: 'chevron-left', expected: 'ChevronLeftIcon' },
|
||||
{ input: 'file-archive', expected: 'FileArchiveIcon' },
|
||||
{ input: 'heart-handshake', expected: 'HeartHandshakeIcon' },
|
||||
{ input: 'monitor-smartphone', expected: 'MonitorSmartphoneIcon' },
|
||||
{ input: 'x-circle', expected: 'XCircleIcon' },
|
||||
{ input: 'rotate-ccw', expected: 'RotateCcwIcon' },
|
||||
{ input: 'bell-ring', expected: 'BellRingIcon' },
|
||||
{ input: 'more-horizontal', expected: 'MoreHorizontalIcon' },
|
||||
{ input: 'list_bulleted', expected: 'ListBulletedIcon' },
|
||||
{ input: 'test.name', expected: 'TestNameIcon' },
|
||||
{ input: 'test-name_final.icon', expected: 'TestNameFinalIcon' },
|
||||
]
|
||||
const testCases: Array<{ input: string; expected: string }> = [
|
||||
{ input: 'align-left', expected: 'AlignLeftIcon' },
|
||||
{ input: 'arrow-big-up-dash', expected: 'ArrowBigUpDashIcon' },
|
||||
{ input: 'check-check', expected: 'CheckCheckIcon' },
|
||||
{ input: 'chevron-left', expected: 'ChevronLeftIcon' },
|
||||
{ input: 'file-archive', expected: 'FileArchiveIcon' },
|
||||
{ input: 'heart-handshake', expected: 'HeartHandshakeIcon' },
|
||||
{ input: 'monitor-smartphone', expected: 'MonitorSmartphoneIcon' },
|
||||
{ input: 'x-circle', expected: 'XCircleIcon' },
|
||||
{ input: 'rotate-ccw', expected: 'RotateCcwIcon' },
|
||||
{ input: 'bell-ring', expected: 'BellRingIcon' },
|
||||
{ input: 'more-horizontal', expected: 'MoreHorizontalIcon' },
|
||||
{ input: 'list_bulleted', expected: 'ListBulletedIcon' },
|
||||
{ input: 'test.name', expected: 'TestNameIcon' },
|
||||
{ input: 'test-name_final.icon', expected: 'TestNameFinalIcon' },
|
||||
]
|
||||
|
||||
let passed = 0
|
||||
let failed = 0
|
||||
let passed = 0
|
||||
let failed = 0
|
||||
|
||||
testCases.forEach(({ input, expected }) => {
|
||||
const result = toPascalCase(input) + (toPascalCase(input).endsWith('Icon') ? '' : 'Icon')
|
||||
const success = result === expected
|
||||
testCases.forEach(({ input, expected }) => {
|
||||
const result = toPascalCase(input) + (toPascalCase(input).endsWith('Icon') ? '' : 'Icon')
|
||||
const success = result === expected
|
||||
|
||||
if (success) {
|
||||
console.log(`✅ ${input} → ${result}`)
|
||||
passed++
|
||||
} else {
|
||||
console.log(`❌ ${input} → ${result} (expected: ${expected})`)
|
||||
failed++
|
||||
}
|
||||
})
|
||||
if (success) {
|
||||
console.log(`✅ ${input} → ${result}`)
|
||||
passed++
|
||||
} else {
|
||||
console.log(`❌ ${input} → ${result} (expected: ${expected})`)
|
||||
failed++
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`\n📊 Test Results: ${passed} passed, ${failed} failed`)
|
||||
console.log(`\n📊 Test Results: ${passed} passed, ${failed} failed`)
|
||||
|
||||
if (failed > 0) {
|
||||
process.exit(1)
|
||||
}
|
||||
if (failed > 0) {
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
function generateFiles(): void {
|
||||
try {
|
||||
console.log('🔄 Generating icon exports...')
|
||||
try {
|
||||
console.log('🔄 Generating icon exports...')
|
||||
|
||||
const { imports, exports } = generateIconExports()
|
||||
const output = `// Auto-generated icon imports and exports
|
||||
const { imports, exports } = generateIconExports()
|
||||
const output = `// Auto-generated icon imports and exports
|
||||
// Do not edit this file manually - run 'pnpm run fix' to regenerate
|
||||
|
||||
${imports}
|
||||
${exports}`
|
||||
|
||||
const packageRoot = path.resolve(__dirname, '..')
|
||||
const outputPath = path.join(packageRoot, 'generated-icons.ts')
|
||||
fs.writeFileSync(outputPath, output)
|
||||
const packageRoot = path.resolve(__dirname, '..')
|
||||
const outputPath = path.join(packageRoot, 'generated-icons.ts')
|
||||
fs.writeFileSync(outputPath, output)
|
||||
|
||||
console.log(`✅ Generated icon exports to: ${outputPath}`)
|
||||
console.log(
|
||||
`📦 Generated ${imports.split('\n').filter((line) => line.trim()).length} icon imports/exports`,
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('❌ Error generating icons:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`✅ Generated icon exports to: ${outputPath}`)
|
||||
console.log(
|
||||
`📦 Generated ${imports.split('\n').filter((line) => line.trim()).length} icon imports/exports`,
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('❌ Error generating icons:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
const args = process.argv.slice(2)
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
if (args.includes('--test')) {
|
||||
runTests()
|
||||
} else if (args.includes('--validate')) {
|
||||
validateIconConsistency()
|
||||
} else {
|
||||
generateFiles()
|
||||
}
|
||||
if (args.includes('--test')) {
|
||||
runTests()
|
||||
} else if (args.includes('--validate')) {
|
||||
validateIconConsistency()
|
||||
} else {
|
||||
generateFiles()
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
function getExpectedIconExports(iconsDir: string): string[] {
|
||||
if (!fs.existsSync(iconsDir)) {
|
||||
return []
|
||||
}
|
||||
if (!fs.existsSync(iconsDir)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return fs
|
||||
.readdirSync(iconsDir)
|
||||
.filter((file) => file.endsWith('.svg'))
|
||||
.map((file) => {
|
||||
const baseName = path.basename(file, '.svg')
|
||||
let pascalName = toPascalCase(baseName)
|
||||
return fs
|
||||
.readdirSync(iconsDir)
|
||||
.filter((file) => file.endsWith('.svg'))
|
||||
.map((file) => {
|
||||
const baseName = path.basename(file, '.svg')
|
||||
let pascalName = toPascalCase(baseName)
|
||||
|
||||
if (pascalName === '') {
|
||||
pascalName = 'Unknown'
|
||||
}
|
||||
if (pascalName === '') {
|
||||
pascalName = 'Unknown'
|
||||
}
|
||||
|
||||
if (!pascalName.endsWith('Icon')) {
|
||||
pascalName += 'Icon'
|
||||
}
|
||||
if (!pascalName.endsWith('Icon')) {
|
||||
pascalName += 'Icon'
|
||||
}
|
||||
|
||||
return pascalName
|
||||
})
|
||||
.sort()
|
||||
return pascalName
|
||||
})
|
||||
.sort()
|
||||
}
|
||||
|
||||
function getActualIconExports(indexFile: string): string[] {
|
||||
if (!fs.existsSync(indexFile)) {
|
||||
return []
|
||||
}
|
||||
if (!fs.existsSync(indexFile)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(indexFile, 'utf8')
|
||||
const exportMatches = content.match(/export const (\w+Icon) = _\w+Icon/g) || []
|
||||
const content = fs.readFileSync(indexFile, 'utf8')
|
||||
const exportMatches = content.match(/export const (\w+Icon) = _\w+Icon/g) || []
|
||||
|
||||
return exportMatches
|
||||
.map((match) => {
|
||||
const result = match.match(/export const (\w+Icon)/)
|
||||
return result ? result[1] : ''
|
||||
})
|
||||
.filter((name) => name.endsWith('Icon'))
|
||||
.sort()
|
||||
return exportMatches
|
||||
.map((match) => {
|
||||
const result = match.match(/export const (\w+Icon)/)
|
||||
return result ? result[1] : ''
|
||||
})
|
||||
.filter((name) => name.endsWith('Icon'))
|
||||
.sort()
|
||||
}
|
||||
|
||||
function validateIconConsistency(): void {
|
||||
try {
|
||||
console.log('🔍 Validating icon consistency...')
|
||||
try {
|
||||
console.log('🔍 Validating icon consistency...')
|
||||
|
||||
const packageRoot = path.resolve(__dirname, '..')
|
||||
const iconsDir = path.join(packageRoot, 'icons')
|
||||
const declarationFile = path.join(packageRoot, 'generated-icons.ts')
|
||||
const packageRoot = path.resolve(__dirname, '..')
|
||||
const iconsDir = path.join(packageRoot, 'icons')
|
||||
const declarationFile = path.join(packageRoot, 'generated-icons.ts')
|
||||
|
||||
const expectedExports = getExpectedIconExports(iconsDir)
|
||||
const actualExports = getActualIconExports(declarationFile)
|
||||
const expectedExports = getExpectedIconExports(iconsDir)
|
||||
const actualExports = getActualIconExports(declarationFile)
|
||||
|
||||
const missingExports = expectedExports.filter((name) => !actualExports.includes(name))
|
||||
const extraExports = actualExports.filter((name) => !expectedExports.includes(name))
|
||||
const missingExports = expectedExports.filter((name) => !actualExports.includes(name))
|
||||
const extraExports = actualExports.filter((name) => !expectedExports.includes(name))
|
||||
|
||||
if (missingExports.length > 0) {
|
||||
console.error(`❌ Missing icon exports: ${missingExports.join(', ')}`)
|
||||
console.error("Run 'pnpm run fix' to generate them.")
|
||||
process.exit(1)
|
||||
}
|
||||
if (missingExports.length > 0) {
|
||||
console.error(`❌ Missing icon exports: ${missingExports.join(', ')}`)
|
||||
console.error("Run 'pnpm run fix' to generate them.")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (extraExports.length > 0) {
|
||||
console.error(
|
||||
`❌ Extra icon exports (no corresponding SVG files): ${extraExports.join(', ')}`,
|
||||
)
|
||||
console.error("Run 'pnpm run fix' to clean them up.")
|
||||
process.exit(1)
|
||||
}
|
||||
if (extraExports.length > 0) {
|
||||
console.error(
|
||||
`❌ Extra icon exports (no corresponding SVG files): ${extraExports.join(', ')}`,
|
||||
)
|
||||
console.error("Run 'pnpm run fix' to clean them up.")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('✅ Icon exports are consistent with SVG files')
|
||||
} catch (error) {
|
||||
console.error('❌ Error validating icons:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('✅ Icon exports are consistent with SVG files')
|
||||
} catch (error) {
|
||||
console.error('❌ Error validating icons:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
2
packages/assets/eslint.config.mjs
Normal file
2
packages/assets/eslint.config.mjs
Normal file
@@ -0,0 +1,2 @@
|
||||
import config from '@modrinth/tooling-config/eslint/nuxt.mjs'
|
||||
export default config
|
||||
@@ -8,26 +8,26 @@ import _ArrowBigUpDashIcon from './icons/arrow-big-up-dash.svg?component'
|
||||
import _AsteriskIcon from './icons/asterisk.svg?component'
|
||||
import _BadgeCheckIcon from './icons/badge-check.svg?component'
|
||||
import _BanIcon from './icons/ban.svg?component'
|
||||
import _BellRingIcon from './icons/bell-ring.svg?component'
|
||||
import _BellIcon from './icons/bell.svg?component'
|
||||
import _BellRingIcon from './icons/bell-ring.svg?component'
|
||||
import _BlocksIcon from './icons/blocks.svg?component'
|
||||
import _BoldIcon from './icons/bold.svg?component'
|
||||
import _BookIcon from './icons/book.svg?component'
|
||||
import _BookOpenIcon from './icons/book-open.svg?component'
|
||||
import _BookTextIcon from './icons/book-text.svg?component'
|
||||
import _BookIcon from './icons/book.svg?component'
|
||||
import _BookmarkIcon from './icons/bookmark.svg?component'
|
||||
import _BotIcon from './icons/bot.svg?component'
|
||||
import _BoxImportIcon from './icons/box-import.svg?component'
|
||||
import _BoxIcon from './icons/box.svg?component'
|
||||
import _BoxImportIcon from './icons/box-import.svg?component'
|
||||
import _BracesIcon from './icons/braces.svg?component'
|
||||
import _BrushCleaningIcon from './icons/brush-cleaning.svg?component'
|
||||
import _CalendarIcon from './icons/calendar.svg?component'
|
||||
import _CardIcon from './icons/card.svg?component'
|
||||
import _ChangeSkinIcon from './icons/change-skin.svg?component'
|
||||
import _ChartIcon from './icons/chart.svg?component'
|
||||
import _CheckIcon from './icons/check.svg?component'
|
||||
import _CheckCheckIcon from './icons/check-check.svg?component'
|
||||
import _CheckCircleIcon from './icons/check-circle.svg?component'
|
||||
import _CheckIcon from './icons/check.svg?component'
|
||||
import _ChevronLeftIcon from './icons/chevron-left.svg?component'
|
||||
import _ChevronRightIcon from './icons/chevron-right.svg?component'
|
||||
import _ClearIcon from './icons/clear.svg?component'
|
||||
@@ -56,13 +56,13 @@ import _EditIcon from './icons/edit.svg?component'
|
||||
import _EllipsisVerticalIcon from './icons/ellipsis-vertical.svg?component'
|
||||
import _ExpandIcon from './icons/expand.svg?component'
|
||||
import _ExternalIcon from './icons/external.svg?component'
|
||||
import _EyeOffIcon from './icons/eye-off.svg?component'
|
||||
import _EyeIcon from './icons/eye.svg?component'
|
||||
import _EyeOffIcon from './icons/eye-off.svg?component'
|
||||
import _FileIcon from './icons/file.svg?component'
|
||||
import _FileArchiveIcon from './icons/file-archive.svg?component'
|
||||
import _FileTextIcon from './icons/file-text.svg?component'
|
||||
import _FileIcon from './icons/file.svg?component'
|
||||
import _FilterXIcon from './icons/filter-x.svg?component'
|
||||
import _FilterIcon from './icons/filter.svg?component'
|
||||
import _FilterXIcon from './icons/filter-x.svg?component'
|
||||
import _FolderArchiveIcon from './icons/folder-archive.svg?component'
|
||||
import _FolderOpenIcon from './icons/folder-open.svg?component'
|
||||
import _FolderSearchIcon from './icons/folder-search.svg?component'
|
||||
@@ -79,8 +79,8 @@ import _HashIcon from './icons/hash.svg?component'
|
||||
import _Heading1Icon from './icons/heading-1.svg?component'
|
||||
import _Heading2Icon from './icons/heading-2.svg?component'
|
||||
import _Heading3Icon from './icons/heading-3.svg?component'
|
||||
import _HeartHandshakeIcon from './icons/heart-handshake.svg?component'
|
||||
import _HeartIcon from './icons/heart.svg?component'
|
||||
import _HeartHandshakeIcon from './icons/heart-handshake.svg?component'
|
||||
import _HistoryIcon from './icons/history.svg?component'
|
||||
import _HomeIcon from './icons/home.svg?component'
|
||||
import _ImageIcon from './icons/image.svg?component'
|
||||
@@ -96,13 +96,13 @@ import _LeftArrowIcon from './icons/left-arrow.svg?component'
|
||||
import _LibraryIcon from './icons/library.svg?component'
|
||||
import _LightBulbIcon from './icons/light-bulb.svg?component'
|
||||
import _LinkIcon from './icons/link.svg?component'
|
||||
import _ListIcon from './icons/list.svg?component'
|
||||
import _ListBulletedIcon from './icons/list-bulleted.svg?component'
|
||||
import _ListEndIcon from './icons/list-end.svg?component'
|
||||
import _ListOrderedIcon from './icons/list-ordered.svg?component'
|
||||
import _ListIcon from './icons/list.svg?component'
|
||||
import _LoaderIcon from './icons/loader.svg?component'
|
||||
import _LockOpenIcon from './icons/lock-open.svg?component'
|
||||
import _LockIcon from './icons/lock.svg?component'
|
||||
import _LockOpenIcon from './icons/lock-open.svg?component'
|
||||
import _LogInIcon from './icons/log-in.svg?component'
|
||||
import _LogOutIcon from './icons/log-out.svg?component'
|
||||
import _MailIcon from './icons/mail.svg?component'
|
||||
@@ -113,25 +113,26 @@ import _MessageIcon from './icons/message.svg?component'
|
||||
import _MicrophoneIcon from './icons/microphone.svg?component'
|
||||
import _MinimizeIcon from './icons/minimize.svg?component'
|
||||
import _MinusIcon from './icons/minus.svg?component'
|
||||
import _MonitorSmartphoneIcon from './icons/monitor-smartphone.svg?component'
|
||||
import _MonitorIcon from './icons/monitor.svg?component'
|
||||
import _MonitorSmartphoneIcon from './icons/monitor-smartphone.svg?component'
|
||||
import _MoonIcon from './icons/moon.svg?component'
|
||||
import _MoreHorizontalIcon from './icons/more-horizontal.svg?component'
|
||||
import _MoreVerticalIcon from './icons/more-vertical.svg?component'
|
||||
import _NewspaperIcon from './icons/newspaper.svg?component'
|
||||
import _NoSignalIcon from './icons/no-signal.svg?component'
|
||||
import _NotepadTextIcon from './icons/notepad-text.svg?component'
|
||||
import _OmorphiaIcon from './icons/omorphia.svg?component'
|
||||
import _OrganizationIcon from './icons/organization.svg?component'
|
||||
import _PackageIcon from './icons/package.svg?component'
|
||||
import _PackageClosedIcon from './icons/package-closed.svg?component'
|
||||
import _PackageOpenIcon from './icons/package-open.svg?component'
|
||||
import _PackageIcon from './icons/package.svg?component'
|
||||
import _PaintbrushIcon from './icons/paintbrush.svg?component'
|
||||
import _PickaxeIcon from './icons/pickaxe.svg?component'
|
||||
import _PlayIcon from './icons/play.svg?component'
|
||||
import _PlugIcon from './icons/plug.svg?component'
|
||||
import _PlusIcon from './icons/plus.svg?component'
|
||||
import _RadioButtonCheckedIcon from './icons/radio-button-checked.svg?component'
|
||||
import _RadioButtonIcon from './icons/radio-button.svg?component'
|
||||
import _RadioButtonCheckedIcon from './icons/radio-button-checked.svg?component'
|
||||
import _ReceiptTextIcon from './icons/receipt-text.svg?component'
|
||||
import _RedoIcon from './icons/redo.svg?component'
|
||||
import _ReplyIcon from './icons/reply.svg?component'
|
||||
@@ -146,8 +147,8 @@ import _ScaleIcon from './icons/scale.svg?component'
|
||||
import _ScanEyeIcon from './icons/scan-eye.svg?component'
|
||||
import _SearchIcon from './icons/search.svg?component'
|
||||
import _SendIcon from './icons/send.svg?component'
|
||||
import _ServerPlusIcon from './icons/server-plus.svg?component'
|
||||
import _ServerIcon from './icons/server.svg?component'
|
||||
import _ServerPlusIcon from './icons/server-plus.svg?component'
|
||||
import _SettingsIcon from './icons/settings.svg?component'
|
||||
import _ShareIcon from './icons/share.svg?component'
|
||||
import _ShieldIcon from './icons/shield.svg?component'
|
||||
@@ -176,23 +177,23 @@ import _TrashIcon from './icons/trash.svg?component'
|
||||
import _TriangleAlertIcon from './icons/triangle-alert.svg?component'
|
||||
import _UnderlineIcon from './icons/underline.svg?component'
|
||||
import _UndoIcon from './icons/undo.svg?component'
|
||||
import _UnknownDonationIcon from './icons/unknown-donation.svg?component'
|
||||
import _UnknownIcon from './icons/unknown.svg?component'
|
||||
import _UnknownDonationIcon from './icons/unknown-donation.svg?component'
|
||||
import _UnlinkIcon from './icons/unlink.svg?component'
|
||||
import _UnplugIcon from './icons/unplug.svg?component'
|
||||
import _UpdatedIcon from './icons/updated.svg?component'
|
||||
import _UploadIcon from './icons/upload.svg?component'
|
||||
import _UserIcon from './icons/user.svg?component'
|
||||
import _UserPlusIcon from './icons/user-plus.svg?component'
|
||||
import _UserXIcon from './icons/user-x.svg?component'
|
||||
import _UserIcon from './icons/user.svg?component'
|
||||
import _UsersIcon from './icons/users.svg?component'
|
||||
import _VersionIcon from './icons/version.svg?component'
|
||||
import _WikiIcon from './icons/wiki.svg?component'
|
||||
import _WindowIcon from './icons/window.svg?component'
|
||||
import _WorldIcon from './icons/world.svg?component'
|
||||
import _WrenchIcon from './icons/wrench.svg?component'
|
||||
import _XCircleIcon from './icons/x-circle.svg?component'
|
||||
import _XIcon from './icons/x.svg?component'
|
||||
import _XCircleIcon from './icons/x-circle.svg?component'
|
||||
import _ZoomInIcon from './icons/zoom-in.svg?component'
|
||||
import _ZoomOutIcon from './icons/zoom-out.svg?component'
|
||||
|
||||
@@ -315,6 +316,7 @@ export const MoreHorizontalIcon = _MoreHorizontalIcon
|
||||
export const MoreVerticalIcon = _MoreVerticalIcon
|
||||
export const NewspaperIcon = _NewspaperIcon
|
||||
export const NoSignalIcon = _NoSignalIcon
|
||||
export const NotepadTextIcon = _NotepadTextIcon
|
||||
export const OmorphiaIcon = _OmorphiaIcon
|
||||
export const OrganizationIcon = _OrganizationIcon
|
||||
export const PackageClosedIcon = _PackageClosedIcon
|
||||
|
||||
14
packages/assets/icons.d.ts
vendored
14
packages/assets/icons.d.ts
vendored
@@ -1,16 +1,16 @@
|
||||
declare module '*.svg?component' {
|
||||
import type { FunctionalComponent, SVGAttributes } from 'vue'
|
||||
import type { FunctionalComponent, SVGAttributes } from 'vue'
|
||||
|
||||
const src: FunctionalComponent<SVGAttributes>
|
||||
export default src
|
||||
const src: FunctionalComponent<SVGAttributes>
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module '*.webp' {
|
||||
const src: string
|
||||
export default src
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module '*?url' {
|
||||
const src: string
|
||||
export default src
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
1
packages/assets/icons/notepad-text.svg
Normal file
1
packages/assets/icons/notepad-text.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-notepad-text-icon lucide-notepad-text"><path d="M8 2v4"/><path d="M12 2v4"/><path d="M16 2v4"/><rect width="16" height="18" x="4" y="4" rx="2"/><path d="M8 10h6"/><path d="M8 14h8"/><path d="M8 18h5"/></svg>
|
||||
|
After Width: | Height: | Size: 409 B |
@@ -8,9 +8,9 @@
|
||||
|
||||
import './omorphia.scss'
|
||||
|
||||
import _FourOhFourNotFound from './branding/404.svg?component'
|
||||
// Branding
|
||||
import _ModrinthIcon from './branding/logo.svg?component'
|
||||
import _FourOhFourNotFound from './branding/404.svg?component'
|
||||
import _ModrinthPlusIcon from './branding/modrinth-plus.svg?component'
|
||||
import _AngryRinthbot from './branding/rinthbot/angry.webp'
|
||||
import _AnnoyedRinthbot from './branding/rinthbot/annoyed.webp'
|
||||
@@ -22,14 +22,6 @@ import _SleepingRinthbot from './branding/rinthbot/sleeping.webp'
|
||||
import _SobbingRinthbot from './branding/rinthbot/sobbing.webp'
|
||||
import _ThinkingRinthbot from './branding/rinthbot/thinking.webp'
|
||||
import _WavingRinthbot from './branding/rinthbot/waving.webp'
|
||||
|
||||
// External Icons
|
||||
import _SSODiscordIcon from './external/sso/discord.svg?component'
|
||||
import _SSOGitHubIcon from './external/sso/github.svg?component'
|
||||
import _SSOGitLabIcon from './external/sso/gitlab.svg?component'
|
||||
import _SSOGoogleIcon from './external/sso/google.svg?component'
|
||||
import _SSOMicrosoftIcon from './external/sso/microsoft.svg?component'
|
||||
import _SSOSteamIcon from './external/sso/steam.svg?component'
|
||||
import _AppleIcon from './external/apple.svg?component'
|
||||
import _BlueskyIcon from './external/bluesky.svg?component'
|
||||
import _BuyMeACoffeeIcon from './external/bmac.svg?component'
|
||||
@@ -42,6 +34,13 @@ import _OpenCollectiveIcon from './external/opencollective.svg?component'
|
||||
import _PatreonIcon from './external/patreon.svg?component'
|
||||
import _PayPalIcon from './external/paypal.svg?component'
|
||||
import _RedditIcon from './external/reddit.svg?component'
|
||||
// External Icons
|
||||
import _SSODiscordIcon from './external/sso/discord.svg?component'
|
||||
import _SSOGitHubIcon from './external/sso/github.svg?component'
|
||||
import _SSOGitLabIcon from './external/sso/gitlab.svg?component'
|
||||
import _SSOGoogleIcon from './external/sso/google.svg?component'
|
||||
import _SSOMicrosoftIcon from './external/sso/microsoft.svg?component'
|
||||
import _SSOSteamIcon from './external/sso/steam.svg?component'
|
||||
import _TumblrIcon from './external/tumblr.svg?component'
|
||||
import _TwitterIcon from './external/twitter.svg?component'
|
||||
import _WindowsIcon from './external/windows.svg?component'
|
||||
@@ -100,7 +99,6 @@ export const AstralRinthLogo = _AstralRinthLogo
|
||||
export const ElyByIcon = _ElyByIcon
|
||||
|
||||
// Skin Models
|
||||
export * from './generated-icons'
|
||||
export { default as ClassicPlayerModel } from './models/classic-player.gltf?url'
|
||||
export { default as SlimPlayerModel } from './models/slim-player.gltf?url'
|
||||
|
||||
export * from './generated-icons'
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,21 +1,19 @@
|
||||
{
|
||||
"name": "@modrinth/assets",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"scripts": {
|
||||
"lint": "pnpm run icons:validate && eslint . && prettier --check .",
|
||||
"fix": "pnpm run icons:generate && eslint . --fix && prettier --write .",
|
||||
"icons:test": "jiti build/generate-exports.ts --test",
|
||||
"icons:validate": "jiti build/generate-exports.ts --validate",
|
||||
"icons:generate": "jiti build/generate-exports.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"jiti": "^2.4.2",
|
||||
"tsconfig": "workspace:*",
|
||||
"vue": "^3.5.13"
|
||||
}
|
||||
"name": "@modrinth/assets",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"scripts": {
|
||||
"lint": "pnpm run icons:validate && eslint . && prettier --check .",
|
||||
"fix": "pnpm run icons:generate && eslint . --fix && prettier --write .",
|
||||
"icons:test": "jiti build/generate-exports.ts --test",
|
||||
"icons:validate": "jiti build/generate-exports.ts --validate",
|
||||
"icons:generate": "jiti build/generate-exports.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modrinth/tooling-config": "workspace:*",
|
||||
"jiti": "^2.4.2",
|
||||
"vue": "^3.5.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
button:focus-visible,
|
||||
a:focus-visible,
|
||||
[tabindex='0']:focus-visible {
|
||||
outline: 0.25rem solid #ea80ff;
|
||||
border-radius: 0.25rem;
|
||||
outline: 0.25rem solid #ea80ff;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,45 +1,45 @@
|
||||
// Use border box on everything to preserve everyone's sanity
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
// Defaults
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-base);
|
||||
--font-standard: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Roboto,
|
||||
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
--mono-font: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
||||
font-family: var(--font-standard);
|
||||
font-size: 16px;
|
||||
font-weight: var(--font-weight-regular);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
// Defaults
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-base);
|
||||
--font-standard: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Roboto,
|
||||
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
--mono-font: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
||||
font-family: var(--font-standard);
|
||||
font-size: 16px;
|
||||
font-weight: var(--font-weight-regular);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
// Font Sizes
|
||||
--font-size-xxs: 0.625rem; //10px
|
||||
--font-size-xs: 0.75rem; //12px
|
||||
--font-size-sm: 0.875rem; //14px
|
||||
--font-size-nm: 1rem; //16px
|
||||
--font-size-md: 1.125rem; //18px
|
||||
--font-size-lg: 1.25rem; //20px
|
||||
--font-size-xl: 1.5rem; //24px
|
||||
--font-size-2xl: 2rem; //32px
|
||||
--font-size-3xl: 3rem; //48px
|
||||
// Font Sizes
|
||||
--font-size-xxs: 0.625rem; //10px
|
||||
--font-size-xs: 0.75rem; //12px
|
||||
--font-size-sm: 0.875rem; //14px
|
||||
--font-size-nm: 1rem; //16px
|
||||
--font-size-md: 1.125rem; //18px
|
||||
--font-size-lg: 1.25rem; //20px
|
||||
--font-size-xl: 1.5rem; //24px
|
||||
--font-size-2xl: 2rem; //32px
|
||||
--font-size-3xl: 3rem; //48px
|
||||
|
||||
// Font Weights
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-extrabold: 800;
|
||||
// Font Weights
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-extrabold: 800;
|
||||
|
||||
--font-weight-text: var(--font-weight-medium);
|
||||
--font-weight-heading: var(--font-weight-extrabold);
|
||||
--font-weight-title: var(--font-weight-extrabold);
|
||||
--font-weight-text: var(--font-weight-medium);
|
||||
--font-weight-heading: var(--font-weight-extrabold);
|
||||
--font-weight-title: var(--font-weight-extrabold);
|
||||
}
|
||||
|
||||
a.uncolored {
|
||||
color: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
@@ -49,189 +49,189 @@ input[type='password'],
|
||||
textarea,
|
||||
.input-text-inherit,
|
||||
.cm-content {
|
||||
border-radius: var(--radius-md);
|
||||
box-sizing: border-box;
|
||||
// safari iOS rounds inputs by default
|
||||
// set the appearance to none to prevent this
|
||||
appearance: none !important;
|
||||
background: var(--color-button-bg);
|
||||
color: var(--color-base);
|
||||
padding: 0.5rem 1rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
transition: box-shadow 0.1s ease-in-out;
|
||||
min-height: 36px;
|
||||
box-shadow:
|
||||
var(--shadow-inset-sm),
|
||||
0 0 0 0 transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: var(--radius-md);
|
||||
box-sizing: border-box;
|
||||
// safari iOS rounds inputs by default
|
||||
// set the appearance to none to prevent this
|
||||
appearance: none !important;
|
||||
background: var(--color-button-bg);
|
||||
color: var(--color-base);
|
||||
padding: 0.5rem 1rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
transition: box-shadow 0.1s ease-in-out;
|
||||
min-height: 36px;
|
||||
box-shadow:
|
||||
var(--shadow-inset-sm),
|
||||
0 0 0 0 transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
box-shadow:
|
||||
inset 0 0 0 transparent,
|
||||
0 0 0 0.25rem var(--color-brand-shadow);
|
||||
color: var(--color-contrast);
|
||||
outline: none;
|
||||
}
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
box-shadow:
|
||||
inset 0 0 0 transparent,
|
||||
0 0 0 0.25rem var(--color-brand-shadow);
|
||||
color: var(--color-contrast);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:disabled,
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
&:disabled,
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:focus::placeholder {
|
||||
opacity: 0.8;
|
||||
}
|
||||
&:focus::placeholder {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-base);
|
||||
opacity: 0.6;
|
||||
}
|
||||
&::placeholder {
|
||||
color: var(--color-base);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-content {
|
||||
white-space: pre-wrap !important;
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
input[type='number'] {
|
||||
&::-webkit-inner-spin-button,
|
||||
&::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
&::-webkit-inner-spin-button,
|
||||
&::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1px;
|
||||
.animated-dropdown {
|
||||
width: unset;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1px;
|
||||
.animated-dropdown {
|
||||
width: unset;
|
||||
|
||||
.selected {
|
||||
border-radius: var(--radius-md) 0 0 var(--radius-md);
|
||||
.selected {
|
||||
border-radius: var(--radius-md) 0 0 var(--radius-md);
|
||||
|
||||
&.render-down {
|
||||
border-radius: var(--radius-md) 0 0 0;
|
||||
}
|
||||
&.render-down {
|
||||
border-radius: var(--radius-md) 0 0 0;
|
||||
}
|
||||
|
||||
&.render-up {
|
||||
border-radius: 0 0 0 var(--radius-md);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.render-up {
|
||||
border-radius: 0 0 0 var(--radius-md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
||||
}
|
||||
input {
|
||||
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.iconified-input {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
padding: 0 0.5rem 0 2.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
input {
|
||||
padding: 0 0.5rem 0 2.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:focus-within svg {
|
||||
opacity: 1;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
&:focus-within svg {
|
||||
opacity: 1;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
left: 0.75rem;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
z-index: 1;
|
||||
svg {
|
||||
position: absolute;
|
||||
left: 0.75rem;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
z-index: 1;
|
||||
|
||||
color: var(--color-base);
|
||||
opacity: 0.6;
|
||||
}
|
||||
color: var(--color-base);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.r-btn {
|
||||
@extend .transparent, .icon-only;
|
||||
.r-btn {
|
||||
@extend .transparent, .icon-only;
|
||||
|
||||
position: absolute;
|
||||
right: 0.125rem;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
right: 0.125rem;
|
||||
z-index: 1;
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
position: relative;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.chart {
|
||||
svg {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
svg {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.button-animation {
|
||||
transition:
|
||||
opacity 0.5s ease-in-out,
|
||||
filter 0.2s ease-in-out,
|
||||
transform 0.05s ease-in-out,
|
||||
outline 0.2s ease-in-out;
|
||||
transition:
|
||||
opacity 0.5s ease-in-out,
|
||||
filter 0.2s ease-in-out,
|
||||
transform 0.05s ease-in-out,
|
||||
outline 0.2s ease-in-out;
|
||||
|
||||
&:active:not(&:disabled) {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
&:active:not(&:disabled) {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
&:disabled {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
&:disabled {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
.button-animation,
|
||||
button {
|
||||
transform: none !important;
|
||||
}
|
||||
.button-animation,
|
||||
button {
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
&:disabled {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
&:disabled {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
.button-animation,
|
||||
button {
|
||||
transform: none !important;
|
||||
}
|
||||
.button-animation,
|
||||
button {
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--color-contrast);
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--color-contrast);
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-block: var(--gap-md) var(--gap-md);
|
||||
color: var(--color-contrast);
|
||||
margin-block: var(--gap-md) var(--gap-md);
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
.hljs,
|
||||
.hljs-subst {
|
||||
color: #444;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.hljs-comment {
|
||||
color: #888888;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
@@ -13,8 +13,8 @@
|
||||
.hljs-meta-keyword,
|
||||
.hljs-doctag,
|
||||
.hljs-name {
|
||||
color: #f58300;
|
||||
font-weight: bold;
|
||||
color: #f58300;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-type,
|
||||
@@ -25,13 +25,13 @@
|
||||
.hljs-quote,
|
||||
.hljs-template-tag,
|
||||
.hljs-deletion {
|
||||
color: var(--color-brand);
|
||||
color: var(--color-brand);
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #008888;
|
||||
font-weight: bold;
|
||||
color: #008888;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-regexp,
|
||||
@@ -41,49 +41,49 @@
|
||||
.hljs-link,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #bc6060;
|
||||
color: #bc6060;
|
||||
}
|
||||
|
||||
.hljs-literal {
|
||||
color: #78a960;
|
||||
color: #78a960;
|
||||
}
|
||||
|
||||
.hljs-built_in,
|
||||
.hljs-bullet,
|
||||
.hljs-code,
|
||||
.hljs-addition {
|
||||
color: #f58300;
|
||||
color: #f58300;
|
||||
}
|
||||
|
||||
.hljs-meta {
|
||||
color: #1f7199;
|
||||
color: #1f7199;
|
||||
}
|
||||
|
||||
.hljs-meta-string {
|
||||
color: #4d99bf;
|
||||
color: #4d99bf;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #222222;
|
||||
padding: 1em 1em 1em 1em;
|
||||
border-width: 5px;
|
||||
border-radius: 2em;
|
||||
border-color: var(--color-button-bg);
|
||||
overflow-x: hidden;
|
||||
background-color: #222222;
|
||||
padding: 1em 1em 1em 1em;
|
||||
border-width: 5px;
|
||||
border-radius: 2em;
|
||||
border-color: var(--color-button-bg);
|
||||
overflow-x: hidden;
|
||||
|
||||
code {
|
||||
line-height: 100%;
|
||||
padding: 0.2em;
|
||||
letter-spacing: -0.05em;
|
||||
word-break: normal;
|
||||
font-family: monospace;
|
||||
}
|
||||
code {
|
||||
line-height: 100%;
|
||||
padding: 0.2em;
|
||||
letter-spacing: -0.05em;
|
||||
word-break: normal;
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Regular.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Regular.woff?v=3.19') format('woff');
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Regular.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Regular.woff?v=3.19') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Medium.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Medium.woff?v=3.19') format('woff');
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Medium.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Medium.woff?v=3.19') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-SemiBold.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-SemiBold.woff?v=3.19') format('woff');
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-SemiBold.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-SemiBold.woff?v=3.19') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Bold.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Bold.woff?v=3.19') format('woff');
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Bold.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Bold.woff?v=3.19') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-ExtraBold.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-ExtraBold.woff?v=3.19') format('woff');
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-ExtraBold.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-ExtraBold.woff?v=3.19') format('woff');
|
||||
}
|
||||
|
||||
118
packages/assets/styles/normalize.scss
vendored
118
packages/assets/styles/normalize.scss
vendored
@@ -9,8 +9,8 @@
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
@@ -21,7 +21,7 @@ html {
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,7 +29,7 @@ body {
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,8 +38,8 @@ main {
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
@@ -51,9 +51,9 @@ h1 {
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,8 +62,8 @@ hr {
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
@@ -74,7 +74,7 @@ pre {
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,9 +83,9 @@ a {
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,7 +94,7 @@ abbr[title] {
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,8 +105,8 @@ strong {
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +114,7 @@ samp {
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,18 +124,18 @@ small {
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
@@ -146,7 +146,7 @@ sup {
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
@@ -162,10 +162,10 @@ input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,8 +175,8 @@ textarea {
|
||||
|
||||
button,
|
||||
input {
|
||||
/* 1 */
|
||||
overflow: visible;
|
||||
/* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,8 +186,8 @@ input {
|
||||
|
||||
button,
|
||||
select {
|
||||
/* 1 */
|
||||
text-transform: none;
|
||||
/* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,7 +198,7 @@ button,
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,8 +209,8 @@ button::-moz-focus-inner,
|
||||
[type='button']::-moz-focus-inner,
|
||||
[type='reset']::-moz-focus-inner,
|
||||
[type='submit']::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,7 +221,7 @@ button:-moz-focusring,
|
||||
[type='button']:-moz-focusring,
|
||||
[type='reset']:-moz-focusring,
|
||||
[type='submit']:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,7 +229,7 @@ button:-moz-focusring,
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,12 +240,12 @@ fieldset {
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,7 +253,7 @@ legend {
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,7 +261,7 @@ progress {
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,8 +271,8 @@ textarea {
|
||||
|
||||
[type='checkbox'],
|
||||
[type='radio'] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,7 +281,7 @@ textarea {
|
||||
|
||||
[type='number']::-webkit-inner-spin-button,
|
||||
[type='number']::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,8 +290,8 @@ textarea {
|
||||
*/
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,7 +299,7 @@ textarea {
|
||||
*/
|
||||
|
||||
[type='search']::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -308,8 +308,8 @@ textarea {
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
@@ -320,7 +320,7 @@ textarea {
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -328,7 +328,7 @@ details {
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
@@ -339,7 +339,7 @@ summary {
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -347,5 +347,5 @@ template {
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,292 +1,292 @@
|
||||
.light-properties {
|
||||
--color-bg: #e5e7eb;
|
||||
--color-raised-bg: #ffffff;
|
||||
--color-super-raised-bg: #e9e9e9;
|
||||
--color-button-bg: hsl(220, 13%, 91%);
|
||||
--color-button-border: rgba(161, 161, 161, 0.35);
|
||||
--color-scrollbar: #96a2b0;
|
||||
--color-bg: #e5e7eb;
|
||||
--color-raised-bg: #ffffff;
|
||||
--color-super-raised-bg: #e9e9e9;
|
||||
--color-button-bg: hsl(220, 13%, 91%);
|
||||
--color-button-border: rgba(161, 161, 161, 0.35);
|
||||
--color-scrollbar: #96a2b0;
|
||||
|
||||
--color-divider: #babfc5;
|
||||
--color-divider-dark: #c8cdd3;
|
||||
--color-divider: #babfc5;
|
||||
--color-divider-dark: #c8cdd3;
|
||||
|
||||
--color-base: hsl(221, 39%, 11%);
|
||||
--color-secondary: #6b7280;
|
||||
--color-contrast: #1a202c;
|
||||
--color-accent-contrast: #ffffff;
|
||||
--color-base: hsl(221, 39%, 11%);
|
||||
--color-secondary: #6b7280;
|
||||
--color-contrast: #1a202c;
|
||||
--color-accent-contrast: #ffffff;
|
||||
|
||||
--color-red: #cb2245;
|
||||
--color-orange: #e08325;
|
||||
--color-green: #00af5c;
|
||||
--color-blue: #1f68c0;
|
||||
--color-purple: #8e32f3;
|
||||
--color-gray: #595b61;
|
||||
--color-red: #cb2245;
|
||||
--color-orange: #e08325;
|
||||
--color-green: #00af5c;
|
||||
--color-blue: #1f68c0;
|
||||
--color-purple: #8e32f3;
|
||||
--color-gray: #595b61;
|
||||
|
||||
--color-red-highlight: rgba(203, 34, 69, 0.25);
|
||||
--color-orange-highlight: rgba(224, 131, 37, 0.25);
|
||||
--color-green-highlight: rgba(0, 175, 92, 0.25);
|
||||
--color-blue-highlight: rgba(31, 104, 192, 0.25);
|
||||
--color-purple-highlight: rgba(142, 50, 243, 0.25);
|
||||
--color-gray-highlight: rgba(89, 91, 97, 0.25);
|
||||
--color-red-highlight: rgba(203, 34, 69, 0.25);
|
||||
--color-orange-highlight: rgba(224, 131, 37, 0.25);
|
||||
--color-green-highlight: rgba(0, 175, 92, 0.25);
|
||||
--color-blue-highlight: rgba(31, 104, 192, 0.25);
|
||||
--color-purple-highlight: rgba(142, 50, 243, 0.25);
|
||||
--color-gray-highlight: rgba(89, 91, 97, 0.25);
|
||||
|
||||
--color-red-bg: rgba(203, 34, 69, 0.1);
|
||||
--color-orange-bg: rgba(224, 131, 37, 0.1);
|
||||
--color-green-bg: rgba(0, 175, 92, 0.1);
|
||||
--color-blue-bg: rgba(31, 104, 192, 0.1);
|
||||
--color-purple-bg: rgba(142, 50, 243, 0.1);
|
||||
--color-red-bg: rgba(203, 34, 69, 0.1);
|
||||
--color-orange-bg: rgba(224, 131, 37, 0.1);
|
||||
--color-green-bg: rgba(0, 175, 92, 0.1);
|
||||
--color-blue-bg: rgba(31, 104, 192, 0.1);
|
||||
--color-purple-bg: rgba(142, 50, 243, 0.1);
|
||||
|
||||
--color-brand: var(--color-green);
|
||||
--color-brand-highlight: var(--color-green-highlight);
|
||||
--color-brand-shadow: rgba(0, 175, 92, 0.7);
|
||||
--color-brand: var(--color-green);
|
||||
--color-brand-highlight: var(--color-green-highlight);
|
||||
--color-brand-shadow: rgba(0, 175, 92, 0.7);
|
||||
|
||||
--shadow-inset-lg: inset 0px -2px 2px hsla(221, 39%, 91%, 0.1);
|
||||
--shadow-inset: inset 0px -2px 2px hsla(221, 39%, 91%, 0.05);
|
||||
--shadow-inset-sm: inset 0px -1px 2px hsla(221, 39%, 91%, 0.15);
|
||||
--shadow-inset-lg: inset 0px -2px 2px hsla(221, 39%, 91%, 0.1);
|
||||
--shadow-inset: inset 0px -2px 2px hsla(221, 39%, 91%, 0.05);
|
||||
--shadow-inset-sm: inset 0px -1px 2px hsla(221, 39%, 91%, 0.15);
|
||||
|
||||
--shadow-raised-lg: 0px 2px 4px hsla(221, 39%, 11%, 0.2);
|
||||
--shadow-raised: 0.3px 0.5px 0.6px hsl(var(--shadow-color) / 0.15),
|
||||
1px 2px 2.2px -1.7px hsl(var(--shadow-color) / 0.12),
|
||||
4.4px 8.8px 9.7px -3.4px hsl(var(--shadow-color) / 0.09);
|
||||
--shadow-floating: hsla(0, 0%, 0%, 0) 0px 0px 0px 0px, hsla(0, 0%, 0%, 0) 0px 0px 0px 0px,
|
||||
hsla(0, 0%, 0%, 0.1) 0px 4px 6px -1px, hsla(0, 0%, 0%, 0.1) 0px 2px 4px -1px;
|
||||
--shadow-raised-lg: 0px 2px 4px hsla(221, 39%, 11%, 0.2);
|
||||
--shadow-raised: 0.3px 0.5px 0.6px hsl(var(--shadow-color) / 0.15),
|
||||
1px 2px 2.2px -1.7px hsl(var(--shadow-color) / 0.12),
|
||||
4.4px 8.8px 9.7px -3.4px hsl(var(--shadow-color) / 0.09);
|
||||
--shadow-floating: hsla(0, 0%, 0%, 0) 0px 0px 0px 0px, hsla(0, 0%, 0%, 0) 0px 0px 0px 0px,
|
||||
hsla(0, 0%, 0%, 0.1) 0px 4px 6px -1px, hsla(0, 0%, 0%, 0.1) 0px 2px 4px -1px;
|
||||
|
||||
--shadow-card: rgba(50, 50, 100, 0.1) 0px 2px 4px 0px;
|
||||
--shadow-card: rgba(50, 50, 100, 0.1) 0px 2px 4px 0px;
|
||||
|
||||
--brand-gradient-bg: linear-gradient(
|
||||
0deg,
|
||||
rgba(68, 182, 138, 0.175) 0%,
|
||||
rgba(58, 250, 112, 0.125) 100%
|
||||
);
|
||||
--brand-gradient-strong-bg: linear-gradient(
|
||||
270deg,
|
||||
rgba(68, 182, 138, 0.175) 0%,
|
||||
rgba(36, 225, 91, 0.12) 100%
|
||||
);
|
||||
--brand-gradient-button: rgba(255, 255, 255, 0.5);
|
||||
--brand-gradient-border: rgba(32, 64, 32, 0.15);
|
||||
--brand-gradient-fade-out-color: linear-gradient(to bottom, rgba(213, 235, 224, 0), #d0ece0 70%);
|
||||
--brand-gradient-bg: linear-gradient(
|
||||
0deg,
|
||||
rgba(68, 182, 138, 0.175) 0%,
|
||||
rgba(58, 250, 112, 0.125) 100%
|
||||
);
|
||||
--brand-gradient-strong-bg: linear-gradient(
|
||||
270deg,
|
||||
rgba(68, 182, 138, 0.175) 0%,
|
||||
rgba(36, 225, 91, 0.12) 100%
|
||||
);
|
||||
--brand-gradient-button: rgba(255, 255, 255, 0.5);
|
||||
--brand-gradient-border: rgba(32, 64, 32, 0.15);
|
||||
--brand-gradient-fade-out-color: linear-gradient(to bottom, rgba(213, 235, 224, 0), #d0ece0 70%);
|
||||
|
||||
--color-button-bg-selected: var(--color-brand);
|
||||
--color-button-text-selected: var(--color-accent-contrast);
|
||||
--color-button-bg-selected: var(--color-brand);
|
||||
--color-button-text-selected: var(--color-accent-contrast);
|
||||
|
||||
--color-gradient-button-bg: linear-gradient(180deg, #f8f9fa 0%, #dce0e6 100%);
|
||||
--color-gradient-button-bg: linear-gradient(180deg, #f8f9fa 0%, #dce0e6 100%);
|
||||
|
||||
--loading-bar-gradient: linear-gradient(to right, var(--color-brand) 0%, #00af5c 100%);
|
||||
--loading-bar-gradient: linear-gradient(to right, var(--color-brand) 0%, #00af5c 100%);
|
||||
|
||||
--color-platform-fabric: #8a7b71;
|
||||
--color-platform-quilt: #8b61b4;
|
||||
--color-platform-forge: #5b6197;
|
||||
--color-platform-neoforge: #dc895c;
|
||||
--color-platform-liteloader: #4c90de;
|
||||
--color-platform-bukkit: #e78362;
|
||||
--color-platform-bungeecord: #c69e39;
|
||||
--color-platform-folia: #6aa54f;
|
||||
--color-platform-paper: #e67e7e;
|
||||
--color-platform-purpur: #7763a3;
|
||||
--color-platform-spigot: #cd7a21;
|
||||
--color-platform-velocity: #4b98b0;
|
||||
--color-platform-waterfall: #5f83cb;
|
||||
--color-platform-sponge: #c49528;
|
||||
--color-platform-ornithe: #6097ca;
|
||||
--color-platform-bta-babric: #5ba938;
|
||||
--color-platform-legacy-fabric: #6879f6;
|
||||
--color-platform-nilloader: #dd5088;
|
||||
--color-platform-fabric: #8a7b71;
|
||||
--color-platform-quilt: #8b61b4;
|
||||
--color-platform-forge: #5b6197;
|
||||
--color-platform-neoforge: #dc895c;
|
||||
--color-platform-liteloader: #4c90de;
|
||||
--color-platform-bukkit: #e78362;
|
||||
--color-platform-bungeecord: #c69e39;
|
||||
--color-platform-folia: #6aa54f;
|
||||
--color-platform-paper: #e67e7e;
|
||||
--color-platform-purpur: #7763a3;
|
||||
--color-platform-spigot: #cd7a21;
|
||||
--color-platform-velocity: #4b98b0;
|
||||
--color-platform-waterfall: #5f83cb;
|
||||
--color-platform-sponge: #c49528;
|
||||
--color-platform-ornithe: #6097ca;
|
||||
--color-platform-bta-babric: #5ba938;
|
||||
--color-platform-legacy-fabric: #6879f6;
|
||||
--color-platform-nilloader: #dd5088;
|
||||
|
||||
--hover-brightness: 0.9;
|
||||
--hover-brightness: 0.9;
|
||||
}
|
||||
|
||||
html {
|
||||
@extend .light-properties;
|
||||
--dark-color-base: #b0bac5;
|
||||
--dark-color-contrast: #ecf9fb;
|
||||
@extend .light-properties;
|
||||
--dark-color-base: #b0bac5;
|
||||
--dark-color-contrast: #ecf9fb;
|
||||
|
||||
--gap-xs: 0.25rem;
|
||||
--gap-sm: 0.5rem;
|
||||
--gap-md: 0.75rem;
|
||||
--gap-lg: 1rem;
|
||||
--gap-xl: 1.5rem;
|
||||
--gap-xs: 0.25rem;
|
||||
--gap-sm: 0.5rem;
|
||||
--gap-md: 0.75rem;
|
||||
--gap-lg: 1rem;
|
||||
--gap-xl: 1.5rem;
|
||||
|
||||
--radius-xs: 0.25rem;
|
||||
--radius-sm: 0.5rem;
|
||||
--radius-md: 0.75rem;
|
||||
--radius-lg: 1rem;
|
||||
--radius-xl: 1.25rem;
|
||||
--radius-max: 999999999px;
|
||||
--radius-xs: 0.25rem;
|
||||
--radius-sm: 0.5rem;
|
||||
--radius-md: 0.75rem;
|
||||
--radius-lg: 1rem;
|
||||
--radius-xl: 1.25rem;
|
||||
--radius-max: 999999999px;
|
||||
|
||||
--color-tooltip-text: var(--dark-color-contrast);
|
||||
--color-tooltip-bg: #000;
|
||||
--color-tooltip-text: var(--dark-color-contrast);
|
||||
--color-tooltip-bg: #000;
|
||||
|
||||
--color-ad: rgba(125, 75, 162, 0.2);
|
||||
--color-ad-raised: rgba(190, 140, 243, 0.5);
|
||||
--color-ad-contrast: black;
|
||||
--color-ad-highlight: var(--color-purple);
|
||||
--color-ad: rgba(125, 75, 162, 0.2);
|
||||
--color-ad-raised: rgba(190, 140, 243, 0.5);
|
||||
--color-ad-contrast: black;
|
||||
--color-ad-highlight: var(--color-purple);
|
||||
}
|
||||
|
||||
.light-mode,
|
||||
.light {
|
||||
@extend .light-properties;
|
||||
@extend .light-properties;
|
||||
}
|
||||
|
||||
.dark-mode,
|
||||
.dark,
|
||||
:root[data-theme='dark'] {
|
||||
--color-bg: #16181c;
|
||||
--color-raised-bg: #26292f;
|
||||
--color-super-raised-bg: #40434a;
|
||||
--color-button-bg: hsl(222, 13%, 30%);
|
||||
--color-button-border: rgba(193, 190, 209, 0.12);
|
||||
--color-scrollbar: var(--color-button-bg);
|
||||
--color-bg: #16181c;
|
||||
--color-raised-bg: #26292f;
|
||||
--color-super-raised-bg: #40434a;
|
||||
--color-button-bg: hsl(222, 13%, 30%);
|
||||
--color-button-border: rgba(193, 190, 209, 0.12);
|
||||
--color-scrollbar: var(--color-button-bg);
|
||||
|
||||
--color-divider: var(--color-button-bg);
|
||||
--color-divider-dark: #646c75;
|
||||
--color-divider: var(--color-button-bg);
|
||||
--color-divider-dark: #646c75;
|
||||
|
||||
--color-base: var(--dark-color-base);
|
||||
--color-secondary: #96a2b0;
|
||||
--color-contrast: var(--dark-color-contrast);
|
||||
--color-accent-contrast: #000000;
|
||||
--color-base: var(--dark-color-base);
|
||||
--color-secondary: #96a2b0;
|
||||
--color-contrast: var(--dark-color-contrast);
|
||||
--color-accent-contrast: #000000;
|
||||
|
||||
--color-red: #ff496e;
|
||||
--color-orange: #ffa347;
|
||||
--color-green: #1bd96a;
|
||||
--color-blue: #4f9cff;
|
||||
--color-purple: #c78aff;
|
||||
--color-gray: #9fa4b3;
|
||||
--color-red: #ff496e;
|
||||
--color-orange: #ffa347;
|
||||
--color-green: #1bd96a;
|
||||
--color-blue: #4f9cff;
|
||||
--color-purple: #c78aff;
|
||||
--color-gray: #9fa4b3;
|
||||
|
||||
--color-red-highlight: rgba(255, 73, 110, 0.25);
|
||||
--color-orange-highlight: rgba(255, 163, 71, 0.25);
|
||||
--color-green-highlight: rgba(27, 217, 106, 0.25);
|
||||
--color-blue-highlight: rgba(79, 156, 255, 0.25);
|
||||
--color-purple-highlight: rgba(199, 138, 255, 0.25);
|
||||
--color-gray-highlight: rgba(159, 164, 179, 0.25);
|
||||
--color-red-highlight: rgba(255, 73, 110, 0.25);
|
||||
--color-orange-highlight: rgba(255, 163, 71, 0.25);
|
||||
--color-green-highlight: rgba(27, 217, 106, 0.25);
|
||||
--color-blue-highlight: rgba(79, 156, 255, 0.25);
|
||||
--color-purple-highlight: rgba(199, 138, 255, 0.25);
|
||||
--color-gray-highlight: rgba(159, 164, 179, 0.25);
|
||||
|
||||
--color-red-bg: rgba(255, 73, 110, 0.2);
|
||||
--color-orange-bg: rgba(255, 163, 71, 0.2);
|
||||
--color-green-bg: rgba(27, 217, 106, 0.2);
|
||||
--color-blue-bg: rgba(79, 156, 255, 0.2);
|
||||
--color-purple-bg: rgba(199, 138, 255, 0.2);
|
||||
--color-red-bg: rgba(255, 73, 110, 0.2);
|
||||
--color-orange-bg: rgba(255, 163, 71, 0.2);
|
||||
--color-green-bg: rgba(27, 217, 106, 0.2);
|
||||
--color-blue-bg: rgba(79, 156, 255, 0.2);
|
||||
--color-purple-bg: rgba(199, 138, 255, 0.2);
|
||||
|
||||
--color-brand: var(--color-green);
|
||||
--color-brand-highlight: rgba(27, 217, 106, 0.25);
|
||||
--color-brand-shadow: rgba(27, 217, 106, 0.7);
|
||||
--color-brand: var(--color-green);
|
||||
--color-brand-highlight: rgba(27, 217, 106, 0.25);
|
||||
--color-brand-shadow: rgba(27, 217, 106, 0.7);
|
||||
|
||||
--shadow-inset-lg: inset 0px -2px 2px hsla(221, 39%, 11%, 0.1);
|
||||
--shadow-inset: inset 0px -2px 2px hsla(221, 39%, 11%, 0.05);
|
||||
--shadow-inset-sm: inset 0px -1px 1px hsla(221, 39%, 11%, 0.25);
|
||||
--shadow-inset-lg: inset 0px -2px 2px hsla(221, 39%, 11%, 0.1);
|
||||
--shadow-inset: inset 0px -2px 2px hsla(221, 39%, 11%, 0.05);
|
||||
--shadow-inset-sm: inset 0px -1px 1px hsla(221, 39%, 11%, 0.25);
|
||||
|
||||
--shadow-raised-lg: 0px 2px 4px hsla(221, 39%, 11%, 0.2);
|
||||
--shadow-raised: 0px -2px 4px hsla(221, 39%, 11%, 0.1);
|
||||
--shadow-floating: hsla(0, 0%, 0%, 0) 0px 0px 0px 0px, hsla(0, 0%, 0%, 0) 0px 0px 0px 0px,
|
||||
hsla(0, 0%, 0%, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
|
||||
--shadow-raised-lg: 0px 2px 4px hsla(221, 39%, 11%, 0.2);
|
||||
--shadow-raised: 0px -2px 4px hsla(221, 39%, 11%, 0.1);
|
||||
--shadow-floating: hsla(0, 0%, 0%, 0) 0px 0px 0px 0px, hsla(0, 0%, 0%, 0) 0px 0px 0px 0px,
|
||||
hsla(0, 0%, 0%, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
|
||||
|
||||
--shadow-card: rgba(0, 0, 0, 0.25) 0px 2px 4px 0px;
|
||||
--shadow-card: rgba(0, 0, 0, 0.25) 0px 2px 4px 0px;
|
||||
|
||||
--brand-gradient-bg: linear-gradient(0deg, rgba(14, 35, 19, 0.2) 0%, rgba(55, 137, 73, 0.1) 100%);
|
||||
--brand-gradient-strong-bg: linear-gradient(270deg, #09110d 10%, #131f17 100%);
|
||||
--brand-gradient-button: rgba(255, 255, 255, 0.08);
|
||||
--brand-gradient-border: rgba(155, 255, 160, 0.08);
|
||||
--brand-gradient-fade-out-color: linear-gradient(to bottom, rgba(24, 30, 31, 0), #171d1e 80%);
|
||||
--brand-gradient-bg: linear-gradient(0deg, rgba(14, 35, 19, 0.2) 0%, rgba(55, 137, 73, 0.1) 100%);
|
||||
--brand-gradient-strong-bg: linear-gradient(270deg, #09110d 10%, #131f17 100%);
|
||||
--brand-gradient-button: rgba(255, 255, 255, 0.08);
|
||||
--brand-gradient-border: rgba(155, 255, 160, 0.08);
|
||||
--brand-gradient-fade-out-color: linear-gradient(to bottom, rgba(24, 30, 31, 0), #171d1e 80%);
|
||||
|
||||
--color-button-bg-selected: var(--color-brand-highlight);
|
||||
--color-button-text-selected: var(--color-brand);
|
||||
--color-button-bg-selected: var(--color-brand-highlight);
|
||||
--color-button-text-selected: var(--color-brand);
|
||||
|
||||
--color-gradient-button-bg: linear-gradient(180deg, #3a3d47 0%, #33363d 100%);
|
||||
--color-gradient-button-bg: linear-gradient(180deg, #3a3d47 0%, #33363d 100%);
|
||||
|
||||
--loading-bar-gradient: linear-gradient(to right, var(--color-brand) 0%, #1ffa9a 100%);
|
||||
--loading-bar-gradient: linear-gradient(to right, var(--color-brand) 0%, #1ffa9a 100%);
|
||||
|
||||
--color-platform-fabric: #dbb69b;
|
||||
--color-platform-quilt: #c796f9;
|
||||
--color-platform-forge: #959eef;
|
||||
--color-platform-neoforge: #f99e6b;
|
||||
--color-platform-liteloader: #7ab0ee;
|
||||
--color-platform-bukkit: #f6af7b;
|
||||
--color-platform-bungeecord: #d2c080;
|
||||
--color-platform-folia: #a5e388;
|
||||
--color-platform-paper: #eeaaaa;
|
||||
--color-platform-purpur: #c3abf7;
|
||||
--color-platform-spigot: #f1cc84;
|
||||
--color-platform-velocity: #83d5ef;
|
||||
--color-platform-waterfall: #78a4fb;
|
||||
--color-platform-sponge: #f9e580;
|
||||
--color-platform-ornithe: #87c7ff;
|
||||
--color-platform-bta-babric: #72cc4a;
|
||||
--color-platform-legacy-fabric: #6879f6;
|
||||
--color-platform-nilloader: #f45e9a;
|
||||
--color-platform-fabric: #dbb69b;
|
||||
--color-platform-quilt: #c796f9;
|
||||
--color-platform-forge: #959eef;
|
||||
--color-platform-neoforge: #f99e6b;
|
||||
--color-platform-liteloader: #7ab0ee;
|
||||
--color-platform-bukkit: #f6af7b;
|
||||
--color-platform-bungeecord: #d2c080;
|
||||
--color-platform-folia: #a5e388;
|
||||
--color-platform-paper: #eeaaaa;
|
||||
--color-platform-purpur: #c3abf7;
|
||||
--color-platform-spigot: #f1cc84;
|
||||
--color-platform-velocity: #83d5ef;
|
||||
--color-platform-waterfall: #78a4fb;
|
||||
--color-platform-sponge: #f9e580;
|
||||
--color-platform-ornithe: #87c7ff;
|
||||
--color-platform-bta-babric: #72cc4a;
|
||||
--color-platform-legacy-fabric: #6879f6;
|
||||
--color-platform-nilloader: #f45e9a;
|
||||
|
||||
--hover-brightness: 1.25;
|
||||
--hover-brightness: 1.25;
|
||||
|
||||
--experimental-color-button-bg: #33363d;
|
||||
--experimental-color-button-bg: #33363d;
|
||||
}
|
||||
|
||||
.oled-mode {
|
||||
@extend .dark-mode;
|
||||
--color-bg: #000000;
|
||||
--color-raised-bg: #101013;
|
||||
--color-button-bg: #222329;
|
||||
@extend .dark-mode;
|
||||
--color-bg: #000000;
|
||||
--color-raised-bg: #101013;
|
||||
--color-button-bg: #222329;
|
||||
|
||||
--color-ad: #0d1828;
|
||||
--color-ad: #0d1828;
|
||||
|
||||
--brand-gradient-bg: linear-gradient(
|
||||
0deg,
|
||||
rgba(22, 66, 51, 0.15) 0%,
|
||||
rgba(55, 137, 73, 0.1) 100%
|
||||
);
|
||||
--brand-gradient-strong-bg: linear-gradient(
|
||||
270deg,
|
||||
rgba(9, 18, 14, 0.6) 10%,
|
||||
rgba(19, 31, 23, 0.5) 100%
|
||||
);
|
||||
--brand-gradient-bg: linear-gradient(
|
||||
0deg,
|
||||
rgba(22, 66, 51, 0.15) 0%,
|
||||
rgba(55, 137, 73, 0.1) 100%
|
||||
);
|
||||
--brand-gradient-strong-bg: linear-gradient(
|
||||
270deg,
|
||||
rgba(9, 18, 14, 0.6) 10%,
|
||||
rgba(19, 31, 23, 0.5) 100%
|
||||
);
|
||||
|
||||
--color-gradient-button-bg: linear-gradient(180deg, #1b1b20 0%, #25262b 100%);
|
||||
--color-gradient-button-bg: linear-gradient(180deg, #1b1b20 0%, #25262b 100%);
|
||||
}
|
||||
|
||||
.retro-mode {
|
||||
--brand-gradient-strong-bg: #3a3b38;
|
||||
--brand-gradient-strong-bg: #3a3b38;
|
||||
}
|
||||
|
||||
.experimental-styles-within {
|
||||
--color-link: var(--color-blue) !important;
|
||||
--color-link-hover: var(--color-blue) !important; // DEPRECATED, use filters in future
|
||||
--color-link-active: var(--color-blue) !important; // DEPRECATED, use filters in future
|
||||
--color-link: var(--color-blue) !important;
|
||||
--color-link-hover: var(--color-blue) !important; // DEPRECATED, use filters in future
|
||||
--color-link-active: var(--color-blue) !important; // DEPRECATED, use filters in future
|
||||
}
|
||||
|
||||
.light-experiments {
|
||||
--color-bg: #ebebeb;
|
||||
--color-raised-bg: #ffffff;
|
||||
--color-button-bg: #f5f5f5;
|
||||
--color-base: #2c2e31;
|
||||
--color-secondary: #484d54;
|
||||
--color-accent-contrast: #ffffff;
|
||||
--color-bg: #ebebeb;
|
||||
--color-raised-bg: #ffffff;
|
||||
--color-button-bg: #f5f5f5;
|
||||
--color-base: #2c2e31;
|
||||
--color-secondary: #484d54;
|
||||
--color-accent-contrast: #ffffff;
|
||||
}
|
||||
|
||||
.light-mode,
|
||||
.light {
|
||||
.experimental-styles-within,
|
||||
&.experimental-styles-within {
|
||||
@extend .light-experiments;
|
||||
}
|
||||
.experimental-styles-within,
|
||||
&.experimental-styles-within {
|
||||
@extend .light-experiments;
|
||||
}
|
||||
}
|
||||
|
||||
.experimental-styles-within {
|
||||
.light-mode,
|
||||
.light {
|
||||
@extend .light-experiments;
|
||||
}
|
||||
.light-mode,
|
||||
.light {
|
||||
@extend .light-experiments;
|
||||
}
|
||||
}
|
||||
|
||||
.dark-experiments {
|
||||
--color-button-bg: var(--experimental-color-button-bg);
|
||||
--color-button-bg: var(--experimental-color-button-bg);
|
||||
}
|
||||
|
||||
.dark-mode:not(.oled-mode),
|
||||
.dark:not(.oled-mode) {
|
||||
.experimental-styles-within,
|
||||
&.experimental-styles-within {
|
||||
@extend .dark-experiments;
|
||||
}
|
||||
.experimental-styles-within,
|
||||
&.experimental-styles-within {
|
||||
@extend .dark-experiments;
|
||||
}
|
||||
}
|
||||
|
||||
.experimental-styles-within {
|
||||
.dark-mode:not(.oled-mode),
|
||||
.dark:not(.oled-mode) {
|
||||
@extend .dark-experiments;
|
||||
}
|
||||
.dark-mode:not(.oled-mode),
|
||||
.dark:not(.oled-mode) {
|
||||
@extend .dark-experiments;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"include": [".", "icons.d.ts", ".eslintrc.js"],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
"extends": "@modrinth/tooling-config/typescript/vue.json"
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['custom/library'],
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as path from 'path'
|
||||
|
||||
import { repoPath } from './utils'
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,85 +1,85 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import * as path from 'path'
|
||||
import { repoPath, toVarName } from './utils'
|
||||
import { glob } from 'glob'
|
||||
import * as path from 'path'
|
||||
|
||||
import { PUBLIC_SRC, PUBLIC_LOCATIONS, ARTICLES_GLOB, COMPILED_DIR } from './blog.config'
|
||||
import { ARTICLES_GLOB, COMPILED_DIR, PUBLIC_LOCATIONS, PUBLIC_SRC } from './blog.config'
|
||||
import { repoPath, toVarName } from './utils'
|
||||
|
||||
async function checkPublicAssets() {
|
||||
const srcFiles = await glob('**/*', { cwd: PUBLIC_SRC, dot: true })
|
||||
let allOk = true
|
||||
for (const target of PUBLIC_LOCATIONS) {
|
||||
for (const relativeFile of srcFiles) {
|
||||
const shouldExist = path.posix.join(target, relativeFile)
|
||||
try {
|
||||
await fs.access(shouldExist)
|
||||
} catch {
|
||||
console.error(`⚠️ Missing public asset: ${shouldExist}`)
|
||||
allOk = false
|
||||
}
|
||||
}
|
||||
if (allOk) {
|
||||
console.log(`✅ All public assets exist in: ${target}`)
|
||||
}
|
||||
}
|
||||
if (!allOk) process.exit(1)
|
||||
const srcFiles = await glob('**/*', { cwd: PUBLIC_SRC, dot: true })
|
||||
let allOk = true
|
||||
for (const target of PUBLIC_LOCATIONS) {
|
||||
for (const relativeFile of srcFiles) {
|
||||
const shouldExist = path.posix.join(target, relativeFile)
|
||||
try {
|
||||
await fs.access(shouldExist)
|
||||
} catch {
|
||||
console.error(`⚠️ Missing public asset: ${shouldExist}`)
|
||||
allOk = false
|
||||
}
|
||||
}
|
||||
if (allOk) {
|
||||
console.log(`✅ All public assets exist in: ${target}`)
|
||||
}
|
||||
}
|
||||
if (!allOk) process.exit(1)
|
||||
}
|
||||
|
||||
async function checkCompiledArticles() {
|
||||
const mdFiles = await glob(ARTICLES_GLOB)
|
||||
const compiledFiles = await glob(`${COMPILED_DIR}/*.ts`)
|
||||
const compiledVarNames = compiledFiles.map((f) => path.basename(f, '.ts'))
|
||||
const mdFiles = await glob(ARTICLES_GLOB)
|
||||
const compiledFiles = await glob(`${COMPILED_DIR}/*.ts`)
|
||||
const compiledVarNames = compiledFiles.map((f) => path.basename(f, '.ts'))
|
||||
|
||||
// Check all .md have compiled .ts and .content.ts and the proper public thumbnail
|
||||
for (const file of mdFiles) {
|
||||
const varName = toVarName(path.basename(file, '.md'))
|
||||
const compiledPath = path.posix.join(COMPILED_DIR, varName + '.ts')
|
||||
const contentPath = path.posix.join(COMPILED_DIR, varName + '.content.ts')
|
||||
if (!compiledVarNames.includes(varName)) {
|
||||
console.error(`⚠️ Missing compiled article for: ${file} (should be: ${compiledPath})`)
|
||||
process.exit(1)
|
||||
}
|
||||
try {
|
||||
await fs.access(compiledPath)
|
||||
} catch {
|
||||
console.error(`⚠️ Compiled article file not found: ${compiledPath}`)
|
||||
process.exit(1)
|
||||
}
|
||||
try {
|
||||
await fs.access(contentPath)
|
||||
} catch {
|
||||
console.error(`⚠️ Compiled article content file not found: ${contentPath}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
// Check all .md have compiled .ts and .content.ts and the proper public thumbnail
|
||||
for (const file of mdFiles) {
|
||||
const varName = toVarName(path.basename(file, '.md'))
|
||||
const compiledPath = path.posix.join(COMPILED_DIR, varName + '.ts')
|
||||
const contentPath = path.posix.join(COMPILED_DIR, varName + '.content.ts')
|
||||
if (!compiledVarNames.includes(varName)) {
|
||||
console.error(`⚠️ Missing compiled article for: ${file} (should be: ${compiledPath})`)
|
||||
process.exit(1)
|
||||
}
|
||||
try {
|
||||
await fs.access(compiledPath)
|
||||
} catch {
|
||||
console.error(`⚠️ Compiled article file not found: ${compiledPath}`)
|
||||
process.exit(1)
|
||||
}
|
||||
try {
|
||||
await fs.access(contentPath)
|
||||
} catch {
|
||||
console.error(`⚠️ Compiled article content file not found: ${contentPath}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Check compiled .ts still have corresponding .md
|
||||
for (const compiled of compiledFiles) {
|
||||
const varName = path.basename(compiled, '.ts')
|
||||
if (varName === 'index' || varName.endsWith('.content')) continue
|
||||
// Check compiled .ts still have corresponding .md
|
||||
for (const compiled of compiledFiles) {
|
||||
const varName = path.basename(compiled, '.ts')
|
||||
if (varName === 'index' || varName.endsWith('.content')) continue
|
||||
|
||||
const mdPathGlob = repoPath(`packages/blog/articles/**/${varName.replace(/_/g, '*')}.md`)
|
||||
const found = await glob(mdPathGlob)
|
||||
if (!found.length) {
|
||||
console.error(`❌ Compiled article ${compiled} has no matching markdown source!`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
const mdPathGlob = repoPath(`packages/blog/articles/**/${varName.replace(/_/g, '*')}.md`)
|
||||
const found = await glob(mdPathGlob)
|
||||
if (!found.length) {
|
||||
console.error(`❌ Compiled article ${compiled} has no matching markdown source!`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
'🎉 All articles are correctly compiled, matched, and have thumbnails (if declared)!',
|
||||
)
|
||||
console.log(
|
||||
'🎉 All articles are correctly compiled, matched, and have thumbnails (if declared)!',
|
||||
)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🔎 Checking public assets...')
|
||||
await checkPublicAssets()
|
||||
console.log('🔎 Checking public assets...')
|
||||
await checkPublicAssets()
|
||||
|
||||
console.log('🔎 Checking compiled articles...')
|
||||
await checkCompiledArticles()
|
||||
console.log('🔎 Checking compiled articles...')
|
||||
await checkCompiledArticles()
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error('❌ Error in check.ts:', e)
|
||||
process.exit(1)
|
||||
console.error('❌ Error in check.ts:', e)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import * as path from 'path'
|
||||
import matter from 'gray-matter'
|
||||
import { md } from '@modrinth/utils'
|
||||
import { promises as fs } from 'fs'
|
||||
import { glob } from 'glob'
|
||||
import matter from 'gray-matter'
|
||||
import { minify } from 'html-minifier-terser'
|
||||
import { copyDir, toVarName } from './utils'
|
||||
import * as path from 'path'
|
||||
import RSS from 'rss'
|
||||
import { parseStringPromise } from 'xml2js'
|
||||
import { glob } from 'glob'
|
||||
|
||||
import {
|
||||
ARTICLES_GLOB,
|
||||
COMPILED_DIR,
|
||||
ROOT_FILE,
|
||||
PUBLIC_SRC,
|
||||
PUBLIC_LOCATIONS,
|
||||
RSS_PATH,
|
||||
JSON_PATH,
|
||||
SITE_URL,
|
||||
ARTICLES_GLOB,
|
||||
COMPILED_DIR,
|
||||
JSON_PATH,
|
||||
PUBLIC_LOCATIONS,
|
||||
PUBLIC_SRC,
|
||||
ROOT_FILE,
|
||||
RSS_PATH,
|
||||
SITE_URL,
|
||||
} from './blog.config'
|
||||
import { copyDir, toVarName } from './utils'
|
||||
|
||||
async function ensureCompiledDir() {
|
||||
await fs.mkdir(COMPILED_DIR, { recursive: true })
|
||||
await fs.mkdir(COMPILED_DIR, { recursive: true })
|
||||
}
|
||||
|
||||
async function hasThumbnail(slug: string): Promise<boolean> {
|
||||
const thumbnailPath = path.posix.join(PUBLIC_SRC, slug, 'thumbnail.webp')
|
||||
try {
|
||||
await fs.access(thumbnailPath)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
const thumbnailPath = path.posix.join(PUBLIC_SRC, slug, 'thumbnail.webp')
|
||||
try {
|
||||
await fs.access(thumbnailPath)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function getArticleLink(slug: string): string {
|
||||
return `${SITE_URL}/news/article/${slug}`
|
||||
return `${SITE_URL}/news/article/${slug}`
|
||||
}
|
||||
|
||||
function getThumbnailUrl(slug: string, hasThumb: boolean): string {
|
||||
if (hasThumb) {
|
||||
return `${SITE_URL}/news/article/${slug}/thumbnail.webp`
|
||||
} else {
|
||||
return `${SITE_URL}/news/default.webp`
|
||||
}
|
||||
if (hasThumb) {
|
||||
return `${SITE_URL}/news/article/${slug}/thumbnail.webp`
|
||||
} else {
|
||||
return `${SITE_URL}/news/default.webp`
|
||||
}
|
||||
}
|
||||
|
||||
async function compileArticles() {
|
||||
await ensureCompiledDir()
|
||||
await ensureCompiledDir()
|
||||
|
||||
const files = await glob(ARTICLES_GLOB)
|
||||
console.log(`🔎 Found ${files.length} markdown articles!`)
|
||||
const articleExports: string[] = []
|
||||
const articlesArray: string[] = []
|
||||
const articlesForRss = []
|
||||
const articlesForJson = []
|
||||
const files = await glob(ARTICLES_GLOB)
|
||||
console.log(`🔎 Found ${files.length} markdown articles!`)
|
||||
const articleExports: string[] = []
|
||||
const articlesArray: string[] = []
|
||||
const articlesForRss = []
|
||||
const articlesForJson = []
|
||||
|
||||
for (const file of files) {
|
||||
const src = await fs.readFile(file, 'utf8')
|
||||
const { content, data } = matter(src)
|
||||
for (const file of files) {
|
||||
const src = await fs.readFile(file, 'utf8')
|
||||
const { content, data } = matter(src)
|
||||
|
||||
const { title, summary, date, slug: frontSlug, authors: authorsData, ...rest } = data
|
||||
if (!title || !summary || !date) {
|
||||
console.error(`❌ Missing required frontmatter in ${file}. Required: title, summary, date`)
|
||||
process.exit(1)
|
||||
}
|
||||
const { title, summary, date, slug: frontSlug, authors: authorsData, ...rest } = data
|
||||
if (!title || !summary || !date) {
|
||||
console.error(`❌ Missing required frontmatter in ${file}. Required: title, summary, date`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const html = md().render(content)
|
||||
const minifiedHtml = await minify(html, {
|
||||
collapseWhitespace: true,
|
||||
removeComments: true,
|
||||
})
|
||||
const html = md().render(content)
|
||||
const minifiedHtml = await minify(html, {
|
||||
collapseWhitespace: true,
|
||||
removeComments: true,
|
||||
})
|
||||
|
||||
const authors = authorsData ? authorsData : []
|
||||
const authors = authorsData ? authorsData : []
|
||||
|
||||
const slug = frontSlug || path.basename(file, '.md')
|
||||
const varName = toVarName(slug)
|
||||
const exportFile = path.posix.join(COMPILED_DIR, `${varName}.ts`)
|
||||
const contentFile = path.posix.join(COMPILED_DIR, `${varName}.content.ts`)
|
||||
const thumbnailPresent = await hasThumbnail(slug)
|
||||
const slug = frontSlug || path.basename(file, '.md')
|
||||
const varName = toVarName(slug)
|
||||
const exportFile = path.posix.join(COMPILED_DIR, `${varName}.ts`)
|
||||
const contentFile = path.posix.join(COMPILED_DIR, `${varName}.content.ts`)
|
||||
const thumbnailPresent = await hasThumbnail(slug)
|
||||
|
||||
const contentTs = `
|
||||
const contentTs = `
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const html = \`${minifiedHtml}\`;
|
||||
`.trimStart()
|
||||
await fs.writeFile(contentFile, contentTs, 'utf8')
|
||||
await fs.writeFile(contentFile, contentTs, 'utf8')
|
||||
|
||||
const ts = `
|
||||
const ts = `
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(\`./${varName}.content\`).then(m => m.html),
|
||||
@@ -96,35 +96,35 @@ export const article = {
|
||||
authors: ${JSON.stringify(authors)},
|
||||
thumbnail: ${thumbnailPresent},
|
||||
${Object.keys(rest)
|
||||
.map((k) => `${k}: ${JSON.stringify(rest[k])},`)
|
||||
.join('\n ')}
|
||||
.map((k) => `${k}: ${JSON.stringify(rest[k])},`)
|
||||
.join('\n ')}
|
||||
};
|
||||
`.trimStart()
|
||||
|
||||
await fs.writeFile(exportFile, ts, 'utf8')
|
||||
articleExports.push(`import { article as ${varName} } from "./${varName}";`)
|
||||
articlesArray.push(varName)
|
||||
await fs.writeFile(exportFile, ts, 'utf8')
|
||||
articleExports.push(`import { article as ${varName} } from "./${varName}";`)
|
||||
articlesArray.push(varName)
|
||||
|
||||
articlesForRss.push({
|
||||
title,
|
||||
summary,
|
||||
date,
|
||||
slug,
|
||||
html: minifiedHtml,
|
||||
} as never)
|
||||
articlesForRss.push({
|
||||
title,
|
||||
summary,
|
||||
date,
|
||||
slug,
|
||||
html: minifiedHtml,
|
||||
} as never)
|
||||
|
||||
articlesForJson.push({
|
||||
title,
|
||||
summary,
|
||||
thumbnail: getThumbnailUrl(slug, thumbnailPresent),
|
||||
date: new Date(date).toISOString(),
|
||||
link: getArticleLink(slug),
|
||||
} as never)
|
||||
}
|
||||
articlesForJson.push({
|
||||
title,
|
||||
summary,
|
||||
thumbnail: getThumbnailUrl(slug, thumbnailPresent),
|
||||
date: new Date(date).toISOString(),
|
||||
link: getArticleLink(slug),
|
||||
} as never)
|
||||
}
|
||||
|
||||
console.log(`📂 Compiled ${files.length} articles.`)
|
||||
console.log(`📂 Compiled ${files.length} articles.`)
|
||||
|
||||
const rootExport = `
|
||||
const rootExport = `
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
${articleExports.join('\n')}
|
||||
|
||||
@@ -133,124 +133,124 @@ export const articles = [
|
||||
];
|
||||
`.trimStart()
|
||||
|
||||
await fs.writeFile(ROOT_FILE, rootExport, 'utf8')
|
||||
console.log(`🌟 Done! Wrote root articles export.`)
|
||||
await fs.writeFile(ROOT_FILE, rootExport, 'utf8')
|
||||
console.log(`🌟 Done! Wrote root articles export.`)
|
||||
|
||||
await generateRssFeed(articlesForRss)
|
||||
await generateJsonFile(articlesForJson)
|
||||
await generateRssFeed(articlesForRss)
|
||||
await generateJsonFile(articlesForJson)
|
||||
}
|
||||
|
||||
async function generateRssFeed(articles): Promise<void> {
|
||||
const sorted = [...articles].sort(
|
||||
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
|
||||
)
|
||||
const sorted = [...articles].sort(
|
||||
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
|
||||
)
|
||||
|
||||
let currentRssArticles: { title: string; html: string }[] = []
|
||||
try {
|
||||
const xml = await fs.readFile(RSS_PATH, 'utf8')
|
||||
const parsed = await parseStringPromise(xml)
|
||||
const items = parsed.rss?.channel?.[0]?.item || []
|
||||
currentRssArticles = items.map((item) => ({
|
||||
title: (item.title?.[0] ?? '').trim(),
|
||||
html: (item['content:encoded']?.[0] ?? '').replace(/^<!\[CDATA\[|\]\]>$/g, '').trim(),
|
||||
}))
|
||||
} catch {
|
||||
currentRssArticles = []
|
||||
}
|
||||
let currentRssArticles: { title: string; html: string }[] = []
|
||||
try {
|
||||
const xml = await fs.readFile(RSS_PATH, 'utf8')
|
||||
const parsed = await parseStringPromise(xml)
|
||||
const items = parsed.rss?.channel?.[0]?.item || []
|
||||
currentRssArticles = items.map((item) => ({
|
||||
title: (item.title?.[0] ?? '').trim(),
|
||||
html: (item['content:encoded']?.[0] ?? '').replace(/^<!\[CDATA\[|\]\]>$/g, '').trim(),
|
||||
}))
|
||||
} catch {
|
||||
currentRssArticles = []
|
||||
}
|
||||
|
||||
const newArr = sorted.map((a) => ({
|
||||
title: (a.title ?? '').trim(),
|
||||
html: (a.html ?? '').trim(),
|
||||
}))
|
||||
const newArr = sorted.map((a) => ({
|
||||
title: (a.title ?? '').trim(),
|
||||
html: (a.html ?? '').trim(),
|
||||
}))
|
||||
|
||||
let isEqual = currentRssArticles.length === newArr.length
|
||||
if (isEqual) {
|
||||
for (let i = 0; i < newArr.length; ++i) {
|
||||
if (
|
||||
currentRssArticles[i].title !== newArr[i].title ||
|
||||
currentRssArticles[i].html !== newArr[i].html
|
||||
) {
|
||||
isEqual = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
let isEqual = currentRssArticles.length === newArr.length
|
||||
if (isEqual) {
|
||||
for (let i = 0; i < newArr.length; ++i) {
|
||||
if (
|
||||
currentRssArticles[i].title !== newArr[i].title ||
|
||||
currentRssArticles[i].html !== newArr[i].html
|
||||
) {
|
||||
isEqual = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isEqual) {
|
||||
console.log(`⏭️ RSS feed not regenerated (articles unchanged)`)
|
||||
return
|
||||
}
|
||||
if (isEqual) {
|
||||
console.log(`⏭️ RSS feed not regenerated (articles unchanged)`)
|
||||
return
|
||||
}
|
||||
|
||||
const feed = new RSS({
|
||||
title: 'Modrinth News',
|
||||
description: 'Keep up-to-date on the latest news from Modrinth.',
|
||||
feed_url: `${SITE_URL}/news/feed/rss.xml`,
|
||||
site_url: `${SITE_URL}/news/`,
|
||||
language: 'en',
|
||||
generator: '@modrinth/blog',
|
||||
})
|
||||
const feed = new RSS({
|
||||
title: 'Modrinth News',
|
||||
description: 'Keep up-to-date on the latest news from Modrinth.',
|
||||
feed_url: `${SITE_URL}/news/feed/rss.xml`,
|
||||
site_url: `${SITE_URL}/news/`,
|
||||
language: 'en',
|
||||
generator: '@modrinth/blog',
|
||||
})
|
||||
|
||||
for (const article of sorted) {
|
||||
feed.item({
|
||||
title: article.title,
|
||||
description: article.summary,
|
||||
url: `${SITE_URL}/news/article/${article.slug}/`,
|
||||
guid: `${SITE_URL}/news/article/${article.slug}/`,
|
||||
date: article.date,
|
||||
custom_elements: [{ 'content:encoded': `<![CDATA[${article.html}]]>` }],
|
||||
})
|
||||
}
|
||||
for (const article of sorted) {
|
||||
feed.item({
|
||||
title: article.title,
|
||||
description: article.summary,
|
||||
url: `${SITE_URL}/news/article/${article.slug}/`,
|
||||
guid: `${SITE_URL}/news/article/${article.slug}/`,
|
||||
date: article.date,
|
||||
custom_elements: [{ 'content:encoded': `<![CDATA[${article.html}]]>` }],
|
||||
})
|
||||
}
|
||||
|
||||
await fs.mkdir(path.dirname(RSS_PATH), { recursive: true })
|
||||
await fs.writeFile(RSS_PATH, feed.xml({ indent: true }), 'utf8')
|
||||
console.log(`📂 RSS feed written to ${RSS_PATH}`)
|
||||
await fs.mkdir(path.dirname(RSS_PATH), { recursive: true })
|
||||
await fs.writeFile(RSS_PATH, feed.xml({ indent: true }), 'utf8')
|
||||
console.log(`📂 RSS feed written to ${RSS_PATH}`)
|
||||
}
|
||||
|
||||
async function generateJsonFile(articles): Promise<void> {
|
||||
const sorted = [...articles].sort(
|
||||
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
|
||||
)
|
||||
const json = { articles: sorted }
|
||||
await fs.mkdir(path.dirname(JSON_PATH), { recursive: true })
|
||||
await fs.writeFile(JSON_PATH, JSON.stringify(json, null, 2) + '\n', 'utf8')
|
||||
console.log(`📝 Wrote JSON articles to ${JSON_PATH}`)
|
||||
const sorted = [...articles].sort(
|
||||
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
|
||||
)
|
||||
const json = { articles: sorted }
|
||||
await fs.mkdir(path.dirname(JSON_PATH), { recursive: true })
|
||||
await fs.writeFile(JSON_PATH, JSON.stringify(json, null, 2) + '\n', 'utf8')
|
||||
console.log(`📝 Wrote JSON articles to ${JSON_PATH}`)
|
||||
}
|
||||
|
||||
async function deleteDirContents(dir: string) {
|
||||
try {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true })
|
||||
await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
const fullPath = path.posix.join(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
await fs.rm(fullPath, { recursive: true, force: true })
|
||||
} else {
|
||||
await fs.unlink(fullPath)
|
||||
}
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`❌ Error deleting contents of ${dir}:`, error)
|
||||
throw error
|
||||
}
|
||||
try {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true })
|
||||
await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
const fullPath = path.posix.join(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
await fs.rm(fullPath, { recursive: true, force: true })
|
||||
} else {
|
||||
await fs.unlink(fullPath)
|
||||
}
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`❌ Error deleting contents of ${dir}:`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function copyPublicAssets() {
|
||||
console.log('🚚 Copying ./public to all PUBLIC_LOCATIONS...')
|
||||
for (const loc of PUBLIC_LOCATIONS) {
|
||||
await deleteDirContents(loc)
|
||||
await copyDir(PUBLIC_SRC, loc)
|
||||
console.log(`📂 Copied ./public to ${loc}`)
|
||||
}
|
||||
console.log('🎉 All public assets copied!')
|
||||
console.log('🚚 Copying ./public to all PUBLIC_LOCATIONS...')
|
||||
for (const loc of PUBLIC_LOCATIONS) {
|
||||
await deleteDirContents(loc)
|
||||
await copyDir(PUBLIC_SRC, loc)
|
||||
console.log(`📂 Copied ./public to ${loc}`)
|
||||
}
|
||||
console.log('🎉 All public assets copied!')
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await compileArticles()
|
||||
await copyPublicAssets()
|
||||
await compileArticles()
|
||||
await copyPublicAssets()
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error('❌ Error in compile.ts:', e)
|
||||
process.exit(1)
|
||||
console.error('❌ Error in compile.ts:', e)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./a_new_chapter_for_modrinth_servers.content`).then((m) => m.html),
|
||||
title: 'A New Chapter for Modrinth Servers',
|
||||
summary: 'Modrinth Servers is now fully operated in-house by the Modrinth Team.',
|
||||
date: '2025-03-13T00:00:00.000Z',
|
||||
slug: 'a-new-chapter-for-modrinth-servers',
|
||||
authors: ['MpxzqsyW', 'Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./a_new_chapter_for_modrinth_servers.content`).then((m) => m.html),
|
||||
title: 'A New Chapter for Modrinth Servers',
|
||||
summary: 'Modrinth Servers is now fully operated in-house by the Modrinth Team.',
|
||||
date: '2025-03-13T00:00:00.000Z',
|
||||
slug: 'a-new-chapter-for-modrinth-servers',
|
||||
authors: ['MpxzqsyW', 'Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./accelerating_development.content`).then((m) => m.html),
|
||||
title: "Accelerating Modrinth's Development",
|
||||
summary: 'Our fundraiser and the future of Modrinth!',
|
||||
date: '2023-02-01T20:00:00.000Z',
|
||||
slug: 'accelerating-development',
|
||||
authors: ['MpxzqsyW', 'Dc7EYhxG', '6plzAzU4'],
|
||||
thumbnail: false,
|
||||
html: () => import(`./accelerating_development.content`).then((m) => m.html),
|
||||
title: "Accelerating Modrinth's Development",
|
||||
summary: 'Our fundraiser and the future of Modrinth!',
|
||||
date: '2023-02-01T20:00:00.000Z',
|
||||
slug: 'accelerating-development',
|
||||
authors: ['MpxzqsyW', 'Dc7EYhxG', '6plzAzU4'],
|
||||
thumbnail: false,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./becoming_sustainable.content`).then((m) => m.html),
|
||||
title: 'Quintupling Creator Revenue and Becoming Sustainable',
|
||||
summary: 'Announcing an update to our monetization program, creator split, and more!',
|
||||
date: '2024-09-13T20:00:00.000Z',
|
||||
slug: 'becoming-sustainable',
|
||||
authors: ['MpxzqsyW', 'Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
short_title: 'Becoming Sustainable',
|
||||
short_summary: 'Announcing 5x creator revenue and updates to the monetization program.',
|
||||
html: () => import(`./becoming_sustainable.content`).then((m) => m.html),
|
||||
title: 'Quintupling Creator Revenue and Becoming Sustainable',
|
||||
summary: 'Announcing an update to our monetization program, creator split, and more!',
|
||||
date: '2024-09-13T20:00:00.000Z',
|
||||
slug: 'becoming-sustainable',
|
||||
authors: ['MpxzqsyW', 'Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
short_title: 'Becoming Sustainable',
|
||||
short_summary: 'Announcing 5x creator revenue and updates to the monetization program.',
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./capital_return.content`).then((m) => m.html),
|
||||
title: 'A Sustainable Path Forward for Modrinth',
|
||||
summary: 'Our capital return and what’s next.',
|
||||
date: '2024-04-04T20:00:00.000Z',
|
||||
slug: 'capital-return',
|
||||
authors: ['MpxzqsyW'],
|
||||
thumbnail: false,
|
||||
html: () => import(`./capital_return.content`).then((m) => m.html),
|
||||
title: 'A Sustainable Path Forward for Modrinth',
|
||||
summary: 'Our capital return and what’s next.',
|
||||
date: '2024-04-04T20:00:00.000Z',
|
||||
slug: 'capital-return',
|
||||
authors: ['MpxzqsyW'],
|
||||
thumbnail: false,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./carbon_ads.content`).then((m) => m.html),
|
||||
title: "Modrinth's Carbon Ads experiment",
|
||||
summary: 'Experimenting with a different ad providers to find one which one works for us.',
|
||||
date: '2022-09-08T00:00:00.000Z',
|
||||
slug: 'carbon-ads',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./carbon_ads.content`).then((m) => m.html),
|
||||
title: "Modrinth's Carbon Ads experiment",
|
||||
summary: 'Experimenting with a different ad providers to find one which one works for us.',
|
||||
date: '2022-09-08T00:00:00.000Z',
|
||||
slug: 'carbon-ads',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./creator_monetization.content`).then((m) => m.html),
|
||||
title: 'Creators can now make money on Modrinth!',
|
||||
summary:
|
||||
'Introducing the Creator Monetization Program allowing creators to earn revenue from their projects.',
|
||||
date: '2022-11-12T00:00:00.000Z',
|
||||
slug: 'creator-monetization',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./creator_monetization.content`).then((m) => m.html),
|
||||
title: 'Creators can now make money on Modrinth!',
|
||||
summary:
|
||||
'Introducing the Creator Monetization Program allowing creators to earn revenue from their projects.',
|
||||
date: '2022-11-12T00:00:00.000Z',
|
||||
slug: 'creator-monetization',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./creator_update.content`).then((m) => m.html),
|
||||
title: 'Creator Update: Analytics, Organizations, Collections, and more',
|
||||
summary: 'December may be over, but we’re not done giving gifts.',
|
||||
date: '2024-01-06T20:00:00.000Z',
|
||||
slug: 'creator-update',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
short_title: 'The Creator Update',
|
||||
short_summary: 'Adding analytics, orgs, collections, and more!',
|
||||
html: () => import(`./creator_update.content`).then((m) => m.html),
|
||||
title: 'Creator Update: Analytics, Organizations, Collections, and more',
|
||||
summary: 'December may be over, but we’re not done giving gifts.',
|
||||
date: '2024-01-06T20:00:00.000Z',
|
||||
slug: 'creator-update',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
short_title: 'The Creator Update',
|
||||
short_summary: 'Adding analytics, orgs, collections, and more!',
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./creator_updates_july_2025.content`).then((m) => m.html),
|
||||
title: 'Creator Updates, July 2025',
|
||||
summary: 'Addressing recent growth and growing pains that have been affecting creators.',
|
||||
date: '2025-07-02T04:20:00.000Z',
|
||||
slug: 'creator-updates-july-2025',
|
||||
authors: ['MpxzqsyW'],
|
||||
thumbnail: false,
|
||||
html: () => import(`./creator_updates_july_2025.content`).then((m) => m.html),
|
||||
title: 'Creator Updates, July 2025',
|
||||
summary: 'Addressing recent growth and growing pains that have been affecting creators.',
|
||||
date: '2025-07-02T04:20:00.000Z',
|
||||
slug: 'creator-updates-july-2025',
|
||||
authors: ['MpxzqsyW'],
|
||||
thumbnail: false,
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./design_refresh.content`).then((m) => m.html),
|
||||
title: 'Introducing Modrinth+, a refreshed site look, and a new advertising system!',
|
||||
summary: 'Learn about this major update to Modrinth.',
|
||||
date: '2024-08-21T20:00:00.000Z',
|
||||
slug: 'design-refresh',
|
||||
authors: ['MpxzqsyW', 'Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
short_title: 'Modrinth+ and New Ads',
|
||||
short_summary:
|
||||
'Introducing a new ad system, a subscription to remove ads, and a redesign of the website!',
|
||||
html: () => import(`./design_refresh.content`).then((m) => m.html),
|
||||
title: 'Introducing Modrinth+, a refreshed site look, and a new advertising system!',
|
||||
summary: 'Learn about this major update to Modrinth.',
|
||||
date: '2024-08-21T20:00:00.000Z',
|
||||
slug: 'design-refresh',
|
||||
authors: ['MpxzqsyW', 'Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
short_title: 'Modrinth+ and New Ads',
|
||||
short_summary:
|
||||
'Introducing a new ad system, a subscription to remove ads, and a redesign of the website!',
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./download_adjustment.content`).then((m) => m.html),
|
||||
title: 'Correcting Inflated Download Counts due to Rate Limiting Issue',
|
||||
summary: 'A rate limiting issue caused inflated download counts in certain countries.',
|
||||
date: '2023-11-10T20:00:00.000Z',
|
||||
slug: 'download-adjustment',
|
||||
authors: ['6plzAzU4', 'MpxzqsyW'],
|
||||
thumbnail: false,
|
||||
short_title: 'Correcting Inflated Download Counts',
|
||||
html: () => import(`./download_adjustment.content`).then((m) => m.html),
|
||||
title: 'Correcting Inflated Download Counts due to Rate Limiting Issue',
|
||||
summary: 'A rate limiting issue caused inflated download counts in certain countries.',
|
||||
date: '2023-11-10T20:00:00.000Z',
|
||||
slug: 'download-adjustment',
|
||||
authors: ['6plzAzU4', 'MpxzqsyW'],
|
||||
thumbnail: false,
|
||||
short_title: 'Correcting Inflated Download Counts',
|
||||
}
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
import { article as windows_borderless_malware_disclosure } from './windows_borderless_malware_disclosure'
|
||||
import { article as whats_modrinth } from './whats_modrinth'
|
||||
import { article as a_new_chapter_for_modrinth_servers } from './a_new_chapter_for_modrinth_servers'
|
||||
import { article as accelerating_development } from './accelerating_development'
|
||||
import { article as becoming_sustainable } from './becoming_sustainable'
|
||||
import { article as capital_return } from './capital_return'
|
||||
import { article as carbon_ads } from './carbon_ads'
|
||||
import { article as creator_monetization } from './creator_monetization'
|
||||
import { article as creator_update } from './creator_update'
|
||||
import { article as creator_updates_july_2025 } from './creator_updates_july_2025'
|
||||
import { article as design_refresh } from './design_refresh'
|
||||
import { article as download_adjustment } from './download_adjustment'
|
||||
import { article as knossos_v2_1_0 } from './knossos_v2_1_0'
|
||||
import { article as licensing_guide } from './licensing_guide'
|
||||
import { article as modpack_changes } from './modpack_changes'
|
||||
import { article as modpacks_alpha } from './modpacks_alpha'
|
||||
import { article as modrinth_app_beta } from './modrinth_app_beta'
|
||||
import { article as modrinth_beta } from './modrinth_beta'
|
||||
import { article as modrinth_servers_beta } from './modrinth_servers_beta'
|
||||
import { article as new_site_beta } from './new_site_beta'
|
||||
import { article as plugins_resource_packs } from './plugins_resource_packs'
|
||||
import { article as pride_campaign_2025 } from './pride_campaign_2025'
|
||||
import { article as redesign } from './redesign'
|
||||
import { article as skins_now_in_modrinth_app } from './skins_now_in_modrinth_app'
|
||||
import { article as two_years_of_modrinth } from './two_years_of_modrinth'
|
||||
import { article as two_years_of_modrinth_history } from './two_years_of_modrinth_history'
|
||||
import { article as skins_now_in_modrinth_app } from './skins_now_in_modrinth_app'
|
||||
import { article as redesign } from './redesign'
|
||||
import { article as pride_campaign_2025 } from './pride_campaign_2025'
|
||||
import { article as plugins_resource_packs } from './plugins_resource_packs'
|
||||
import { article as new_site_beta } from './new_site_beta'
|
||||
import { article as modrinth_servers_beta } from './modrinth_servers_beta'
|
||||
import { article as modrinth_beta } from './modrinth_beta'
|
||||
import { article as modrinth_app_beta } from './modrinth_app_beta'
|
||||
import { article as modpacks_alpha } from './modpacks_alpha'
|
||||
import { article as modpack_changes } from './modpack_changes'
|
||||
import { article as licensing_guide } from './licensing_guide'
|
||||
import { article as knossos_v2_1_0 } from './knossos_v2_1_0'
|
||||
import { article as download_adjustment } from './download_adjustment'
|
||||
import { article as design_refresh } from './design_refresh'
|
||||
import { article as creator_updates_july_2025 } from './creator_updates_july_2025'
|
||||
import { article as creator_update } from './creator_update'
|
||||
import { article as creator_monetization } from './creator_monetization'
|
||||
import { article as carbon_ads } from './carbon_ads'
|
||||
import { article as capital_return } from './capital_return'
|
||||
import { article as becoming_sustainable } from './becoming_sustainable'
|
||||
import { article as accelerating_development } from './accelerating_development'
|
||||
import { article as a_new_chapter_for_modrinth_servers } from './a_new_chapter_for_modrinth_servers'
|
||||
import { article as whats_modrinth } from './whats_modrinth'
|
||||
import { article as windows_borderless_malware_disclosure } from './windows_borderless_malware_disclosure'
|
||||
|
||||
export const articles = [
|
||||
windows_borderless_malware_disclosure,
|
||||
whats_modrinth,
|
||||
two_years_of_modrinth,
|
||||
two_years_of_modrinth_history,
|
||||
skins_now_in_modrinth_app,
|
||||
redesign,
|
||||
pride_campaign_2025,
|
||||
plugins_resource_packs,
|
||||
new_site_beta,
|
||||
modrinth_servers_beta,
|
||||
modrinth_beta,
|
||||
modrinth_app_beta,
|
||||
modpacks_alpha,
|
||||
modpack_changes,
|
||||
licensing_guide,
|
||||
knossos_v2_1_0,
|
||||
download_adjustment,
|
||||
design_refresh,
|
||||
creator_updates_july_2025,
|
||||
creator_update,
|
||||
creator_monetization,
|
||||
carbon_ads,
|
||||
capital_return,
|
||||
becoming_sustainable,
|
||||
accelerating_development,
|
||||
a_new_chapter_for_modrinth_servers,
|
||||
windows_borderless_malware_disclosure,
|
||||
whats_modrinth,
|
||||
two_years_of_modrinth,
|
||||
two_years_of_modrinth_history,
|
||||
skins_now_in_modrinth_app,
|
||||
redesign,
|
||||
pride_campaign_2025,
|
||||
plugins_resource_packs,
|
||||
new_site_beta,
|
||||
modrinth_servers_beta,
|
||||
modrinth_beta,
|
||||
modrinth_app_beta,
|
||||
modpacks_alpha,
|
||||
modpack_changes,
|
||||
licensing_guide,
|
||||
knossos_v2_1_0,
|
||||
download_adjustment,
|
||||
design_refresh,
|
||||
creator_updates_july_2025,
|
||||
creator_update,
|
||||
creator_monetization,
|
||||
carbon_ads,
|
||||
capital_return,
|
||||
becoming_sustainable,
|
||||
accelerating_development,
|
||||
a_new_chapter_for_modrinth_servers,
|
||||
]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./knossos_v2_1_0.content`).then((m) => m.html),
|
||||
title: 'This week in Modrinth development: Filters and Fixes',
|
||||
summary:
|
||||
'Continuing to improve the user interface after a great first week since Modrinth launched out of beta.',
|
||||
date: '2022-03-09T00:00:00.000Z',
|
||||
slug: 'knossos-v2.1.0',
|
||||
authors: ['Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./knossos_v2_1_0.content`).then((m) => m.html),
|
||||
title: 'This week in Modrinth development: Filters and Fixes',
|
||||
summary:
|
||||
'Continuing to improve the user interface after a great first week since Modrinth launched out of beta.',
|
||||
date: '2022-03-09T00:00:00.000Z',
|
||||
slug: 'knossos-v2.1.0',
|
||||
authors: ['Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./licensing_guide.content`).then((m) => m.html),
|
||||
title: "Beginner's Guide to Licensing your Mods",
|
||||
summary:
|
||||
"Software licenses; the nitty-gritty legal aspect of software development. They're more important than you think.",
|
||||
date: '2021-05-16T00:00:00.000Z',
|
||||
slug: 'licensing-guide',
|
||||
authors: ['6plzAzU4', 'aNd6VJql'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./licensing_guide.content`).then((m) => m.html),
|
||||
title: "Beginner's Guide to Licensing your Mods",
|
||||
summary:
|
||||
"Software licenses; the nitty-gritty legal aspect of software development. They're more important than you think.",
|
||||
date: '2021-05-16T00:00:00.000Z',
|
||||
slug: 'licensing-guide',
|
||||
authors: ['6plzAzU4', 'aNd6VJql'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./modpack_changes.content`).then((m) => m.html),
|
||||
title: 'Changes to Modrinth Modpacks',
|
||||
summary: 'CurseForge CDN links requested to be removed by the end of the month',
|
||||
date: '2022-05-28T00:00:00.000Z',
|
||||
slug: 'modpack-changes',
|
||||
authors: ['MpxzqsyW', 'Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./modpack_changes.content`).then((m) => m.html),
|
||||
title: 'Changes to Modrinth Modpacks',
|
||||
summary: 'CurseForge CDN links requested to be removed by the end of the month',
|
||||
date: '2022-05-28T00:00:00.000Z',
|
||||
slug: 'modpack-changes',
|
||||
authors: ['MpxzqsyW', 'Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./modpacks_alpha.content`).then((m) => m.html),
|
||||
title: 'Modrinth Modpacks: Now in alpha testing',
|
||||
summary:
|
||||
"After over a year of development, we're happy to announce that modpack support is now in alpha testing.",
|
||||
date: '2022-05-15T00:00:00.000Z',
|
||||
slug: 'modpacks-alpha',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./modpacks_alpha.content`).then((m) => m.html),
|
||||
title: 'Modrinth Modpacks: Now in alpha testing',
|
||||
summary:
|
||||
"After over a year of development, we're happy to announce that modpack support is now in alpha testing.",
|
||||
date: '2022-05-15T00:00:00.000Z',
|
||||
slug: 'modpacks-alpha',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./modrinth_app_beta.content`).then((m) => m.html),
|
||||
title: 'Introducing Modrinth App Beta',
|
||||
summary:
|
||||
'Changing the modded Minecraft landscape with the new Modrinth App, alongside several other major features.',
|
||||
date: '2023-08-05T20:00:00.000Z',
|
||||
slug: 'modrinth-app-beta',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: false,
|
||||
short_title: 'Modrinth App Beta and Upgraded Authentication',
|
||||
short_summary: 'Launching Modrinth App Beta and upgrading authentication.',
|
||||
html: () => import(`./modrinth_app_beta.content`).then((m) => m.html),
|
||||
title: 'Introducing Modrinth App Beta',
|
||||
summary:
|
||||
'Changing the modded Minecraft landscape with the new Modrinth App, alongside several other major features.',
|
||||
date: '2023-08-05T20:00:00.000Z',
|
||||
slug: 'modrinth-app-beta',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: false,
|
||||
short_title: 'Modrinth App Beta and Upgraded Authentication',
|
||||
short_summary: 'Launching Modrinth App Beta and upgrading authentication.',
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./modrinth_beta.content`).then((m) => m.html),
|
||||
title: 'Welcome to Modrinth Beta',
|
||||
summary:
|
||||
'After six months of work, Modrinth enters Beta, helping modders host their mods with ease!',
|
||||
date: '2020-12-01T00:00:00.000Z',
|
||||
slug: 'modrinth-beta',
|
||||
authors: ['Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./modrinth_beta.content`).then((m) => m.html),
|
||||
title: 'Welcome to Modrinth Beta',
|
||||
summary:
|
||||
'After six months of work, Modrinth enters Beta, helping modders host their mods with ease!',
|
||||
date: '2020-12-01T00:00:00.000Z',
|
||||
slug: 'modrinth-beta',
|
||||
authors: ['Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./modrinth_servers_beta.content`).then((m) => m.html),
|
||||
title: 'Host your own server with Modrinth Servers — now in beta',
|
||||
summary: 'Fast, simple, reliable servers directly integrated into Modrinth.',
|
||||
date: '2024-11-03T06:00:00.000Z',
|
||||
slug: 'modrinth-servers-beta',
|
||||
authors: ['MpxzqsyW', 'Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
short_title: 'Introducing Modrinth Servers',
|
||||
short_summary: 'Host your next Minecraft server with Modrinth.',
|
||||
html: () => import(`./modrinth_servers_beta.content`).then((m) => m.html),
|
||||
title: 'Host your own server with Modrinth Servers — now in beta',
|
||||
summary: 'Fast, simple, reliable servers directly integrated into Modrinth.',
|
||||
date: '2024-11-03T06:00:00.000Z',
|
||||
slug: 'modrinth-servers-beta',
|
||||
authors: ['MpxzqsyW', 'Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
short_title: 'Introducing Modrinth Servers',
|
||||
short_summary: 'Host your next Minecraft server with Modrinth.',
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./new_site_beta.content`).then((m) => m.html),
|
||||
title: '(April Fools 2023) Powering up your experience: Modrinth Technologies™️ beta launch!',
|
||||
summary: "Welcome to the new era of Modrinth. We can't wait to hear your feedback.",
|
||||
date: '2023-04-01T08:00:00.000Z',
|
||||
slug: 'new-site-beta',
|
||||
authors: [],
|
||||
thumbnail: true,
|
||||
short_title: '(April Fools 2023) Modrinth Technologies™️ beta launch!',
|
||||
short_summary: 'Power up your experience.',
|
||||
html: () => import(`./new_site_beta.content`).then((m) => m.html),
|
||||
title: '(April Fools 2023) Powering up your experience: Modrinth Technologies™️ beta launch!',
|
||||
summary: "Welcome to the new era of Modrinth. We can't wait to hear your feedback.",
|
||||
date: '2023-04-01T08:00:00.000Z',
|
||||
slug: 'new-site-beta',
|
||||
authors: [],
|
||||
thumbnail: true,
|
||||
short_title: '(April Fools 2023) Modrinth Technologies™️ beta launch!',
|
||||
short_summary: 'Power up your experience.',
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./plugins_resource_packs.content`).then((m) => m.html),
|
||||
title: 'Plugins and Resource Packs now have a home on Modrinth',
|
||||
summary:
|
||||
'A small update with a big impact: plugins and resource packs are now available on Modrinth!',
|
||||
date: '2022-08-27T00:00:00.000Z',
|
||||
slug: 'plugins-resource-packs',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./plugins_resource_packs.content`).then((m) => m.html),
|
||||
title: 'Plugins and Resource Packs now have a home on Modrinth',
|
||||
summary:
|
||||
'A small update with a big impact: plugins and resource packs are now available on Modrinth!',
|
||||
date: '2022-08-27T00:00:00.000Z',
|
||||
slug: 'plugins-resource-packs',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./pride_campaign_2025.content`).then((m) => m.html),
|
||||
title: 'A Pride Month Success: Over $8,400 Raised for The Trevor Project!',
|
||||
summary: 'Reflecting on our Pride Month fundraiser campaign for LGBTQ+ youth.',
|
||||
date: '2025-07-01T18:00:00.000Z',
|
||||
slug: 'pride-campaign-2025',
|
||||
authors: ['6plzAzU4', 'bOHH0P9Z', '2cqK8Q5p', 'vNcGR3Fd'],
|
||||
thumbnail: true,
|
||||
short_title: 'Pride Month Fundraiser 2025',
|
||||
short_summary: 'A reflection on our Pride Month fundraiser campaign.',
|
||||
html: () => import(`./pride_campaign_2025.content`).then((m) => m.html),
|
||||
title: 'A Pride Month Success: Over $8,400 Raised for The Trevor Project!',
|
||||
summary: 'Reflecting on our Pride Month fundraiser campaign for LGBTQ+ youth.',
|
||||
date: '2025-07-01T18:00:00.000Z',
|
||||
slug: 'pride-campaign-2025',
|
||||
authors: ['6plzAzU4', 'bOHH0P9Z', '2cqK8Q5p', 'vNcGR3Fd'],
|
||||
thumbnail: true,
|
||||
short_title: 'Pride Month Fundraiser 2025',
|
||||
short_summary: 'A reflection on our Pride Month fundraiser campaign.',
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./redesign.content`).then((m) => m.html),
|
||||
title: 'Now showing on Modrinth: A new look!',
|
||||
summary: 'Releasing many new features and improvements, including a redesign!',
|
||||
date: '2022-02-27T00:00:00.000Z',
|
||||
slug: 'redesign',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./redesign.content`).then((m) => m.html),
|
||||
title: 'Now showing on Modrinth: A new look!',
|
||||
summary: 'Releasing many new features and improvements, including a redesign!',
|
||||
date: '2022-02-27T00:00:00.000Z',
|
||||
slug: 'redesign',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./skins_now_in_modrinth_app.content`).then((m) => m.html),
|
||||
title: 'Skins — Now in Modrinth App!',
|
||||
summary:
|
||||
'Customize your look, save your favorite skins, and swap them out in a flash, all within Modrinth App.',
|
||||
date: '2025-07-06T23:45:00.000Z',
|
||||
slug: 'skins-now-in-modrinth-app',
|
||||
authors: ['bOHH0P9Z', 'Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./skins_now_in_modrinth_app.content`).then((m) => m.html),
|
||||
title: 'Skins — Now in Modrinth App!',
|
||||
summary:
|
||||
'Customize your look, save your favorite skins, and swap them out in a flash, all within Modrinth App.',
|
||||
date: '2025-07-06T23:45:00.000Z',
|
||||
slug: 'skins-now-in-modrinth-app',
|
||||
authors: ['bOHH0P9Z', 'Dc7EYhxG'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./two_years_of_modrinth.content`).then((m) => m.html),
|
||||
title: "Modrinth's Anniversary Update",
|
||||
summary: "Marking two years of Modrinth and discussing our New Year's Resolutions for 2023.",
|
||||
date: '2023-01-07T00:00:00.000Z',
|
||||
slug: 'two-years-of-modrinth',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./two_years_of_modrinth.content`).then((m) => m.html),
|
||||
title: "Modrinth's Anniversary Update",
|
||||
summary: "Marking two years of Modrinth and discussing our New Year's Resolutions for 2023.",
|
||||
date: '2023-01-07T00:00:00.000Z',
|
||||
slug: 'two-years-of-modrinth',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./two_years_of_modrinth_history.content`).then((m) => m.html),
|
||||
title: 'Two years of Modrinth: a retrospective',
|
||||
summary: 'The history of Modrinth as we know it from December 2020 to December 2022.',
|
||||
date: '2023-01-07T00:00:00.000Z',
|
||||
slug: 'two-years-of-modrinth-history',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: false,
|
||||
html: () => import(`./two_years_of_modrinth_history.content`).then((m) => m.html),
|
||||
title: 'Two years of Modrinth: a retrospective',
|
||||
summary: 'The history of Modrinth as we know it from December 2020 to December 2022.',
|
||||
date: '2023-01-07T00:00:00.000Z',
|
||||
slug: 'two-years-of-modrinth-history',
|
||||
authors: ['6plzAzU4'],
|
||||
thumbnail: false,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./whats_modrinth.content`).then((m) => m.html),
|
||||
title: 'What is Modrinth?',
|
||||
summary:
|
||||
"Hello, we are Modrinth – an open source mods hosting platform. Sounds dry, doesn't it? So let me tell you our story – and I promise, it won't be boring!",
|
||||
date: '2020-11-27T00:00:00.000Z',
|
||||
slug: 'whats-modrinth',
|
||||
authors: ['aNd6VJql'],
|
||||
thumbnail: false,
|
||||
html: () => import(`./whats_modrinth.content`).then((m) => m.html),
|
||||
title: 'What is Modrinth?',
|
||||
summary:
|
||||
"Hello, we are Modrinth – an open source mods hosting platform. Sounds dry, doesn't it? So let me tell you our story – and I promise, it won't be boring!",
|
||||
date: '2020-11-27T00:00:00.000Z',
|
||||
slug: 'whats-modrinth',
|
||||
authors: ['aNd6VJql'],
|
||||
thumbnail: false,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
export const article = {
|
||||
html: () => import(`./windows_borderless_malware_disclosure.content`).then((m) => m.html),
|
||||
title: 'Malware Discovery Disclosure: "Windows Borderless" mod',
|
||||
summary: 'Threat Analysis and Plan of Action',
|
||||
date: '2024-05-07T20:00:00.000Z',
|
||||
slug: 'windows-borderless-malware-disclosure',
|
||||
authors: ['Dc7EYhxG', 'MpxzqsyW'],
|
||||
thumbnail: true,
|
||||
html: () => import(`./windows_borderless_malware_disclosure.content`).then((m) => m.html),
|
||||
title: 'Malware Discovery Disclosure: "Windows Borderless" mod',
|
||||
summary: 'Threat Analysis and Plan of Action',
|
||||
date: '2024-05-07T20:00:00.000Z',
|
||||
slug: 'windows-borderless-malware-disclosure',
|
||||
authors: ['Dc7EYhxG', 'MpxzqsyW'],
|
||||
thumbnail: true,
|
||||
}
|
||||
|
||||
2
packages/blog/eslint.config.mjs
Normal file
2
packages/blog/eslint.config.mjs
Normal file
@@ -0,0 +1,2 @@
|
||||
import config from '@modrinth/tooling-config/eslint/nuxt.mjs'
|
||||
export default config
|
||||
@@ -1,29 +1,27 @@
|
||||
{
|
||||
"name": "@modrinth/blog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "./index.ts",
|
||||
"types": "./index.d.ts",
|
||||
"scripts": {
|
||||
"lint": "jiti ./check.ts && eslint . && prettier --check .",
|
||||
"fix": "jiti ./compile.ts && eslint . --fix && prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/glob": "^9.0.0",
|
||||
"@types/html-minifier-terser": "^7.0.2",
|
||||
"@types/rss": "^0.0.32",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"jiti": "^2.4.2",
|
||||
"tsconfig": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"glob": "^10.2.7",
|
||||
"gray-matter": "^4.0.3",
|
||||
"html-minifier-terser": "^7.2.0",
|
||||
"rss": "^1.2.2",
|
||||
"xml2js": "^0.6.2"
|
||||
}
|
||||
"name": "@modrinth/blog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "./index.ts",
|
||||
"types": "./index.d.ts",
|
||||
"scripts": {
|
||||
"lint": "jiti ./check.ts && eslint . && prettier --check .",
|
||||
"fix": "jiti ./compile.ts && eslint . --fix && prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modrinth/tooling-config": "workspace:*",
|
||||
"@types/glob": "^9.0.0",
|
||||
"@types/html-minifier-terser": "^7.0.2",
|
||||
"@types/rss": "^0.0.32",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"jiti": "^2.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"glob": "^10.2.7",
|
||||
"gray-matter": "^4.0.3",
|
||||
"html-minifier-terser": "^7.2.0",
|
||||
"rss": "^1.2.2",
|
||||
"xml2js": "^0.6.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"include": [".", ".eslintrc.js"],
|
||||
"exclude": ["dist", "build", "node_modules"],
|
||||
"compilerOptions": {
|
||||
"lib": ["esnext", "dom"],
|
||||
"noImplicitAny": false
|
||||
}
|
||||
"extends": "@modrinth/tooling-config/typescript/base.json"
|
||||
}
|
||||
|
||||
@@ -3,38 +3,38 @@ import * as path from 'path'
|
||||
|
||||
let REPO_ROOT_CACHE: string | null = null
|
||||
export function getRepoRoot(): string {
|
||||
if (REPO_ROOT_CACHE) return REPO_ROOT_CACHE
|
||||
return (REPO_ROOT_CACHE = execSync('git rev-parse --show-toplevel').toString().trim())
|
||||
if (REPO_ROOT_CACHE) return REPO_ROOT_CACHE
|
||||
return (REPO_ROOT_CACHE = execSync('git rev-parse --show-toplevel').toString().trim())
|
||||
}
|
||||
|
||||
export function repoPath(...segments: string[]): string {
|
||||
return path.posix.join(getRepoRoot(), ...segments)
|
||||
return path.posix.join(getRepoRoot(), ...segments)
|
||||
}
|
||||
|
||||
export async function copyDir(
|
||||
src: string,
|
||||
dest: string,
|
||||
logFn: (src: string, dest: string) => void = () => {},
|
||||
src: string,
|
||||
dest: string,
|
||||
logFn: (src: string, dest: string) => void = () => {},
|
||||
): Promise<void> {
|
||||
const { promises: fs } = await import('fs')
|
||||
await fs.mkdir(dest, { recursive: true })
|
||||
const entries = await fs.readdir(src, { withFileTypes: true })
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.posix.join(src, entry.name)
|
||||
const destPath = path.posix.join(dest, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
await copyDir(srcPath, destPath, logFn)
|
||||
} else if (entry.isFile()) {
|
||||
await fs.copyFile(srcPath, destPath)
|
||||
logFn(srcPath, destPath)
|
||||
}
|
||||
}
|
||||
const { promises: fs } = await import('fs')
|
||||
await fs.mkdir(dest, { recursive: true })
|
||||
const entries = await fs.readdir(src, { withFileTypes: true })
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.posix.join(src, entry.name)
|
||||
const destPath = path.posix.join(dest, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
await copyDir(srcPath, destPath, logFn)
|
||||
} else if (entry.isFile()) {
|
||||
await fs.copyFile(srcPath, destPath)
|
||||
logFn(srcPath, destPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function toVarName(file: string): string {
|
||||
return file
|
||||
.replace(/\.md$/, '')
|
||||
.replace(/[^a-zA-Z0-9]/g, '_')
|
||||
.replace(/^_+/, '')
|
||||
.replace(/_+$/, '')
|
||||
return file
|
||||
.replace(/\.md$/, '')
|
||||
.replace(/[^a-zA-Z0-9]/g, '_')
|
||||
.replace(/^_+/, '')
|
||||
.replace(/_+$/, '')
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# `eslint-config-custom`
|
||||
|
||||
Collection of internal eslint configurations.
|
||||
@@ -1,27 +0,0 @@
|
||||
const { resolve } = require('node:path')
|
||||
|
||||
const project = resolve(process.cwd(), 'tsconfig.json')
|
||||
|
||||
module.exports = {
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'prettier',
|
||||
'plugin:import/recommended',
|
||||
'plugin:import/typescript',
|
||||
'turbo',
|
||||
],
|
||||
globals: {
|
||||
React: true,
|
||||
JSX: true,
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project,
|
||||
},
|
||||
},
|
||||
},
|
||||
ignorePatterns: ['node_modules/', 'dist/'],
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
const { resolve } = require('node:path')
|
||||
|
||||
const project = resolve(process.cwd(), 'tsconfig.json')
|
||||
|
||||
module.exports = {
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'@nuxtjs/eslint-config-typescript',
|
||||
'plugin:prettier/recommended',
|
||||
'prettier',
|
||||
'plugin:import/recommended',
|
||||
'plugin:import/typescript',
|
||||
'turbo',
|
||||
],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project,
|
||||
},
|
||||
},
|
||||
},
|
||||
ignorePatterns: ['.nuxt/**', '.output/**', 'node_modules', 'dist/**'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-multiple-template-root': 'off',
|
||||
'import/extensions': ['error', 'always', { ignorePackages: true }],
|
||||
},
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "eslint-config-custom",
|
||||
"license": "MIT",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
||||
"@vue/eslint-config-typescript": "^13.0.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-turbo": "^2.0.7",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-unicorn": "^54.0.0",
|
||||
"typescript": "^5.5.3"
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
const { resolve } = require('node:path')
|
||||
|
||||
const project = resolve(process.cwd(), 'tsconfig.json')
|
||||
|
||||
module.exports = {
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'prettier',
|
||||
'plugin:import/recommended',
|
||||
'plugin:import/typescript',
|
||||
'turbo',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project,
|
||||
},
|
||||
},
|
||||
},
|
||||
ignorePatterns: ['node_modules/', 'dist/', '.eslintrc.js', '*.d.ts'],
|
||||
|
||||
rules: {
|
||||
'import/no-default-export': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-multiple-template-root': 'off',
|
||||
camelcase: 'off',
|
||||
'no-console': 'off',
|
||||
'no-bitwise': 'off',
|
||||
'unicorn/filename-case': 'off',
|
||||
'comma-dangle': ['error', 'only-multiline'],
|
||||
'vue/no-v-html': 'off',
|
||||
},
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['custom/library'],
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
}
|
||||
1
packages/moderation/.prettierignore
Normal file
1
packages/moderation/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
src/locales/**
|
||||
@@ -1,6 +1,8 @@
|
||||
<!-- TODO: After checklist v1.5, move everything into src directory. -->
|
||||
|
||||
# @modrinth/moderation
|
||||
|
||||
This package contains the moderation checklist system used for reviewing projects on Modrinth. It provides a structured and transparent way to define moderation stages, actions, and messages that are displayed to moderators during the review process.
|
||||
This package contains both the moderation checklist system used by moderators for reviewing projects on Modrinth, and the publishing checklist (nag system) that provides automated feedback to project authors during the submission process.
|
||||
|
||||
## Structure
|
||||
|
||||
@@ -9,22 +11,30 @@ The package is organized as follows:
|
||||
```
|
||||
/packages/moderation/
|
||||
├── data/
|
||||
│ ├── checklist.ts # Main checklist definition - imports and exports all stages
|
||||
│ ├── messages/ # Markdown files containing message templates
|
||||
│ ├── checklist.ts # Main moderation checklist definition - imports and exports all stages
|
||||
│ ├── messages/ # Markdown files containing message templates for moderation
|
||||
│ │ ├── title/ # Messages for the title stage
|
||||
│ │ ├── description/ # Messages for the description stage
|
||||
│ │ └── ... # One directory per stage
|
||||
│ └── stages/ # Stage definition files
|
||||
│ ├── title.ts # Title stage definition
|
||||
│ ├── description.ts # Description stage definition
|
||||
│ └── ... # One file per stage
|
||||
│ ├── stages/ # Moderation stage definition files
|
||||
│ │ ├── title.ts # Title stage definition
|
||||
│ │ ├── description.ts # Description stage definition
|
||||
│ │ └── ... # One file per stage
|
||||
│ └── nags/ # Publishing checklist (nag system) files
|
||||
│ ├── core.ts # Core nags (required fields, basic validation)
|
||||
│ └── ...
|
||||
└── types/ # Type definitions
|
||||
├── actions.ts # Action-related types
|
||||
├── messages.ts # Message-related types
|
||||
└── stage.ts # Stage-related types
|
||||
├── actions.ts # Action-related types (moderation)
|
||||
├── messages.ts # Message-related types (moderation)
|
||||
├── stage.ts # Stage-related types (moderation)
|
||||
└── nags.ts # Nag-related types (publishing checklist)
|
||||
```
|
||||
|
||||
## Stages
|
||||
## Moderation Checklist System
|
||||
|
||||
The moderation checklist provides a structured and transparent way to define moderation stages, actions, and messages that are displayed to moderators during the review process.
|
||||
|
||||
### Stages
|
||||
|
||||
A stage represents a discrete step in the moderation process, like checking a project's title, description, or links. Each stage has:
|
||||
|
||||
@@ -35,7 +45,7 @@ A stage represents a discrete step in the moderation process, like checking a pr
|
||||
|
||||
Stages are defined in individual files in the `data/stages` directory and are assembled into the complete checklist in `data/checklist.ts`.
|
||||
|
||||
## Actions
|
||||
### Actions
|
||||
|
||||
Actions represent decisions moderators can make for each stage. They can be buttons, dropdowns, toggles, etc. Actions can have:
|
||||
|
||||
@@ -47,11 +57,11 @@ Actions represent decisions moderators can make for each stage. They can be butt
|
||||
|
||||
Each action requires a unique `id` field that is used for conditional logic and action relationships. The `suggestedStatus` and `severity` fields help determine the overall moderation outcome.
|
||||
|
||||
## Messages
|
||||
### Messages
|
||||
|
||||
Messages are the actual text that will be included in communications to project authors. To promote maintainability and reuse, messages are stored as Markdown files in the `data/messages` directory, organized by stage.
|
||||
|
||||
### Variable replacement
|
||||
#### Variable replacement
|
||||
|
||||
You can use variables in your messages that will be replaced with user input:
|
||||
|
||||
@@ -81,11 +91,11 @@ More text after the variable.
|
||||
|
||||
The `%MESSAGE%` placeholder will be replaced with the text entered by the moderator.
|
||||
|
||||
## Conditional logic
|
||||
### Conditional logic
|
||||
|
||||
The moderation system supports conditional behavior that changes based on the selection of other actions.
|
||||
|
||||
### Conditional messages
|
||||
#### Conditional messages
|
||||
|
||||
You can define different messages for an action based on other selected actions:
|
||||
|
||||
@@ -108,7 +118,7 @@ You can define different messages for an action based on other selected actions:
|
||||
}
|
||||
```
|
||||
|
||||
### Enabling and disabling actions
|
||||
#### Enabling and disabling actions
|
||||
|
||||
Actions can enable or disable other actions when selected:
|
||||
|
||||
@@ -131,19 +141,102 @@ Actions can enable or disable other actions when selected:
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional text inputs
|
||||
#### Conditional text inputs
|
||||
|
||||
Text inputs can be conditionally shown based on selected actions:
|
||||
|
||||
```typescript
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Additional Information',
|
||||
variable: 'INFO',
|
||||
showWhen: {
|
||||
requiredActions: ['specific_action_id'],
|
||||
excludedActions: ['incompatible_action_id'],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Additional Information',
|
||||
variable: 'INFO',
|
||||
showWhen: {
|
||||
requiredActions: ['specific_action_id'],
|
||||
excludedActions: ['incompatible_action_id'],
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
## Publishing Checklist (Nag System)
|
||||
|
||||
The nag system provides automated feedback to project authors during the submission process, helping them improve their projects before they reach moderation. It analyzes project data and provides suggestions, warnings, and requirements.
|
||||
|
||||
### Nags
|
||||
|
||||
A nag represents a specific issue or suggestion for improvement. Each nag has:
|
||||
|
||||
- A unique `id` for identification
|
||||
- A `title` and `description` displayed to the user
|
||||
- A `status` indicating severity: `'required'`, `'warning'`, or `'suggestion'`
|
||||
- A `shouldShow` function that determines when the nag should be displayed
|
||||
- An optional `link` to help users address the issue
|
||||
|
||||
### Internationalization
|
||||
|
||||
Use vintl's `defineMessage` syntax.
|
||||
|
||||
If you want to use context in the messages, you can do so like this:
|
||||
|
||||
```typescript
|
||||
description: (context: NagContext) => {
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
return formatMessage(defineMessage(...), {
|
||||
length: context.project.body?.length || 0,
|
||||
minChars: MIN_DESCRIPTION_CHARS,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Nag Context
|
||||
|
||||
The `NagContext` type provides access to:
|
||||
|
||||
- `project`: Current project data
|
||||
- `versions`: Project versions
|
||||
- `tags`: Frontend "tags" (generated state)
|
||||
- `currentRoute`: Current page route
|
||||
- and other data...
|
||||
|
||||
### Adding New Nags
|
||||
|
||||
To add a new nag:
|
||||
|
||||
1. Add the nag definition to the appropriate category file (or make a new category file and add it to `data/nags.ts`)
|
||||
2. Add corresponding i18n messages to the `.i18n.ts` file
|
||||
3. Implement the `shouldShow` logic based on project state
|
||||
4. Add appropriate links to help users resolve the issue
|
||||
5. Run `pnpm run fix` to fix lint issues & generate the root locale index.json file.
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
// In description.ts
|
||||
{
|
||||
id: 'new-nag',
|
||||
title: messages.newNagTitle,
|
||||
description: messages.newNagDescription,
|
||||
status: 'warning',
|
||||
shouldShow: (context: NagContext) => {
|
||||
// Your validation logic here
|
||||
return someCondition
|
||||
},
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: messages.editDescriptionTitle,
|
||||
shouldShow: (context: NagContext) => context.currentRoute !== 'type-id-settings-description',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// In description.i18n.ts
|
||||
newNagTitle: {
|
||||
id: 'nags.new-nag.title',
|
||||
defaultMessage: 'New Nag Title',
|
||||
},
|
||||
newNagDescription: {
|
||||
id: 'nags.new-nag.description',
|
||||
defaultMessage: 'Description of the new nag issue.',
|
||||
```
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import type { KeybindListener } from '../types/keybinds'
|
||||
|
||||
const keybinds: KeybindListener[] = [
|
||||
{
|
||||
id: 'next-stage',
|
||||
keybind: 'ArrowRight',
|
||||
description: 'Go to next stage',
|
||||
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
||||
action: (ctx) => ctx.actions.tryGoNext(),
|
||||
},
|
||||
{
|
||||
id: 'previous-stage',
|
||||
keybind: 'ArrowLeft',
|
||||
description: 'Go to previous stage',
|
||||
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
||||
action: (ctx) => ctx.actions.tryGoBack(),
|
||||
},
|
||||
{
|
||||
id: 'generate-message',
|
||||
keybind: 'Ctrl+Shift+E',
|
||||
description: 'Generate moderation message',
|
||||
action: (ctx) => ctx.actions.tryGenerateMessage(),
|
||||
},
|
||||
{
|
||||
id: 'toggle-collapse',
|
||||
keybind: 'Shift+C',
|
||||
description: 'Toggle collapse/expand',
|
||||
action: (ctx) => ctx.actions.tryToggleCollapse(),
|
||||
},
|
||||
{
|
||||
id: 'reset-progress',
|
||||
keybind: 'Ctrl+Shift+R',
|
||||
description: 'Reset moderation progress',
|
||||
action: (ctx) => ctx.actions.tryResetProgress(),
|
||||
},
|
||||
{
|
||||
id: 'skip-project',
|
||||
keybind: 'Ctrl+Shift+S',
|
||||
description: 'Skip to next project',
|
||||
enabled: (ctx) => ctx.state.futureProjectCount > 0 && !ctx.state.isDone,
|
||||
action: (ctx) => ctx.actions.trySkipProject(),
|
||||
},
|
||||
]
|
||||
|
||||
export default keybinds
|
||||
@@ -1,32 +0,0 @@
|
||||
import type { ModerationModpackPermissionApprovalType, Project } from '@modrinth/utils'
|
||||
import type { Stage } from '../types/stage'
|
||||
import { PackageOpenIcon } from '@modrinth/assets'
|
||||
|
||||
export default {
|
||||
id: 'modpack-permissions',
|
||||
title: 'Modpack Permissions',
|
||||
icon: PackageOpenIcon,
|
||||
// Replace me please.
|
||||
guidance_url:
|
||||
'https://www.notion.so/Content-Moderation-Cheat-Sheets-22d5ee711bf081a4920ef08879fe6bf5?source=copy_link#22d5ee711bf08116bd8bc1186f357062',
|
||||
shouldShow: (project: Project) => project.project_type === 'modpack',
|
||||
actions: [
|
||||
{
|
||||
id: 'button',
|
||||
type: 'button',
|
||||
label: 'This dummy button must be present or the stage will not appear.',
|
||||
},
|
||||
],
|
||||
} as Stage
|
||||
|
||||
export const finalPermissionMessages: Record<
|
||||
ModerationModpackPermissionApprovalType['id'],
|
||||
string | undefined
|
||||
> = {
|
||||
yes: undefined,
|
||||
'with-attribution-and-source': undefined,
|
||||
'with-attribution': `The following content has attribution requirements, meaning that you must link back to the page where you originally found this content in your Modpack's description or version changelog (e.g. linking a mod's CurseForge page if you got it from CurseForge):`,
|
||||
no: 'The following content is not allowed in Modrinth modpacks due to licensing restrictions. Please contact the author(s) directly for permission or remove the content from your modpack:',
|
||||
'permanent-no': `The following content is not allowed in Modrinth modpacks, regardless of permission obtained. This may be because it breaks Modrinth's content rules or because the authors, upon being contacted for permission, have declined. Please remove the content from your modpack:`,
|
||||
unidentified: `The following content could not be identified. Please provide proof of its origin along with proof that you have permission to include it:`,
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { ReportQuickReply } from '../types/reports'
|
||||
|
||||
export default [
|
||||
{
|
||||
label: 'Antivirus',
|
||||
message: async () => (await import('./messages/reports/antivirus.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Spam',
|
||||
message: async () => (await import('./messages/reports/spam.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Gameplay Issue',
|
||||
message: async () => (await import('./messages/reports/gameplay-issue.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Platform Issue',
|
||||
message: async () => (await import('./messages/reports/platform-issue.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Stale',
|
||||
message: async () => (await import('./messages/reports/stale.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: 'Confirmed Malware',
|
||||
message: async () => (await import('./messages/reports/confirmed-malware.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
] as ReadonlyArray<ReportQuickReply>
|
||||
@@ -1,58 +0,0 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { TagsIcon } from '@modrinth/assets'
|
||||
|
||||
const categories: Stage = {
|
||||
title: "Are the project's tags accurate?",
|
||||
id: 'tags',
|
||||
icon: TagsIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/settings/tags',
|
||||
shouldShow: (project) =>
|
||||
project.categories.length > 0 || project.additional_categories.length > 0,
|
||||
text: async () => {
|
||||
return (await import('../messages/checklist-text/categories.md?raw')).default
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: 'categories_inaccurate',
|
||||
type: 'button',
|
||||
label: 'Inaccurate',
|
||||
weight: 700,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/categories/inaccurate.md?raw')).default,
|
||||
disablesActions: ['categories_optimization_misused', 'categories_resolutions_misused'],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'categories_optimization_misused',
|
||||
type: 'button',
|
||||
label: 'Optimization',
|
||||
weight: 701,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) =>
|
||||
project.categories.includes('optimization') ||
|
||||
project.additional_categories.includes('optimization'),
|
||||
message: async () =>
|
||||
(await import('../messages/categories/inaccurate.md?raw')).default +
|
||||
(await import('../messages/categories/optimization_misused.md?raw')).default,
|
||||
disablesActions: ['categories_inaccurate', 'categories_resolutions_misused'],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'categories_resolutions_misused',
|
||||
type: 'button',
|
||||
label: 'Resolutions',
|
||||
weight: 702,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => project.project_type === 'resourcepack',
|
||||
message: async () =>
|
||||
(await import('../messages/categories/inaccurate.md?raw')).default +
|
||||
(await import('../messages/categories/resolutions_misused.md?raw')).default,
|
||||
disablesActions: ['categories_inaccurate', 'categories_optimization_misused'],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default categories
|
||||
@@ -1,109 +0,0 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { LibraryIcon } from '@modrinth/assets'
|
||||
|
||||
const description: Stage = {
|
||||
title: 'Is the description sufficient, accurate, and accessible?',
|
||||
id: 'description',
|
||||
icon: LibraryIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#general-expectations',
|
||||
navigate: '/',
|
||||
actions: [
|
||||
{
|
||||
id: 'description_insufficient',
|
||||
type: 'button',
|
||||
label: 'Insufficient (custom)',
|
||||
weight: 400,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/description/insufficient.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Please elaborate on how the author can improve their description.',
|
||||
variable: 'EXPLAINER',
|
||||
large: true,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_insufficient_packs',
|
||||
type: 'button',
|
||||
label: 'Insufficient',
|
||||
weight: 401,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/description/insufficient-packs.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_insufficient_projects',
|
||||
type: 'button',
|
||||
label: 'Insufficient',
|
||||
weight: 401,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
shouldShow: (project) => project.project_type !== 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/description/insufficient-projects.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_non_english',
|
||||
type: 'button',
|
||||
label: 'Non-english',
|
||||
weight: 402,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/description/non-english.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_unfinished',
|
||||
type: 'button',
|
||||
label: 'Unfinished',
|
||||
weight: 403,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/description/unfinished.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_headers_as_body',
|
||||
type: 'button',
|
||||
label: 'Headers as body text',
|
||||
weight: 404,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/description/headers-as-body.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_image_only',
|
||||
type: 'button',
|
||||
label: 'Image-only',
|
||||
weight: 405,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/description/image-only.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_non_standard_text',
|
||||
type: 'button',
|
||||
label: 'Non-standard text',
|
||||
weight: 406,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () =>
|
||||
(await import('../messages/description/non-standard-text.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'description_clarity',
|
||||
type: 'button',
|
||||
label: 'Unclear / Misleading',
|
||||
weight: 407,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/description/clarity.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default description
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { ImageIcon } from '@modrinth/assets'
|
||||
|
||||
const gallery: Stage = {
|
||||
title: "Are this project's gallery images sufficient?",
|
||||
id: 'gallery',
|
||||
icon: ImageIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#general-expectations',
|
||||
navigate: '/gallery',
|
||||
actions: [
|
||||
{
|
||||
id: 'gallery_insufficient',
|
||||
type: 'button',
|
||||
label: 'Insufficient',
|
||||
weight: 900,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/gallery/insufficient.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'gallery_not_relevant',
|
||||
type: 'button',
|
||||
label: 'Not relevant',
|
||||
weight: 901,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => project.gallery && project.gallery.length > 0,
|
||||
message: async () => (await import('../messages/gallery/not-relevant.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default gallery
|
||||
@@ -1,84 +0,0 @@
|
||||
import { BookTextIcon } from '@modrinth/assets'
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const licensesNotRequiringSource: string[] = [
|
||||
'LicenseRef-All-Rights-Reserved',
|
||||
'Apache-2.0',
|
||||
'BSD-2-Clause',
|
||||
'BSD-3-Clause',
|
||||
'CC0-1.0',
|
||||
'CC-BY-4.0',
|
||||
'CC-BY-SA-4.0',
|
||||
'CC-BY-NC-4.0',
|
||||
'CC-BY-NC-SA-4.0',
|
||||
'CC-BY-ND-4.0',
|
||||
'CC-BY-NC-ND-4.0',
|
||||
'ISC',
|
||||
'MIT',
|
||||
'Zlib',
|
||||
]
|
||||
|
||||
const licenseStage: Stage = {
|
||||
title: 'Is this license and link valid?',
|
||||
text: async () => (await import('../messages/checklist-text/licensing.md?raw')).default,
|
||||
id: 'license',
|
||||
icon: BookTextIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/settings/license',
|
||||
actions: [
|
||||
{
|
||||
id: 'license_invalid_link',
|
||||
type: 'button',
|
||||
label: 'Invalid Link',
|
||||
weight: 600,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
shouldShow: (project) => Boolean(project.license.url),
|
||||
message: async () => (await import('../messages/license/invalid_link.md?raw')).default,
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'license_invalid_link-custom_license',
|
||||
type: 'toggle',
|
||||
label: 'Invalid Link: Custom License',
|
||||
weight: 601,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () =>
|
||||
(await import('../messages/license/invalid_link-custom_license.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'license_no_source',
|
||||
type: 'conditional-button',
|
||||
label: 'No Source',
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'medium',
|
||||
shouldShow: (project) => !licensesNotRequiringSource.includes(project.license.id),
|
||||
messageVariants: [
|
||||
{
|
||||
conditions: {
|
||||
excludedActions: ['license_no_source-fork'],
|
||||
},
|
||||
weight: 602,
|
||||
message: async () => (await import('../messages/license/no_source.md?raw')).default,
|
||||
},
|
||||
],
|
||||
fallbackWeight: 602,
|
||||
fallbackMessage: async () => '',
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'license_no_source-fork',
|
||||
type: 'toggle',
|
||||
label: 'No Source: Fork',
|
||||
weight: 602,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/license/no_source-fork.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default licenseStage
|
||||
@@ -1,88 +0,0 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { LinkIcon } from '@modrinth/assets'
|
||||
|
||||
const links: Stage = {
|
||||
title: "Are the project's links accurate and accessible?",
|
||||
id: 'links',
|
||||
icon: LinkIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules',
|
||||
navigate: '/settings/links',
|
||||
shouldShow: (project) =>
|
||||
Boolean(
|
||||
project.issues_url ||
|
||||
project.source_url ||
|
||||
project.wiki_url ||
|
||||
project.discord_url ||
|
||||
project.donation_urls.length > 0,
|
||||
),
|
||||
text: async (project) => {
|
||||
let text = (await import('../messages/checklist-text/links/base.md?raw')).default
|
||||
|
||||
if (project.donation_urls.length > 0) {
|
||||
text += (await import('../messages/checklist-text/links/donation/donations.md?raw')).default
|
||||
|
||||
for (const donation of project.donation_urls) {
|
||||
text += (await import(`../messages/checklist-text/links/donation/donation.md?raw`)).default
|
||||
.replace('{URL}', donation.url)
|
||||
.replace('{PLATFORM}', donation.platform)
|
||||
}
|
||||
}
|
||||
|
||||
return text
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: 'links_misused',
|
||||
type: 'button',
|
||||
label: 'Links are misused',
|
||||
weight: 500,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/links/misused.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'What links are misused?',
|
||||
variable: 'MISUSED_LINKS',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'links_unaccessible',
|
||||
type: 'button',
|
||||
label: 'Links are inaccessible',
|
||||
weight: 510,
|
||||
suggestedStatus: 'flagged',
|
||||
// Theoretically a conditional could go here to prevent overlap of misuse and unaccessible messages repeating while still allowing for a multi-select in each.
|
||||
// if links_misused was selected, send nothing.
|
||||
message: async () => (await import('../messages/links/not_accessible.md?raw')).default,
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'links_unaccessible_options',
|
||||
type: 'multi-select-chips',
|
||||
label: 'Warn of inaccessible link?',
|
||||
shouldShow: (project) => Boolean(project.source_url || project.discord_url),
|
||||
options: [
|
||||
{
|
||||
label: 'Source',
|
||||
weight: 511,
|
||||
shouldShow: (project) => Boolean(project.source_url),
|
||||
message: async () =>
|
||||
(await import('../messages/links/not_accessible-source.md?raw')).default,
|
||||
},
|
||||
{
|
||||
label: 'Discord',
|
||||
weight: 512,
|
||||
shouldShow: (project) => Boolean(project.discord_url),
|
||||
message: async () =>
|
||||
(await import('../messages/links/not_accessible-discord.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default links
|
||||
@@ -1,111 +0,0 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { CopyrightIcon } from '@modrinth/assets'
|
||||
|
||||
const reupload: Stage = {
|
||||
title: 'Does the author have proper permissions to post this project?',
|
||||
id: 'reupload',
|
||||
icon: CopyrightIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules',
|
||||
actions: [
|
||||
{
|
||||
id: 'reupload_reupload',
|
||||
type: 'button',
|
||||
label: 'Re-upload',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/reupload/reupload.md?raw')).default,
|
||||
disablesActions: [
|
||||
'reupload_unclear_fork',
|
||||
'reupload_insufficient_fork',
|
||||
'reupload_request_proof',
|
||||
'reupload_identity_verification',
|
||||
],
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'What is the title of the original project?',
|
||||
variable: 'ORIGINAL_PROJECT',
|
||||
required: true,
|
||||
suggestions: ['Vanilla Tweaks'],
|
||||
},
|
||||
{
|
||||
label: 'What is the author of the original project?',
|
||||
variable: 'ORIGINAL_AUTHOR',
|
||||
required: true,
|
||||
suggestions: ['Vanilla Tweaks Team'],
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'reupload_unclear_fork',
|
||||
type: 'button',
|
||||
label: 'Unclear Fork',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/reupload/fork.md?raw')).default,
|
||||
disablesActions: [
|
||||
'reupload_reupload',
|
||||
'reupload_insufficient_fork',
|
||||
'reupload_request_proof',
|
||||
'reupload_identity_verification',
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'reupload_insufficient_fork',
|
||||
type: 'button',
|
||||
label: 'Insufficient Fork',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () => (await import('../messages/reupload/insufficient_fork.md?raw')).default,
|
||||
disablesActions: [
|
||||
'reupload_unclear_fork',
|
||||
'reupload_reupload',
|
||||
'reupload_request_proof',
|
||||
'reupload_identity_verification',
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'reupload_request_proof',
|
||||
type: 'button',
|
||||
label: 'Proof of permissions',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () =>
|
||||
(await import('../messages/reupload/proof_of_permissions.md?raw')).default,
|
||||
disablesActions: [
|
||||
'reupload_reupload',
|
||||
'reupload_unclear_fork',
|
||||
'reupload_insufficient_fork',
|
||||
'reupload_identity_verification',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'reupload_identity_verification',
|
||||
type: 'button',
|
||||
label: 'Verify Identity',
|
||||
weight: 1100,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
message: async () =>
|
||||
(await import('../messages/reupload/identity_verification.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Where else can the project be found?',
|
||||
variable: 'PLATFORM',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
disablesActions: [
|
||||
'reupload_reupload',
|
||||
'reupload_insufficient_fork',
|
||||
'reupload_request_proof',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default reupload
|
||||
@@ -1,33 +0,0 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { ListBulletedIcon } from '@modrinth/assets'
|
||||
|
||||
const ruleFollowing: Stage = {
|
||||
title: 'Does this project violate the rules?',
|
||||
id: 'rule-following',
|
||||
icon: ListBulletedIcon,
|
||||
guidance_url:
|
||||
'https://www.notion.so/Creator-Communication-Guide-1b65ee711bf080ec9337e3ccdded146c',
|
||||
navigate: '/moderation',
|
||||
actions: [
|
||||
{
|
||||
id: 'rule_breaking_yes',
|
||||
type: 'button',
|
||||
label: 'Yes',
|
||||
weight: 0,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'critical',
|
||||
message: async () => (await import('../messages/rule-breaking.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Please explain to the user how it infringes on our content rules.',
|
||||
variable: 'MESSAGE',
|
||||
required: true,
|
||||
large: true,
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default ruleFollowing
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { GlobeIcon } from '@modrinth/assets'
|
||||
|
||||
const sideTypes: Stage = {
|
||||
title: "Is the project's environment information accurate?",
|
||||
id: 'environment',
|
||||
icon: GlobeIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/settings',
|
||||
text: async () => (await import('../messages/checklist-text/side_types.md?raw')).default,
|
||||
actions: [
|
||||
{
|
||||
id: 'side_types_inaccurate_modpack',
|
||||
type: 'button',
|
||||
label: 'Inaccurate',
|
||||
weight: 800,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/side-types/inaccurate-modpack.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'side_types_inaccurate_mod',
|
||||
type: 'button',
|
||||
label: 'Inaccurate',
|
||||
weight: 800,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => project.project_type === 'mod',
|
||||
message: async () => (await import('../messages/side-types/inaccurate-mod.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default sideTypes
|
||||
@@ -1,88 +0,0 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction, DropdownAction, DropdownActionOption } from '../../types/actions'
|
||||
import { TriangleAlertIcon } from '@modrinth/assets'
|
||||
|
||||
const statusAlerts: Stage = {
|
||||
title: `Is anything else affecting this project's status?`,
|
||||
id: 'status-alerts',
|
||||
icon: TriangleAlertIcon,
|
||||
text: async () => (await import('../messages/checklist-text/status-alerts/text.md?raw')).default,
|
||||
guidance_url:
|
||||
'https://www.notion.so/Project-Modification-Guidelines-22e5ee711bf080628416f0471ba6af02',
|
||||
navigate: '/moderation',
|
||||
actions: [
|
||||
{
|
||||
id: 'status_corrections_applied',
|
||||
type: 'button',
|
||||
label: 'Corrections applied',
|
||||
weight: -999999,
|
||||
suggestedStatus: 'approved',
|
||||
disablesActions: ['status_private_use', 'status_account_issues'],
|
||||
message: async () => (await import('../messages/status-alerts/fixed.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'status_private_use',
|
||||
type: 'button',
|
||||
label: 'Private use',
|
||||
weight: -999999,
|
||||
suggestedStatus: 'flagged',
|
||||
disablesActions: ['status_corrections_applied', 'status_account_issues'],
|
||||
message: async () => (await import('../messages/status-alerts/private.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'status_account_issues',
|
||||
type: 'button',
|
||||
label: 'Account issues',
|
||||
weight: -999999,
|
||||
suggestedStatus: 'rejected',
|
||||
disablesActions: ['status_corrections_applied', 'status_private_use'],
|
||||
message: async () =>
|
||||
(await import('../messages/status-alerts/account_issues.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'status_tec_source_request',
|
||||
type: 'button',
|
||||
label: `Request Source`,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'critical',
|
||||
disablesActions: ['status_corrections_applied', 'status_private_use'],
|
||||
shouldShow: (project) =>
|
||||
project.project_type === 'mod' ||
|
||||
project.project_type === 'shader' ||
|
||||
project.project_type.toString() === 'plugin',
|
||||
weight: -999999,
|
||||
message: async () => '',
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'status_tec_source_request_options',
|
||||
type: 'dropdown',
|
||||
label: 'Why are you requesting source?',
|
||||
options: [
|
||||
{
|
||||
label: 'Obfuscated',
|
||||
weight: 999999,
|
||||
message: async () =>
|
||||
(await import('../messages/status-alerts/tec/source_request-obfs.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Binaries',
|
||||
weight: 999000,
|
||||
message: async () =>
|
||||
(await import('../messages/status-alerts/tec/source_request-bins.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
],
|
||||
} as DropdownAction,
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'status_automod_confusion',
|
||||
type: 'button',
|
||||
label: `Automod confusion`,
|
||||
weight: -999999,
|
||||
message: async () =>
|
||||
(await import('../messages/status-alerts/automod_confusion.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default statusAlerts
|
||||
@@ -1,53 +0,0 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction } from '../../types/actions'
|
||||
import { AlignLeftIcon } from '@modrinth/assets'
|
||||
|
||||
const summary: Stage = {
|
||||
title: "Is the project's summary sufficient?",
|
||||
text: async () => (await import('../messages/checklist-text/summary/summary.md?raw')).default,
|
||||
id: 'summary',
|
||||
icon: AlignLeftIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
actions: [
|
||||
{
|
||||
id: 'summary_insufficient',
|
||||
type: 'button',
|
||||
label: 'Insufficient',
|
||||
weight: 300,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
disablesActions: ['summary_repeat_title'],
|
||||
message: async () => (await import('../messages/summary/insufficient.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'summary_repeat_title',
|
||||
type: 'button',
|
||||
label: 'Repeat of title',
|
||||
weight: 300,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
disablesActions: ['summary_insufficient'],
|
||||
message: async () => (await import('../messages/summary/repeat-title.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'summary_formatting',
|
||||
type: 'button',
|
||||
label: 'Formatting',
|
||||
weight: 301,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/summary/formatting.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'summary_non_english',
|
||||
type: 'button',
|
||||
label: 'Non-english',
|
||||
weight: 302,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/summary/non-english.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default summary
|
||||
@@ -1,96 +0,0 @@
|
||||
import { BookOpenIcon } from '@modrinth/assets'
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { Project } from '@modrinth/utils'
|
||||
|
||||
function hasCustomSlug(project: Project): boolean {
|
||||
return (
|
||||
project.slug !==
|
||||
project.title
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replaceAll(' ', '-')
|
||||
.replaceAll(/[^a-zA-Z0-9!@$()`.+,_"-]/g, '')
|
||||
.replaceAll(/--+/gm, '-')
|
||||
)
|
||||
}
|
||||
|
||||
const titleSlug: Stage = {
|
||||
title: 'Are the Name and URL accurate and appropriate?',
|
||||
id: 'title-&-slug',
|
||||
text: async (project) => {
|
||||
let text = (await import('../messages/checklist-text/title-slug/title.md?raw')).default
|
||||
if (hasCustomSlug(project))
|
||||
text += (await import('../messages/checklist-text/title-slug/slug.md?raw')).default
|
||||
return text
|
||||
},
|
||||
icon: BookOpenIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
actions: [
|
||||
{
|
||||
id: 'title_useless_info',
|
||||
type: 'button',
|
||||
label: 'Contains useless info',
|
||||
weight: 100,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'low',
|
||||
message: async () => (await import('../messages/title/useless-info.md?raw')).default,
|
||||
},
|
||||
{
|
||||
id: 'title_minecraft_branding',
|
||||
type: 'button',
|
||||
label: 'Minecraft title',
|
||||
weight: 100,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/title/minecraft-branding.md?raw')).default,
|
||||
},
|
||||
{
|
||||
id: 'title_similarities',
|
||||
type: 'button',
|
||||
label: 'Title similarities',
|
||||
weight: 110,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () => (await import('../messages/title/similarities.md?raw')).default,
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'title_similarities_options',
|
||||
type: 'multi-select-chips',
|
||||
label: 'Similarities additional info',
|
||||
options: [
|
||||
{
|
||||
label: 'Modpack named after mod',
|
||||
weight: 111,
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/title/similarities-modpack.md?raw')).default,
|
||||
},
|
||||
{
|
||||
label: 'Forked project',
|
||||
weight: 112,
|
||||
message: async () =>
|
||||
(await import('../messages/title/similarities-fork.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'slug_misused_options',
|
||||
type: 'multi-select-chips',
|
||||
label: 'Slug issues?',
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'low',
|
||||
shouldShow: (project) => hasCustomSlug(project),
|
||||
options: [
|
||||
{
|
||||
label: 'Misused',
|
||||
weight: 200,
|
||||
message: async () => (await import('../messages/slug/misused.md?raw')).default,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default titleSlug
|
||||
@@ -1,24 +0,0 @@
|
||||
import { XIcon } from '@modrinth/assets'
|
||||
import type { Stage } from '../../types/stage'
|
||||
|
||||
const undefinedProjectStage: Stage = {
|
||||
title: 'This project is undefined!',
|
||||
id: 'undefined-project',
|
||||
icon: XIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/versions',
|
||||
shouldShow: (project) => project.versions.length === 0,
|
||||
actions: [
|
||||
{
|
||||
id: 'undefined_no_versions',
|
||||
type: 'button',
|
||||
label: 'No Versions',
|
||||
weight: -100,
|
||||
suggestedStatus: 'rejected',
|
||||
message: async () =>
|
||||
(await import('../messages/undefined-project/no_versions.md?raw')).default,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default undefinedProjectStage
|
||||
@@ -1,174 +0,0 @@
|
||||
import type { Stage } from '../../types/stage'
|
||||
import type { ButtonAction, DropdownAction, DropdownActionOption } from '../../types/actions'
|
||||
import { VersionIcon } from '@modrinth/assets'
|
||||
|
||||
const versions: Stage = {
|
||||
title: "Are this project's files correct?",
|
||||
id: 'versions',
|
||||
icon: VersionIcon,
|
||||
guidance_url: 'https://modrinth.com/legal/rules#miscellaneous',
|
||||
navigate: '/versions',
|
||||
actions: [
|
||||
{
|
||||
id: 'versions_incorrect_additional',
|
||||
type: 'button',
|
||||
label: 'Incorrect additional files',
|
||||
weight: 1000,
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/incorrect_additional_files.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_incorrect_project_type',
|
||||
type: 'button',
|
||||
label: 'Incorrect Project Type',
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'medium',
|
||||
weight: -999999,
|
||||
message: async () => '',
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'versions_incorrect_project_type_options',
|
||||
type: 'dropdown',
|
||||
label: 'What type should this project be?',
|
||||
options: [
|
||||
{
|
||||
label: 'Modpack',
|
||||
weight: 1001,
|
||||
shouldShow: (project) => project.project_type !== 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/invalid-modpacks.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Resource Pack',
|
||||
weight: 1001,
|
||||
shouldShow: (project) => project.project_type !== 'resourcepack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/invalid-resourcepacks.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Data Pack',
|
||||
weight: 1001,
|
||||
shouldShow: (project) => !project.loaders.includes('datapack'),
|
||||
message: async () =>
|
||||
(await import('../messages/versions/invalid-datapacks.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
],
|
||||
} as DropdownAction,
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_alternate_versions',
|
||||
type: 'button',
|
||||
label: 'Alternate Versions',
|
||||
suggestedStatus: 'flagged',
|
||||
severity: 'medium',
|
||||
weight: -999999,
|
||||
message: async () => '',
|
||||
enablesActions: [
|
||||
{
|
||||
id: 'versions_alternate_versions_options',
|
||||
type: 'dropdown',
|
||||
label: 'How are the alternate versions distributed?',
|
||||
options: [
|
||||
{
|
||||
label: 'Primary Files',
|
||||
weight: 1002,
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-primary.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Additional Files',
|
||||
weight: 1002,
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-additional.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Monofile',
|
||||
weight: 1002,
|
||||
shouldShow: (project) =>
|
||||
project.project_type === 'resourcepack' || project.loaders.includes('datapack'),
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-mono.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Server Files (Primary Files)',
|
||||
weight: 1002,
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-server.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'Server Files (Additional Files)',
|
||||
weight: 1002,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-server-additional.md?raw'))
|
||||
.default,
|
||||
} as DropdownActionOption,
|
||||
{
|
||||
label: 'mods.zip',
|
||||
weight: 1002,
|
||||
suggestedStatus: 'rejected',
|
||||
severity: 'high',
|
||||
shouldShow: (project) => project.project_type === 'modpack',
|
||||
message: async () =>
|
||||
(await import('../messages/versions/alternate_versions-zip.md?raw')).default,
|
||||
} as DropdownActionOption,
|
||||
],
|
||||
} as DropdownAction,
|
||||
],
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_vanilla_assets',
|
||||
type: 'button',
|
||||
label: 'Vanilla Assets',
|
||||
suggestedStatus: `rejected`,
|
||||
severity: `medium`,
|
||||
weight: 1003,
|
||||
shouldShow: (project) => project.project_type === 'resourcepack',
|
||||
message: async () => (await import('../messages/versions/vanilla_assets.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_redist_libs',
|
||||
type: 'button',
|
||||
label: 'Packed Libs',
|
||||
suggestedStatus: `rejected`,
|
||||
severity: `medium`,
|
||||
weight: 1003,
|
||||
shouldShow: (project) => project.project_type === 'mod' || project.project_type === 'plugin',
|
||||
message: async () => (await import('../messages/versions/redist_libs.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'versions_duplicate_primary_files',
|
||||
type: 'button',
|
||||
label: 'Duplicate Primary Files',
|
||||
suggestedStatus: 'flagged',
|
||||
severity: `medium`,
|
||||
weight: 1004,
|
||||
message: async () => (await import('../messages/versions/broken_version.md?raw')).default,
|
||||
} as ButtonAction,
|
||||
{
|
||||
id: 'unsupported_project_type',
|
||||
type: 'button',
|
||||
label: `Unsupported`,
|
||||
suggestedStatus: `rejected`,
|
||||
severity: `medium`,
|
||||
weight: 1005,
|
||||
message: async () =>
|
||||
(await import('../messages/versions/unsupported_project.md?raw')).default,
|
||||
relevantExtraInput: [
|
||||
{
|
||||
label: 'Project Type',
|
||||
required: true,
|
||||
variable: 'INVALID_TYPE',
|
||||
},
|
||||
],
|
||||
} as ButtonAction,
|
||||
],
|
||||
}
|
||||
|
||||
export default versions
|
||||
2
packages/moderation/eslint.config.mjs
Normal file
2
packages/moderation/eslint.config.mjs
Normal file
@@ -0,0 +1,2 @@
|
||||
import config from '@modrinth/tooling-config/eslint/nuxt.mjs'
|
||||
export default config
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"name": "@modrinth/moderation",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "./index.ts",
|
||||
"types": "./index.d.ts",
|
||||
"scripts": {
|
||||
"lint": "eslint . && prettier --check .",
|
||||
"fix": "eslint . --fix && prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"@modrinth/assets": "workspace:*",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"tsconfig": "workspace:*"
|
||||
}
|
||||
"name": "@modrinth/moderation",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
"lint": "eslint . && prettier --check .",
|
||||
"fix": "eslint . --fix && prettier --write .",
|
||||
"intl:extract": "formatjs extract \"**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore \"**/*.d.ts\" --ignore \"node_modules/**/*\" --out-file src/locales/en-US/index.json --preserve-whitespace"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modrinth/assets": "workspace:*",
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formatjs/cli": "^6.2.12",
|
||||
"@vintl/vintl": "^4.4.1",
|
||||
"@modrinth/tooling-config": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import type { Stage } from '../types/stage'
|
||||
import modpackPermissionsStage from './modpack-permissions-stage'
|
||||
import categories from './stages/categories'
|
||||
import reupload from './stages/reupload'
|
||||
import description from './stages/description'
|
||||
import gallery from './stages/gallery'
|
||||
import license from './stages/license'
|
||||
import links from './stages/links'
|
||||
import reupload from './stages/reupload'
|
||||
import ruleFollowing from './stages/rule-following'
|
||||
import sideTypes from './stages/side-types'
|
||||
import statusAlerts from './stages/status-alerts'
|
||||
import summary from './stages/summary'
|
||||
import titleSlug from './stages/title-slug'
|
||||
import versions from './stages/versions'
|
||||
import license from './stages/license'
|
||||
import undefinedProject from './stages/undefined-project'
|
||||
import statusAlerts from './stages/status-alerts'
|
||||
import versions from './stages/versions'
|
||||
|
||||
export default [
|
||||
titleSlug,
|
||||
summary,
|
||||
description,
|
||||
links,
|
||||
license,
|
||||
categories,
|
||||
sideTypes,
|
||||
gallery,
|
||||
versions,
|
||||
reupload,
|
||||
ruleFollowing,
|
||||
modpackPermissionsStage,
|
||||
statusAlerts,
|
||||
undefinedProject,
|
||||
titleSlug,
|
||||
summary,
|
||||
description,
|
||||
links,
|
||||
license,
|
||||
categories,
|
||||
sideTypes,
|
||||
gallery,
|
||||
versions,
|
||||
reupload,
|
||||
ruleFollowing,
|
||||
modpackPermissionsStage,
|
||||
statusAlerts,
|
||||
undefinedProject,
|
||||
] as ReadonlyArray<Stage>
|
||||
45
packages/moderation/src/data/keybinds.ts
Normal file
45
packages/moderation/src/data/keybinds.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { KeybindListener } from '../types/keybinds'
|
||||
|
||||
const keybinds: KeybindListener[] = [
|
||||
{
|
||||
id: 'next-stage',
|
||||
keybind: 'ArrowRight',
|
||||
description: 'Go to next stage',
|
||||
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
||||
action: (ctx) => ctx.actions.tryGoNext(),
|
||||
},
|
||||
{
|
||||
id: 'previous-stage',
|
||||
keybind: 'ArrowLeft',
|
||||
description: 'Go to previous stage',
|
||||
enabled: (ctx) => !ctx.state.isDone && !ctx.state.hasGeneratedMessage,
|
||||
action: (ctx) => ctx.actions.tryGoBack(),
|
||||
},
|
||||
{
|
||||
id: 'generate-message',
|
||||
keybind: 'Ctrl+Shift+E',
|
||||
description: 'Generate moderation message',
|
||||
action: (ctx) => ctx.actions.tryGenerateMessage(),
|
||||
},
|
||||
{
|
||||
id: 'toggle-collapse',
|
||||
keybind: 'Shift+C',
|
||||
description: 'Toggle collapse/expand',
|
||||
action: (ctx) => ctx.actions.tryToggleCollapse(),
|
||||
},
|
||||
{
|
||||
id: 'reset-progress',
|
||||
keybind: 'Ctrl+Shift+R',
|
||||
description: 'Reset moderation progress',
|
||||
action: (ctx) => ctx.actions.tryResetProgress(),
|
||||
},
|
||||
{
|
||||
id: 'skip-project',
|
||||
keybind: 'Ctrl+Shift+S',
|
||||
description: 'Skip to next project',
|
||||
enabled: (ctx) => ctx.state.futureProjectCount > 0 && !ctx.state.isDone,
|
||||
action: (ctx) => ctx.actions.trySkipProject(),
|
||||
},
|
||||
]
|
||||
|
||||
export default keybinds
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user