You've already forked AstralRinth
forked from didirus/AstralRinth
Update utilities and documentation
This commit is contained in:
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.formatOnPaste": true,
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,7 @@ _The Modrinth component library, in Svelte_
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### ⚠️ In-progress: Not accepting contributions currently.
|
Read the documentation at [omorphia.modrinth.com.](https://omorphia.modrinth.com)
|
||||||
|
|
||||||
Learn more at [omorphia.modrinth.com.](https://omorphia.modrinth.com)
|
|
||||||
|
|
||||||
Requires Node v16.5+.
|
Requires Node v16.5+.
|
||||||
|
|
||||||
|
|||||||
@@ -83,5 +83,8 @@
|
|||||||
"throttle-debounce": "^3.0.1",
|
"throttle-debounce": "^3.0.1",
|
||||||
"undici": "^5.2.0",
|
"undici": "^5.2.0",
|
||||||
"unplugin-icons": "^0.13.3"
|
"unplugin-icons": "^0.13.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11683
pnpm-lock.yaml
generated
11683
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -23,3 +23,9 @@ export { default as Select } from './components/Select.svelte';
|
|||||||
export { default as Slider } from './components/Slider.svelte';
|
export { default as Slider } from './components/Slider.svelte';
|
||||||
|
|
||||||
export { default as TextInput } from './components/TextInput.svelte';
|
export { default as TextInput } from './components/TextInput.svelte';
|
||||||
|
|
||||||
|
/* UTILITIES */
|
||||||
|
export { ago } from './utils/ago';
|
||||||
|
export { Permissions } from './utils/permissions';
|
||||||
|
export { formatVersions } from './utils/versions';
|
||||||
|
export { markdown, markdownInline } from './utils/parse';
|
||||||
|
|||||||
10
src/package/plugins/generator/index.d.ts
vendored
Normal file
10
src/package/plugins/generator/index.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export default function Generator(options: PluginOptions): {
|
||||||
|
name: string;
|
||||||
|
buildStart(): Promise<void>;
|
||||||
|
};
|
||||||
|
export interface PluginOptions {
|
||||||
|
projectColors: boolean;
|
||||||
|
landingPage: boolean;
|
||||||
|
gameVersions: boolean;
|
||||||
|
tags: boolean;
|
||||||
|
}
|
||||||
@@ -12,18 +12,16 @@ process.env.VITE_API_URL || process.env?.NODE_ENV === 'development'
|
|||||||
// Time to live: 7 days
|
// Time to live: 7 days
|
||||||
const TTL = 7 * 24 * 60 * 60 * 1000;
|
const TTL = 7 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
export default function Generator(options: PluginOptions) {
|
export default function Generator(options) {
|
||||||
return {
|
return {
|
||||||
name: 'rollup-plugin-omorphia-generator',
|
name: 'rollup-plugin-omorphia-generator',
|
||||||
async buildStart() {
|
async buildStart() {
|
||||||
let state: State = {};
|
let state = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state = JSON.parse(await fs.readFile('./generated/state.json', 'utf8'));
|
state = JSON.parse(await fs.readFile('./generated/state.json', 'utf8'));
|
||||||
} catch {
|
} catch {
|
||||||
// File doesn't exist, create folder
|
// File doesn't exist, create folder
|
||||||
await fs.mkdir('./generated', { recursive: true });
|
await fs.mkdir('./generated', { recursive: true });
|
||||||
|
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
'./generated/state.json',
|
'./generated/state.json',
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
@@ -35,7 +33,6 @@ export default function Generator(options: PluginOptions) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't generate if the last generation was less than TTL and the options are the same
|
// Don't generate if the last generation was less than TTL and the options are the same
|
||||||
if (
|
if (
|
||||||
state?.lastGenerated &&
|
state?.lastGenerated &&
|
||||||
@@ -44,12 +41,10 @@ export default function Generator(options: PluginOptions) {
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.landingPage) await landingPage(API_URL);
|
if (options.landingPage) await landingPage(API_URL);
|
||||||
if (options.projectColors) await projectColors(API_URL);
|
if (options.projectColors) await projectColors(API_URL);
|
||||||
if (options.gameVersions) await gameVersions(API_URL);
|
if (options.gameVersions) await gameVersions(API_URL);
|
||||||
if (options.tags) await tags(API_URL);
|
if (options.tags) await tags(API_URL);
|
||||||
|
|
||||||
// Write new state
|
// Write new state
|
||||||
state.lastGenerated = new Date().toISOString();
|
state.lastGenerated = new Date().toISOString();
|
||||||
state.options = options;
|
state.options = options;
|
||||||
@@ -57,15 +52,3 @@ export default function Generator(options: PluginOptions) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginOptions {
|
|
||||||
projectColors: boolean;
|
|
||||||
landingPage: boolean;
|
|
||||||
gameVersions: boolean;
|
|
||||||
tags: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
lastGenerated?: string;
|
|
||||||
options?: PluginOptions;
|
|
||||||
}
|
|
||||||
1
src/package/plugins/generator/outputs/gameVersions.d.ts
vendored
Normal file
1
src/package/plugins/generator/outputs/gameVersions.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export declare function gameVersions(API_URL: string): Promise<void>;
|
||||||
@@ -2,18 +2,16 @@ import { fetch } from 'undici';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import cliProgress from 'cli-progress';
|
import cliProgress from 'cli-progress';
|
||||||
|
|
||||||
export async function gameVersions(API_URL: string) {
|
export async function gameVersions(API_URL) {
|
||||||
const progressBar = new cliProgress.SingleBar({
|
const progressBar = new cliProgress.SingleBar({
|
||||||
format: 'Generating game versions | {bar} | {percentage}%',
|
format: 'Generating game versions | {bar} | {percentage}%',
|
||||||
barCompleteChar: '\u2588',
|
barCompleteChar: '\u2588',
|
||||||
barIncompleteChar: '\u2591',
|
barIncompleteChar: '\u2591',
|
||||||
hideCursor: true,
|
hideCursor: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
progressBar.start(2, 0);
|
progressBar.start(2, 0);
|
||||||
|
|
||||||
const gameVersions = await (await fetch(API_URL + 'tag/game_version')).json();
|
const gameVersions = await (await fetch(API_URL + 'tag/game_version')).json();
|
||||||
|
|
||||||
progressBar.increment();
|
progressBar.increment();
|
||||||
|
|
||||||
// Write JSON file
|
// Write JSON file
|
||||||
1
src/package/plugins/generator/outputs/landingPage.d.ts
vendored
Normal file
1
src/package/plugins/generator/outputs/landingPage.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export declare function landingPage(API_URL: string): Promise<void>;
|
||||||
@@ -2,20 +2,19 @@ import { fetch } from 'undici';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import cliProgress from 'cli-progress';
|
import cliProgress from 'cli-progress';
|
||||||
|
|
||||||
export async function landingPage(API_URL: string) {
|
export async function landingPage(API_URL) {
|
||||||
const progressBar = new cliProgress.SingleBar({
|
const progressBar = new cliProgress.SingleBar({
|
||||||
format: 'Generating landing page | {bar} | {percentage}% || {value}/{total} mods',
|
format: 'Generating landing page | {bar} | {percentage}% || {value}/{total} mods',
|
||||||
barCompleteChar: '\u2588',
|
barCompleteChar: '\u2588',
|
||||||
barIncompleteChar: '\u2591',
|
barIncompleteChar: '\u2591',
|
||||||
hideCursor: true,
|
hideCursor: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
progressBar.start(100, 0);
|
progressBar.start(100, 0);
|
||||||
|
|
||||||
// Fetch top 100 mods
|
// Fetch top 100 mods
|
||||||
const response = (await (
|
const response = await (
|
||||||
await fetch(API_URL + 'search?limit=100&facets=[["project_type:mod"]]')
|
await fetch(API_URL + 'search?limit=100&facets=[["project_type:mod"]]')
|
||||||
).json()) as Record<string, any>;
|
).json();
|
||||||
|
|
||||||
// Simplified array with the format: ['id', 'slug', 'icon_extension']
|
// Simplified array with the format: ['id', 'slug', 'icon_extension']
|
||||||
const compressed = response.hits
|
const compressed = response.hits
|
||||||
@@ -37,6 +36,5 @@ export async function landingPage(API_URL: string) {
|
|||||||
random: Math.random(),
|
random: Math.random(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
progressBar.stop();
|
progressBar.stop();
|
||||||
}
|
}
|
||||||
1
src/package/plugins/generator/outputs/projectColors.d.ts
vendored
Normal file
1
src/package/plugins/generator/outputs/projectColors.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export declare function projectColors(API_URL: string): Promise<void>;
|
||||||
@@ -5,30 +5,21 @@ import Jimp from 'jimp';
|
|||||||
import { getAverageColor } from 'fast-average-color-node';
|
import { getAverageColor } from 'fast-average-color-node';
|
||||||
|
|
||||||
// Note: This function has issues and will occasionally fail with some project icons. It averages at a 99.4% success rate. Most issues are from ECONNRESET errors & Jimp not being able to handle webp & svg images.
|
// Note: This function has issues and will occasionally fail with some project icons. It averages at a 99.4% success rate. Most issues are from ECONNRESET errors & Jimp not being able to handle webp & svg images.
|
||||||
export async function projectColors(API_URL: string) {
|
export async function projectColors(API_URL) {
|
||||||
const progressBar = new cliProgress.SingleBar({
|
const progressBar = new cliProgress.SingleBar({
|
||||||
format: 'Generating project colors | {bar} | {percentage}% || {value}/{total} projects',
|
format: 'Generating project colors | {bar} | {percentage}% || {value}/{total} projects',
|
||||||
barCompleteChar: '\u2588',
|
barCompleteChar: '\u2588',
|
||||||
barIncompleteChar: '\u2591',
|
barIncompleteChar: '\u2591',
|
||||||
hideCursor: true,
|
hideCursor: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get total number of projects
|
// Get total number of projects
|
||||||
const projectCount = (
|
const projectCount = (await (await fetch(API_URL + 'search?limit=0')).json()).total_hits;
|
||||||
(await (await fetch(API_URL + 'search?limit=0')).json()) as Record<string, any>
|
|
||||||
).total_hits;
|
|
||||||
|
|
||||||
progressBar.start(projectCount, 0);
|
progressBar.start(projectCount, 0);
|
||||||
|
|
||||||
const writeStream = createWriteStream('./generated/projects.json');
|
const writeStream = createWriteStream('./generated/projects.json');
|
||||||
|
|
||||||
writeStream.write('{');
|
writeStream.write('{');
|
||||||
|
|
||||||
// Used to form the JSON string (so that the first doesn't have a comma prefix)
|
// Used to form the JSON string (so that the first doesn't have a comma prefix)
|
||||||
let first = true;
|
let first = true;
|
||||||
|
|
||||||
let completed = 0;
|
let completed = 0;
|
||||||
|
|
||||||
// Number of pages through search to fetch
|
// Number of pages through search to fetch
|
||||||
const requestCount = Math.ceil(projectCount / 100);
|
const requestCount = Math.ceil(projectCount / 100);
|
||||||
await Promise.allSettled(
|
await Promise.allSettled(
|
||||||
@@ -37,15 +28,13 @@ export async function projectColors(API_URL: string) {
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to fetch projects: ${response.statusText}`);
|
throw new Error(`Failed to fetch projects: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get project hits & use map to get rid of extra data
|
// Get project hits & use map to get rid of extra data
|
||||||
const hits = ((await response.json()) as Record<string, any>).hits.map((project) => ({
|
const hits = (await response.json()).hits.map((project) => ({
|
||||||
project_id: project.project_id,
|
project_id: project.project_id,
|
||||||
slug: project.slug,
|
slug: project.slug,
|
||||||
title: project.title,
|
title: project.title,
|
||||||
icon_url: project.icon_url,
|
icon_url: project.icon_url,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Try parsing the icon of each project
|
// Try parsing the icon of each project
|
||||||
await Promise.allSettled(
|
await Promise.allSettled(
|
||||||
hits.map(async (project) => {
|
hits.map(async (project) => {
|
||||||
@@ -59,32 +48,25 @@ export async function projectColors(API_URL: string) {
|
|||||||
const image = await Jimp.read(
|
const image = await Jimp.read(
|
||||||
project.icon_url.replace('cdn', 'cdn-raw') // Skip redirect to raw CDN
|
project.icon_url.replace('cdn', 'cdn-raw') // Skip redirect to raw CDN
|
||||||
);
|
);
|
||||||
|
|
||||||
// Resize image before getting average color (faster)
|
// Resize image before getting average color (faster)
|
||||||
image.resize(256, 256);
|
image.resize(256, 256);
|
||||||
|
|
||||||
// Get bottom edge of image
|
// Get bottom edge of image
|
||||||
const edge = image.clone().crop(0, 255, 256, 1);
|
const edge = image.clone().crop(0, 255, 256, 1);
|
||||||
|
|
||||||
const buffer = await edge.getBufferAsync(Jimp.AUTO);
|
const buffer = await edge.getBufferAsync(Jimp.AUTO);
|
||||||
let color = (await getAverageColor(buffer)).hexa;
|
let color = (await getAverageColor(buffer)).hexa;
|
||||||
|
|
||||||
// If the edge is transparent, use the average color of the entire image
|
// If the edge is transparent, use the average color of the entire image
|
||||||
if (color === '#00000000') {
|
if (color === '#00000000') {
|
||||||
const buffer = await image.getBufferAsync(Jimp.AUTO);
|
const buffer = await image.getBufferAsync(Jimp.AUTO);
|
||||||
color = (await getAverageColor(buffer)).hexa;
|
color = (await getAverageColor(buffer)).hexa;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove color transparency
|
// Remove color transparency
|
||||||
color = color.replace(/.{2}$/, '');
|
color = color.replace(/.{2}$/, '');
|
||||||
|
|
||||||
// Only use comma prefix if not first
|
// Only use comma prefix if not first
|
||||||
let prefix = ',';
|
let prefix = ',';
|
||||||
if (first) {
|
if (first) {
|
||||||
prefix = '';
|
prefix = '';
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeStream.write(`${prefix}"${project.project_id}":"${color}"`);
|
writeStream.write(`${prefix}"${project.project_id}":"${color}"`);
|
||||||
completed++;
|
completed++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -97,10 +79,8 @@ export async function projectColors(API_URL: string) {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
writeStream.write('}');
|
writeStream.write('}');
|
||||||
writeStream.end();
|
writeStream.end();
|
||||||
|
|
||||||
progressBar.stop();
|
progressBar.stop();
|
||||||
console.log(`Failed to parse ${projectCount - completed} project icons.`);
|
console.log(`Failed to parse ${projectCount - completed} project icons.`);
|
||||||
}
|
}
|
||||||
1
src/package/plugins/generator/outputs/tags.d.ts
vendored
Normal file
1
src/package/plugins/generator/outputs/tags.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export declare function tags(API_URL: string): Promise<void>;
|
||||||
@@ -1,31 +1,27 @@
|
|||||||
import { fetch } from 'undici';
|
import { fetch } from 'undici';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import cliProgress from 'cli-progress';
|
import cliProgress from 'cli-progress';
|
||||||
|
export async function tags(API_URL) {
|
||||||
export async function tags(API_URL: string) {
|
|
||||||
const progressBar = new cliProgress.SingleBar({
|
const progressBar = new cliProgress.SingleBar({
|
||||||
format: 'Generating tags | {bar} | {percentage}%',
|
format: 'Generating tags | {bar} | {percentage}%',
|
||||||
barCompleteChar: '\u2588',
|
barCompleteChar: '\u2588',
|
||||||
barIncompleteChar: '\u2591',
|
barIncompleteChar: '\u2591',
|
||||||
hideCursor: true,
|
hideCursor: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
progressBar.start(7, 0);
|
progressBar.start(7, 0);
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
let [categories, loaders, licenses, donationPlatforms]: any = await Promise.all([
|
let [categories, loaders, licenses, donationPlatforms] = await Promise.all([
|
||||||
await (await fetch(API_URL + 'tag/category')).json(),
|
await (await fetch(API_URL + 'tag/category')).json(),
|
||||||
await (await fetch(API_URL + 'tag/loader')).json(),
|
await (await fetch(API_URL + 'tag/loader')).json(),
|
||||||
await (await fetch(API_URL + 'tag/license')).json(),
|
await (await fetch(API_URL + 'tag/license')).json(),
|
||||||
await (await fetch(API_URL + 'tag/donation_platform')).json(),
|
await (await fetch(API_URL + 'tag/donation_platform')).json(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
progressBar.update(4);
|
progressBar.update(4);
|
||||||
|
|
||||||
// Delete icons from original arrays
|
// Delete icons from original arrays
|
||||||
categories = categories.map(({ icon, ...rest }) => rest);
|
categories = categories.map(({ icon, ...rest }) => rest);
|
||||||
loaders = loaders.map(({ icon, ...rest }) => rest);
|
loaders = loaders.map(({ icon, ...rest }) => rest);
|
||||||
|
|
||||||
progressBar.increment();
|
progressBar.increment();
|
||||||
|
|
||||||
// Create single object with icons
|
// Create single object with icons
|
||||||
@@ -33,7 +29,6 @@ export async function tags(API_URL: string) {
|
|||||||
...categories.reduce((a, v) => ({ ...a, [v.name]: v.icon }), {}),
|
...categories.reduce((a, v) => ({ ...a, [v.name]: v.icon }), {}),
|
||||||
...loaders.reduce((a, v) => ({ ...a, [v.name]: v.icon }), {}),
|
...loaders.reduce((a, v) => ({ ...a, [v.name]: v.icon }), {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
progressBar.increment();
|
progressBar.increment();
|
||||||
|
|
||||||
// Set project types
|
// Set project types
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
--padding: 1rem 1.3rem;
|
--padding: 1rem 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.text {
|
&.markdown {
|
||||||
--padding: 1.5rem;
|
--padding: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export { ago } from './ago';
|
|
||||||
export { simplify } from './number';
|
|
||||||
export { Permissions } from './permissions';
|
|
||||||
export { formatVersions } from './versions';
|
|
||||||
@@ -40,7 +40,7 @@ marked.setOptions({
|
|||||||
function sanitize(html: string): string {
|
function sanitize(html: string): string {
|
||||||
return insane(html, {
|
return insane(html, {
|
||||||
allowedAttributes: {
|
allowedAttributes: {
|
||||||
a: ['href', 'name', 'target', 'title', 'rel'],
|
a: ['href', 'target', 'title', 'rel'],
|
||||||
iframe: ['allowfullscreen', 'src', 'width', 'height'],
|
iframe: ['allowfullscreen', 'src', 'width', 'height'],
|
||||||
img: ['src', 'width', 'height', 'alt'],
|
img: ['src', 'width', 'height', 'alt'],
|
||||||
h1: ['id'],
|
h1: ['id'],
|
||||||
@@ -121,14 +121,22 @@ function sanitize(html: string): string {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markdown(markdown: string): string {
|
|
||||||
return marked.parse(markdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function markdownInline(markdown: string): string {
|
export function markdownInline(markdown: string): string {
|
||||||
return marked.parseInline(markdown);
|
return insane(
|
||||||
|
marked.parseInline(markdown),
|
||||||
|
{
|
||||||
|
allowedAttributes: {
|
||||||
|
a: ['href', 'target', 'title', 'rel'],
|
||||||
|
},
|
||||||
|
allowedClasses: {},
|
||||||
|
allowedSchemes: ['http', 'https', 'mailto'],
|
||||||
|
allowedTags: ['a', 'b', 'br', 'code', 'em', 'i', 'strike', 'strong', 'sub', 'sup', 'u'],
|
||||||
|
transformText: null,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markdownXSS(markdown: string): string {
|
export function markdown(markdown: string): string {
|
||||||
return sanitize(marked.parse(markdown));
|
return sanitize(marked.parse(markdown));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,8 +79,7 @@ export function formatVersions(versionArray: string[]): string {
|
|||||||
return output.join(', ');
|
return output.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPrimary = (files: Version['files']) =>
|
export const getPrimary = (files) => files.find((file) => file.primary) || files[0];
|
||||||
files.find((file) => file.primary) || files[0];
|
|
||||||
|
|
||||||
export function downloadUrl(file): string {
|
export function downloadUrl(file): string {
|
||||||
return import.meta.env.VITE_API_URL + `version_file/${file?.hashes.sha1}/download`;
|
return import.meta.env.VITE_API_URL + `version_file/${file?.hashes.sha1}/download`;
|
||||||
|
|||||||
@@ -1,35 +1,38 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from 'omorphia'
|
import { Button } from 'omorphia';
|
||||||
import IconMoon from 'virtual:icons/heroicons-outline/moon'
|
import IconMoon from 'virtual:icons/heroicons-outline/moon';
|
||||||
import IconSun from 'virtual:icons/heroicons-outline/sun'
|
import IconSun from 'virtual:icons/heroicons-outline/sun';
|
||||||
|
|
||||||
export let meta: { raised: boolean };
|
export let meta: { raised: boolean };
|
||||||
|
|
||||||
let theme = 'light'
|
let theme = 'light';
|
||||||
let background = meta.raised ? 'var(--color-raised-bg)' : 'var(--color-bg)'
|
let background = meta.raised ? 'var(--color-raised-bg)' : 'var(--color-bg)';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="example">
|
<div class="example">
|
||||||
<div class="example__preview theme-{theme} base" style:background={background}>
|
<div class="example__preview theme-{theme} base" style:background>
|
||||||
<slot name="example"/>
|
<slot name="example" />
|
||||||
</div>
|
</div>
|
||||||
<div class="example__source">
|
<div class="example__source">
|
||||||
<div class="example__source__options">
|
<div class="example__source__options">
|
||||||
<Button color="primary-light" on:click={() => theme === 'light' ? theme = 'dark' : theme = 'light'}>
|
<Button
|
||||||
|
color="primary-light"
|
||||||
|
on:click={() => (theme === 'light' ? (theme = 'dark') : (theme = 'light'))}
|
||||||
|
>
|
||||||
{#if theme === 'light'}
|
{#if theme === 'light'}
|
||||||
<IconMoon/>
|
<IconMoon />
|
||||||
{:else}
|
{:else}
|
||||||
<IconSun/>
|
<IconSun />
|
||||||
{/if}
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<pre class="example__source__code language-svelte"><slot name="code"/></pre>
|
<pre class="example__source__code language-svelte"><slot name="code" /></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
.example {
|
.example {
|
||||||
margin-bottom: 32px;
|
margin: 15px 0 32px;
|
||||||
|
|
||||||
&__preview {
|
&__preview {
|
||||||
border-radius: var(--rounded-sm-top);
|
border-radius: var(--rounded-sm-top);
|
||||||
|
|||||||
@@ -1,157 +1,162 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
import IconMenu from 'virtual:icons/lucide/menu';
|
import IconMenu from 'virtual:icons/lucide/menu';
|
||||||
|
|
||||||
const components = Object.keys(import.meta.glob('../../components/**'))
|
const components = Object.keys(import.meta.glob('../../components/**'))
|
||||||
.map((it) => it.replace('../../components/', '').replace('.md', ''))
|
.map((it) => it.replace('../../components/', '').replace('.md', ''))
|
||||||
.sort();
|
.sort();
|
||||||
|
|
||||||
const classes = Object.keys(import.meta.glob('../../classes/**'))
|
const classes = Object.keys(import.meta.glob('../../classes/**'))
|
||||||
.map((it) => it.replace('../../classes/', '').replace('.md', ''))
|
.map((it) => it.replace('../../classes/', '').replace('.md', ''))
|
||||||
.sort();
|
.sort();
|
||||||
|
|
||||||
let slideIn = false;
|
let slideIn = false;
|
||||||
|
|
||||||
$: if ($page.url.pathname) {
|
$: if ($page.url.pathname) {
|
||||||
slideIn = false;
|
slideIn = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="sidebar" class:slideIn>
|
<nav class="sidebar" class:slideIn>
|
||||||
<div class="sidebar__content">
|
<div class="sidebar__content">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<span class="section__title">Getting started</span>
|
<span class="section__title">Getting started</span>
|
||||||
<a href="/" class="section__link">Introduction</a>
|
<a href="/" class="section__link">Introduction</a>
|
||||||
<a href="/getting-started/configure" class="section__link">Configure</a>
|
<a href="/getting-started/configure" class="section__link">Configure</a>
|
||||||
<a href="/getting-started/icons" class="section__link">Using Icons</a>
|
<a href="/getting-started/icons" class="section__link">Using Icons</a>
|
||||||
<a href="/getting-started/css" class="section__link">Writing CSS</a>
|
<!-- <a href="/getting-started/css" class="section__link">Writing CSS</a> -->
|
||||||
<a href="/getting-started/illustrations" class="section__link">Illustrations</a>
|
<a href="/getting-started/illustrations" class="section__link">Illustrations</a>
|
||||||
<a href="/getting-started/utils" class="section__link">Built-in utilities</a>
|
<a href="/getting-started/utils" class="section__link">Built-in utilities</a>
|
||||||
<a href="/getting-started/generator" class="section__link">Generator plugin</a>
|
<a href="/getting-started/generator" class="section__link">Generator plugin</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<span class="section__title">Components</span>
|
||||||
|
{#each components as component}
|
||||||
|
<a href="/components/{component}" class="section__link">{component}</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<span class="section__title">Classes</span>
|
||||||
|
{#each classes as page}
|
||||||
|
<a href="/classes/{page}" class="section__link">{page}</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<button class="sidebar__toggle" on:click={() => (slideIn = !slideIn)}>
|
||||||
<span class="section__title">Components</span>
|
<IconMenu />
|
||||||
{#each components as component}
|
</button>
|
||||||
<a href="/components/{component}" class="section__link">{component}</a>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<span class="section__title">Classes</span>
|
|
||||||
{#each classes as page}
|
|
||||||
<a href="/classes/{page}" class="section__link">{page}</a>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="sidebar__toggle" on:click={() => (slideIn = !slideIn)}>
|
|
||||||
<IconMenu />
|
|
||||||
</button>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
:root {
|
:root {
|
||||||
--sidebar-color: hsl(220, 15%, 40%);
|
--sidebar-color: hsl(220, 15%, 40%);
|
||||||
--title-color: hsl(216, 10%, 80%);
|
--title-color: hsl(216, 10%, 80%);
|
||||||
--link-color: hsl(216, 10%, 90%);
|
--link-color: hsl(216, 10%, 90%);
|
||||||
--scrollbar-thumb-color: hsl(216, 10%, 70%);
|
--scrollbar-thumb-color: hsl(216, 10%, 70%);
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
background-color: var(--sidebar-color);
|
|
||||||
color: var(--title-color);
|
|
||||||
width: var(--sidebar-width);
|
|
||||||
max-width: 70vw;
|
|
||||||
position: fixed;
|
|
||||||
left: -100%;
|
|
||||||
top: 0;
|
|
||||||
z-index: 5;
|
|
||||||
transition: left 0.2s ease-in-out;
|
|
||||||
box-shadow: 2px 0px 4px hsla(221, 39%, 11%, 0.2);
|
|
||||||
|
|
||||||
@media (--md) {
|
|
||||||
left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
.sidebar {
|
||||||
mask-image: linear-gradient(to bottom, transparent, hsla(0, 0%, 0%, 1) 5% 95%, transparent);
|
background-color: var(--sidebar-color);
|
||||||
padding: 88px 32px 32px;
|
color: var(--title-color);
|
||||||
height: 100vh;
|
width: var(--sidebar-width);
|
||||||
max-height: 100vh;
|
max-width: 70vw;
|
||||||
overflow-y: auto;
|
position: fixed;
|
||||||
grid-gap: 40px;
|
left: -100%;
|
||||||
display: flex;
|
top: 0;
|
||||||
flex-direction: column;
|
z-index: 5;
|
||||||
|
transition: left 0.2s ease-in-out;
|
||||||
|
box-shadow: 2px 0px 4px hsla(221, 39%, 11%, 0.2);
|
||||||
|
|
||||||
.section {
|
@media (--md) {
|
||||||
display: flex;
|
left: 0;
|
||||||
flex-direction: column;
|
|
||||||
grid-gap: 0.5rem;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__link {
|
&__content {
|
||||||
color: var(--link-color);
|
mask-image: linear-gradient(
|
||||||
text-decoration: none;
|
to bottom,
|
||||||
|
transparent,
|
||||||
|
hsla(0, 0%, 0%, 1) 5% 95%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
padding: 88px 32px 32px;
|
||||||
|
height: 100vh;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
grid-gap: 40px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
&:hover {
|
.section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-gap: 0.5rem;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link {
|
||||||
|
color: var(--link-color);
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: white;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__toggle {
|
||||||
|
position: fixed;
|
||||||
|
left: 16px;
|
||||||
|
bottom: 16px;
|
||||||
|
padding: 8px;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
z-index: 20;
|
||||||
|
border-radius: var(--rounded);
|
||||||
color: white;
|
color: white;
|
||||||
text-decoration: underline;
|
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
|
||||||
}
|
transition: left 0.2s cubic-bezier(0.38, 0.52, 0.37, 1.27);
|
||||||
|
|
||||||
|
:global(.icon) {
|
||||||
|
width: 32px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (--md) {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.slideIn {
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
.sidebar__toggle {
|
||||||
|
left: calc(32px + min(70vw, var(--sidebar-width)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-color: var(--scrollbar-thumb-color) var(--sidebar-color);
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background-color: var(--sidebar-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--scrollbar-thumb-color);
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 3px solid var(--sidebar-color);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__toggle {
|
|
||||||
position: fixed;
|
|
||||||
left: 16px;
|
|
||||||
bottom: 16px;
|
|
||||||
padding: 8px;
|
|
||||||
aspect-ratio: 1 / 1;
|
|
||||||
background-color: var(--accent-color);
|
|
||||||
z-index: 20;
|
|
||||||
border-radius: var(--rounded);
|
|
||||||
color: white;
|
|
||||||
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
|
|
||||||
transition: left 0.2s cubic-bezier(0.38, 0.52, 0.37, 1.27);
|
|
||||||
|
|
||||||
:global(.icon) {
|
|
||||||
width: 32px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (--md) {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.slideIn {
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
.sidebar__toggle {
|
|
||||||
left: calc(32px + min(70vw, var(--sidebar-width)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollbar-color: var(--scrollbar-thumb-color) var(--sidebar-color);
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background-color: var(--sidebar-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background-color: var(--scrollbar-thumb-color);
|
|
||||||
border-radius: 999px;
|
|
||||||
border: 3px solid var(--sidebar-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:where(h1, h2, h3, h4, h5, h6) {
|
&:where(h1, h2, h3, h4, h5, h6) {
|
||||||
margin: 20px 0 10px;
|
margin: 30px 0 10px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
@@ -76,6 +76,8 @@
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
/*border-bottom: 1px solid #cccccc;*/
|
/*border-bottom: 1px solid #cccccc;*/
|
||||||
color: black;
|
color: black;
|
||||||
|
|
||||||
|
margin-top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:where(h3) {
|
&:where(h3) {
|
||||||
|
|||||||
@@ -2,25 +2,82 @@
|
|||||||
title: Built-in utilities
|
title: Built-in utilities
|
||||||
---
|
---
|
||||||
|
|
||||||
TODO
|
## Markdown
|
||||||
|
|
||||||
## ... ago
|
Use the markdown utilities to parse markdown text into HTML. Both markdown parsers have HTML sanitization built-in.
|
||||||
|
|
||||||
## Simplify number
|
### Body parser
|
||||||
|
|
||||||
## Markdown & XSS
|
The `markdown` parser is designed for bodies of markdown text and supports images, tables, lists, and youtube `<iframe>`s.
|
||||||
|
|
||||||
### Parsers
|
> Use the `.markdown` class on the element containing your parsed markdown.
|
||||||
|
|
||||||
- sanitize
|
```svelte example
|
||||||
- markdown
|
<script lang="ts">
|
||||||
- markdownInline
|
import { markdown } from "omorphia"
|
||||||
- markdownXSS
|
|
||||||
|
|
||||||
### Markdown
|
const source = '## Example markdown \n\
|
||||||
|
This is **some** *text*! \n\
|
||||||
|
#### An image \n\
|
||||||
|
<img src="https://cdn.modrinth.com/data/YL57xq9U/images/d382106b9a2b943d06107c31c139c77849f1a0e8.png" />'
|
||||||
|
</script>
|
||||||
|
|
||||||
Put parsed HTML into a `<div>` with `class="markdown"`.
|
<div class="card markdown">
|
||||||
|
{@html markdown(source)}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inline parser
|
||||||
|
|
||||||
|
The `markdownInline` parser is perfect for translations and short bios. It doesn't allow complex elements such as images or tables.
|
||||||
|
|
||||||
|
```svelte example raised
|
||||||
|
<script lang="ts">
|
||||||
|
import { markdownInline } from "omorphia"
|
||||||
|
|
||||||
|
const source = "This is some **bolded** and *italicized* text."
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>{@html markdownInline(source)}</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human readable "ago" times
|
||||||
|
|
||||||
|
```svelte example raised
|
||||||
|
<script lang="ts">
|
||||||
|
import { ago } from 'omorphia';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>Something happened {ago(Date.now())}.</p>
|
||||||
|
<p>Something happened {ago(new Date(Date.now() - 1000 * 60 * 60 * 2))}.</p>
|
||||||
|
<p>Something happened {ago(new Date(Date.now() - 1000 * 60 * 60 * 24 * 7))}.</p>
|
||||||
|
<p>Something happened {ago(new Date(Date.now() - 1000 * 60 * 60 * 24 * 7 * 5))}.</p>
|
||||||
|
```
|
||||||
|
|
||||||
## Permissions
|
## Permissions
|
||||||
|
|
||||||
|
The `Permissions` class provides an easy way to manage user permissions.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Permissions } from 'omorphia';
|
||||||
|
|
||||||
|
const adminLevel = new Permissions('ALL');
|
||||||
|
const memberLevel = new Permissions(member.permissions); /* `member` from API */
|
||||||
|
const userLevel = new Permissions(0);
|
||||||
|
|
||||||
|
if (memberLevel.data.uploadVersions) {
|
||||||
|
console.log('Can upload versions!');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
|
The `formatVersions` function provides an easy way to parse a project's versions into a readable string.
|
||||||
|
|
||||||
|
```svelte example raised
|
||||||
|
<script lang="ts">
|
||||||
|
import { formatVersions } from 'omorphia';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>{formatVersions(["1.18", "1.18.1", "1.18.2", "1.17.1"])}</p>
|
||||||
|
```
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import examples from 'mdsvexamples/vite';
|
|||||||
import sveld from './plugins/sveld.js';
|
import sveld from './plugins/sveld.js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { preprocess, plugins } from './src/package/config/svelte.config.js';
|
import { preprocess, plugins } from './src/package/config/svelte.config.js';
|
||||||
|
import Generator from './src/package/plugins/generator/index.js';
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
@@ -20,6 +21,9 @@ const config = {
|
|||||||
},
|
},
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [
|
plugins: [
|
||||||
|
Generator({
|
||||||
|
gameVersions: true,
|
||||||
|
}),
|
||||||
...plugins,
|
...plugins,
|
||||||
examples,
|
examples,
|
||||||
sveld(),
|
sveld(),
|
||||||
@@ -29,6 +33,7 @@ const config = {
|
|||||||
alias: {
|
alias: {
|
||||||
$package: path.resolve('./src/package'),
|
$package: path.resolve('./src/package'),
|
||||||
$routes: path.resolve('./src/routes'),
|
$routes: path.resolve('./src/routes'),
|
||||||
|
$generated: path.resolve('./generated'),
|
||||||
omorphia: path.resolve('./src/package'),
|
omorphia: path.resolve('./src/package'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -38,6 +43,12 @@ const config = {
|
|||||||
external: '/_app/COMPONENT_API.json',
|
external: '/_app/COMPONENT_API.json',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
server: {
|
||||||
|
fs: {
|
||||||
|
allow: ['generated'],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
files: {
|
files: {
|
||||||
lib: 'src/package',
|
lib: 'src/package',
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"omorphia": ["src/package"],
|
"omorphia": ["src/package"],
|
||||||
"$package/*": ["src/package/*"],
|
"$package/*": ["src/package/*"],
|
||||||
"$routes/*": ["src/routes/*"],
|
"$routes/*": ["src/routes/*"],
|
||||||
|
"$generated/*": ["generated/*"],
|
||||||
"$lib": ["src/package"],
|
"$lib": ["src/package"],
|
||||||
"$lib/*": ["src/package/*"]
|
"$lib/*": ["src/package/*"]
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user