mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 21:26:40 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e71b67e9fc |
@@ -31,6 +31,7 @@
|
||||
"@tauri-apps/plugin-window-state": "^2.2.2",
|
||||
"@types/three": "^0.172.0",
|
||||
"@vueuse/core": "^11.1.0",
|
||||
"comark": "^0.3.2",
|
||||
"dayjs": "^1.11.10",
|
||||
"floating-vue": "^5.2.2",
|
||||
"fuse.js": "^6.6.2",
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
defineMessages,
|
||||
I18nDebugPanel,
|
||||
LoadingBar,
|
||||
MarkdownBody,
|
||||
NewsArticleCard,
|
||||
NotificationPanel,
|
||||
OverflowMenu,
|
||||
@@ -55,7 +56,6 @@ import {
|
||||
useFormatBytes,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { renderString } from '@modrinth/utils'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
@@ -1477,10 +1477,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
:header="criticalErrorMessage.header"
|
||||
class="m-6 mb-0"
|
||||
>
|
||||
<div
|
||||
class="markdown-body text-primary"
|
||||
v-html="renderString(criticalErrorMessage.body ?? '')"
|
||||
></div>
|
||||
<MarkdownBody body-class="text-primary" :markdown="criticalErrorMessage.body ?? ''" />
|
||||
</Admonition>
|
||||
<Admonition
|
||||
v-if="authUnreachable"
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<div class="description-cards">
|
||||
<Card>
|
||||
<h3 class="card-title">Changelog</h3>
|
||||
<div class="markdown-body" v-html="renderString(version.changelog ?? '')" />
|
||||
<MarkdownBody :markdown="version.changelog ?? ''" />
|
||||
</Card>
|
||||
<Card>
|
||||
<h3 class="card-title">Files</h3>
|
||||
@@ -176,10 +176,10 @@ import {
|
||||
ButtonStyled,
|
||||
Card,
|
||||
CopyCode,
|
||||
MarkdownBody,
|
||||
useFormatBytes,
|
||||
useFormatDateTime,
|
||||
} from '@modrinth/ui'
|
||||
import { renderString } from '@modrinth/utils'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
|
||||
@@ -59,14 +59,12 @@
|
||||
"dompurify": "^3.1.7",
|
||||
"floating-vue": "^5.2.2",
|
||||
"fuse.js": "^6.6.2",
|
||||
"highlight.js": "^11.7.0",
|
||||
"intl-messageformat": "^10.7.7",
|
||||
"iso-3166-2": "1.0.0",
|
||||
"jose": "^6.2.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jszip": "^3.10.1",
|
||||
"lru-cache": "^11.2.4",
|
||||
"markdown-it": "14.1.0",
|
||||
"pathe": "^1.1.2",
|
||||
"prettier": "^3.6.2",
|
||||
"qrcode.vue": "^3.4.0",
|
||||
@@ -76,8 +74,7 @@
|
||||
"vue-router": "*",
|
||||
"vue-typed-virtual-list": "^1.0.10",
|
||||
"vue3-ace-editor": "^2.2.4",
|
||||
"vue3-apexcharts": "^1.5.2",
|
||||
"xss": "^1.0.14"
|
||||
"vue3-apexcharts": "^1.5.2"
|
||||
},
|
||||
"web-types": "../../web-types.json",
|
||||
"prettier": "@modrinth/tooling-config/frontend.prettier.config.cjs"
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
.
|
||||
</template>
|
||||
<nuxt-link v-else :to="notification.link" class="title-link">
|
||||
<span v-html="renderString(notification.title)" />
|
||||
<MarkdownBody :markdown="notification.title" inline />
|
||||
</nuxt-link>
|
||||
<!-- <span v-else class="known-errors">Error reading notification.</span>-->
|
||||
</div>
|
||||
@@ -318,11 +318,12 @@ import {
|
||||
DoubleIcon,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
MarkdownBody,
|
||||
ProjectStatusBadge,
|
||||
useFormatDateTime,
|
||||
useRelativeTime,
|
||||
} from '@modrinth/ui'
|
||||
import { getUserLink, renderString } from '@modrinth/utils'
|
||||
import { getUserLink } from '@modrinth/utils'
|
||||
|
||||
import { markAsRead } from '~/helpers/platform-notifications'
|
||||
import { getProjectLink, getVersionLink } from '~/helpers/projects'
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
getProjectTypeIcon,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
highlightCodeLines,
|
||||
OverflowMenu,
|
||||
type OverflowMenuOption,
|
||||
useFormatBytes,
|
||||
@@ -35,7 +36,6 @@ import { NavTabs } from '@modrinth/ui'
|
||||
import {
|
||||
capitalizeString,
|
||||
formatProjectType,
|
||||
highlightCodeLines,
|
||||
type ThreadMessage,
|
||||
type User,
|
||||
} from '@modrinth/utils'
|
||||
@@ -565,6 +565,7 @@ const expandedClasses = reactive<Set<string>>(new Set())
|
||||
const autoExpandedFileIds = reactive<Set<string>>(new Set())
|
||||
const showCopyFeedback = reactive<Map<string, boolean>>(new Map())
|
||||
const highlightedSourceCache = reactive<Map<string, { source: string; lines: string[] }>>(new Map())
|
||||
const highlightedSourceLoading = reactive<Set<string>>(new Set())
|
||||
const LAZY_LOAD_CLASS_SOURCE_MINIMUM = 10
|
||||
|
||||
interface ClassGroup {
|
||||
@@ -723,9 +724,18 @@ function getHighlightedClassSource(classItem: ClassGroup): string[] {
|
||||
const cached = highlightedSourceCache.get(classItem.key)
|
||||
if (cached?.source === source) return cached.lines
|
||||
|
||||
const lines = highlightCodeLines(source, 'java')
|
||||
highlightedSourceCache.set(classItem.key, { source, lines })
|
||||
return lines
|
||||
if (!highlightedSourceLoading.has(classItem.key)) {
|
||||
highlightedSourceLoading.add(classItem.key)
|
||||
highlightCodeLines(source, 'java')
|
||||
.then((lines) => {
|
||||
highlightedSourceCache.set(classItem.key, { source, lines })
|
||||
})
|
||||
.finally(() => {
|
||||
highlightedSourceLoading.delete(classItem.key)
|
||||
})
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function isClassLoadingSource(classItem: ClassGroup): boolean {
|
||||
|
||||
@@ -186,7 +186,11 @@
|
||||
</h2>
|
||||
|
||||
<div v-if="currentStageObj.text" class="mb-4">
|
||||
<div v-if="stageTextExpanded" class="markdown-body" v-html="stageTextExpanded"></div>
|
||||
<MarkdownBody
|
||||
v-if="stageTextExpanded"
|
||||
:markdown="stageTextExpanded"
|
||||
highlight
|
||||
/>
|
||||
<div v-else class="markdown-body">Loading stage content...</div>
|
||||
</div>
|
||||
|
||||
@@ -493,18 +497,14 @@ import {
|
||||
DropdownSelect,
|
||||
injectNotificationManager,
|
||||
injectProjectPageContext,
|
||||
MarkdownBody,
|
||||
MarkdownEditor,
|
||||
OverflowMenu,
|
||||
type OverflowMenuOption,
|
||||
StyledInput,
|
||||
useDebugLogger,
|
||||
} from '@modrinth/ui'
|
||||
import {
|
||||
type ModerationJudgements,
|
||||
type ModerationModpackItem,
|
||||
type ProjectStatus,
|
||||
renderHighlightedString,
|
||||
} from '@modrinth/utils'
|
||||
import type { ModerationJudgements, ModerationModpackItem, ProjectStatus } from '@modrinth/utils'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
||||
import type { Component } from 'vue'
|
||||
@@ -1135,13 +1135,11 @@ const stageTextExpanded = computedAsync(async () => {
|
||||
const stageIndex = currentStage.value
|
||||
const stage = checklist[stageIndex]
|
||||
if (stage.text) {
|
||||
return renderHighlightedString(
|
||||
expandVariables(
|
||||
await stage.text(projectV2.value, projectV3.value),
|
||||
projectV2.value,
|
||||
projectV3.value,
|
||||
variables.value,
|
||||
),
|
||||
return expandVariables(
|
||||
await stage.text(projectV2.value, projectV3.value),
|
||||
projectV2.value,
|
||||
projectV3.value,
|
||||
variables.value,
|
||||
)
|
||||
}
|
||||
return null
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
<Badge v-if="report.closed" type="closed" />
|
||||
<Badge :type="`Reported for ${report.report_type}`" color="orange" />
|
||||
</div>
|
||||
<div v-if="showMessage" class="markdown-body" v-html="renderHighlightedString(report.body)" />
|
||||
<MarkdownBody v-if="showMessage" :markdown="report.body" highlight />
|
||||
<ThreadSummary
|
||||
v-if="thread"
|
||||
:thread="thread"
|
||||
@@ -107,8 +107,8 @@
|
||||
|
||||
<script setup>
|
||||
import { ReportIcon, UnknownIcon, VersionIcon } from '@modrinth/assets'
|
||||
import { Avatar, Badge, CopyCode, useFormatDateTime, useRelativeTime } from '@modrinth/ui'
|
||||
import { formatProjectType, renderHighlightedString } from '@modrinth/utils'
|
||||
import { Avatar, Badge, CopyCode, MarkdownBody, useFormatDateTime, useRelativeTime } from '@modrinth/ui'
|
||||
import { formatProjectType } from '@modrinth/utils'
|
||||
|
||||
import ThreadSummary from '~/components/ui/thread/ThreadSummary.vue'
|
||||
import { getProjectTypeForUrl } from '~/helpers/projects.js'
|
||||
|
||||
@@ -146,10 +146,14 @@ import {
|
||||
Badge,
|
||||
ButtonStyled,
|
||||
OverflowMenu,
|
||||
markdownHasImage,
|
||||
markdownToPlainText,
|
||||
renderMarkdown,
|
||||
useFormatDateTime,
|
||||
useRelativeTime,
|
||||
} from '@modrinth/ui'
|
||||
import { renderString } from '@modrinth/utils'
|
||||
import { computedAsync } from '@vueuse/core'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { isStaff } from '~/helpers/users.js'
|
||||
|
||||
@@ -187,21 +191,21 @@ const props = defineProps({
|
||||
const emit = defineEmits(['update-thread'])
|
||||
const flags = useFeatureFlags()
|
||||
|
||||
const formattedMessage = computed(() => {
|
||||
const body = renderString(props.message.body.body)
|
||||
const formattedMessage = computedAsync(async () => {
|
||||
const markdown = props.message.body.body
|
||||
const body = await renderMarkdown(markdown)
|
||||
if (props.forceCompact) {
|
||||
const hasImage = body.includes('<img')
|
||||
const noHtml = body.replace(/<\/?[^>]+(>|$)/g, '')
|
||||
if (noHtml.trim()) {
|
||||
return noHtml
|
||||
} else if (hasImage) {
|
||||
const plainText = markdownToPlainText(markdown)
|
||||
if (plainText) {
|
||||
return plainText
|
||||
} else if (markdownHasImage(markdown)) {
|
||||
return 'sent an image.'
|
||||
} else {
|
||||
return 'sent a message.'
|
||||
}
|
||||
}
|
||||
return body
|
||||
})
|
||||
}, '')
|
||||
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
|
||||
@@ -58,13 +58,9 @@
|
||||
{{ project.license.name ? project.license.name : formatMessage(messages.licenseTitle) }}
|
||||
</span>
|
||||
</template>
|
||||
<div
|
||||
class="markdown-body"
|
||||
v-html="
|
||||
renderString(licenseText).isEmpty
|
||||
? formatMessage(messages.loadingLicenseText)
|
||||
: renderString(licenseText)
|
||||
"
|
||||
<MarkdownBody
|
||||
:markdown="licenseText"
|
||||
:fallback="formatMessage(messages.loadingLicenseText)"
|
||||
/>
|
||||
</NewModal>
|
||||
<OpenInAppModal ref="openInAppModal" />
|
||||
@@ -1080,6 +1076,7 @@ import {
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
IntlFormatted,
|
||||
MarkdownBody,
|
||||
NavTabs,
|
||||
NewModal,
|
||||
OpenInAppModal,
|
||||
@@ -1107,7 +1104,7 @@ import {
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import VersionSummary from '@modrinth/ui/src/components/version/VersionSummary.vue'
|
||||
import { capitalizeString, formatProjectType, renderString } from '@modrinth/utils'
|
||||
import { capitalizeString, formatProjectType } from '@modrinth/utils'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
@@ -68,8 +68,9 @@
|
||||
<div
|
||||
v-if="version.changelog && !version.duplicate"
|
||||
class="markdown-body"
|
||||
v-html="renderHighlightedString(version.changelog)"
|
||||
/>
|
||||
>
|
||||
<MarkdownBody :markdown="version.changelog" highlight />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -93,11 +94,11 @@ import {
|
||||
ButtonStyled,
|
||||
injectModrinthClient,
|
||||
injectProjectPageContext,
|
||||
MarkdownBody,
|
||||
Pagination,
|
||||
useFormatDateTime,
|
||||
} from '@modrinth/ui'
|
||||
import VersionFilterControl from '@modrinth/ui/src/components/version/VersionFilterControl.vue'
|
||||
import { renderHighlightedString } from '@modrinth/utils'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { onMounted, watch } from 'vue'
|
||||
|
||||
|
||||
@@ -193,11 +193,10 @@
|
||||
</div>
|
||||
<div class="version-page__changelog universal-card">
|
||||
<h3>Changelog</h3>
|
||||
<div
|
||||
class="markdown-body"
|
||||
v-html="
|
||||
version.changelog ? renderHighlightedString(version.changelog) : 'No changelog specified.'
|
||||
"
|
||||
<MarkdownBody
|
||||
:markdown="version.changelog ?? ''"
|
||||
fallback="No changelog specified."
|
||||
highlight
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -441,13 +440,13 @@ import {
|
||||
ENVIRONMENTS_COPY,
|
||||
injectNotificationManager,
|
||||
injectProjectPageContext,
|
||||
MarkdownBody,
|
||||
MultiSelect,
|
||||
PROJECT_DEP_MARKER_QUERY,
|
||||
StyledInput,
|
||||
useFormatBytes,
|
||||
useFormatDateTime,
|
||||
} from '@modrinth/ui'
|
||||
import { renderHighlightedString } from '@modrinth/utils'
|
||||
|
||||
import Breadcrumbs from '~/components/ui/Breadcrumbs.vue'
|
||||
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
|
||||
// Apply default styles
|
||||
@import 'styles/defaults';
|
||||
// Apply code block highlighting styles
|
||||
@import 'styles/highlightjs';
|
||||
|
||||
// Finally, apply accessibility-related global styling
|
||||
@import 'styles/accessibility';
|
||||
|
||||
|
||||
@@ -781,6 +781,29 @@ a:not(.no-click-animation),
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
li > p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li > p + p {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
li > ul,
|
||||
li > ol {
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: block;
|
||||
}
|
||||
@@ -858,9 +881,15 @@ a:not(.no-click-animation),
|
||||
overflow-x: auto;
|
||||
|
||||
code {
|
||||
display: block;
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
font-size: 80%;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
overflow-wrap: normal;
|
||||
white-space: pre;
|
||||
word-break: normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
.hljs,
|
||||
.hljs-subst {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.hljs-comment {
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-attribute,
|
||||
.hljs-selector-tag,
|
||||
.hljs-meta-keyword,
|
||||
.hljs-doctag,
|
||||
.hljs-name {
|
||||
color: #f58300;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-type,
|
||||
.hljs-string,
|
||||
.hljs-number,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-quote,
|
||||
.hljs-template-tag,
|
||||
.hljs-deletion {
|
||||
color: var(--color-brand);
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #008888;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-regexp,
|
||||
.hljs-symbol,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-link,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #bc6060;
|
||||
}
|
||||
|
||||
.hljs-literal {
|
||||
color: #78a960;
|
||||
}
|
||||
|
||||
.hljs-built_in,
|
||||
.hljs-bullet,
|
||||
.hljs-code,
|
||||
.hljs-addition {
|
||||
color: #f58300;
|
||||
}
|
||||
|
||||
.hljs-meta {
|
||||
color: #1f7199;
|
||||
}
|
||||
|
||||
.hljs-meta-string {
|
||||
color: #4d99bf;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #222222;
|
||||
padding: 1em 1em 1em 1em;
|
||||
border-width: 5px;
|
||||
border-radius: 2em;
|
||||
border-color: var(--color-divider);
|
||||
overflow-x: hidden;
|
||||
|
||||
code {
|
||||
line-height: 100%;
|
||||
padding: 0.2em;
|
||||
letter-spacing: -0.05em;
|
||||
word-break: normal;
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
import { compareImportSources } from '@modrinth/tooling-config/script-utils/import-sort'
|
||||
import { md } from '@modrinth/utils'
|
||||
import { createMarkdownRenderer } from '@modrinth/ui/src/utils/markdown'
|
||||
import { promises as fs } from 'fs'
|
||||
import { glob } from 'glob'
|
||||
import matter from 'gray-matter'
|
||||
import { minify } from 'html-minifier-terser'
|
||||
import type { Options } from 'markdown-it'
|
||||
import type Renderer from 'markdown-it/lib/renderer.mjs'
|
||||
import type Token from 'markdown-it/lib/token.mjs'
|
||||
import * as path from 'path'
|
||||
import RSS from 'rss'
|
||||
import { parseStringPromise } from 'xml2js'
|
||||
@@ -77,35 +74,14 @@ async function compileArticles() {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const mdIt = md()
|
||||
const slug = frontSlug || path.basename(file, '.md')
|
||||
const renderArticleMarkdown = createMarkdownRenderer({
|
||||
baseUrl: `${SITE_URL}/news/article/${slug}/`,
|
||||
stripBaseUrl: SITE_URL,
|
||||
highlightCode: true,
|
||||
})
|
||||
|
||||
// Normalizes relative URL resolution to occur in the context of the article's directory.
|
||||
// This prevents user agents from resolving relative URLs differently based on whether
|
||||
// the current document URL has a trailing slash or not.
|
||||
function normalizeRendererHtmlUriAttribute(ruleName: string, attrName: string) {
|
||||
const defaultRenderer =
|
||||
mdIt.renderer.rules[ruleName] ||
|
||||
function (tokens, idx, options, _env, self) {
|
||||
return self.renderToken(tokens, idx, options)
|
||||
}
|
||||
|
||||
return (tokens: Token[], idx: number, options: Options, env: object, self: Renderer) => {
|
||||
const attrUrlValue = tokens[idx].attrGet(attrName)
|
||||
if (attrUrlValue) {
|
||||
tokens[idx].attrSet(
|
||||
attrName,
|
||||
new URL(attrUrlValue, `${SITE_URL}/news/article/${slug}/`).href.replace(SITE_URL, ''),
|
||||
)
|
||||
}
|
||||
return defaultRenderer(tokens, idx, options, env, self)
|
||||
}
|
||||
}
|
||||
|
||||
mdIt.renderer.rules.image = normalizeRendererHtmlUriAttribute('image', 'src')
|
||||
mdIt.renderer.rules.link_open = normalizeRendererHtmlUriAttribute('link_open', 'href')
|
||||
|
||||
const minifiedHtml = await minify(mdIt.render(content), {
|
||||
const minifiedHtml = await minify(await renderArticleMarkdown(content), {
|
||||
collapseWhitespace: true,
|
||||
removeComments: true,
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"jiti": "^2.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"@modrinth/ui": "workspace:*",
|
||||
"dayjs": "^1.11.10",
|
||||
"glob": "^10.2.7",
|
||||
"gray-matter": "^4.0.3",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { defineMessage, useVIntl } from '@modrinth/ui'
|
||||
import { renderHighlightedString } from '@modrinth/utils'
|
||||
import { countMarkdownText, defineMessage, useVIntl } from '@modrinth/ui'
|
||||
|
||||
import type { Nag, NagContext } from '../../types/nags'
|
||||
|
||||
@@ -74,42 +73,7 @@ export function analyzeImageContent(markdown: string): {
|
||||
|
||||
export function countText(markdown: string): number {
|
||||
if (!markdown) return 0
|
||||
|
||||
const fallback = (md: string): number => {
|
||||
const withoutCode = md.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')
|
||||
const withoutImagesAndLinks = withoutCode
|
||||
.replace(/!\[[^\]]*]\([^)]+\)/g, ' ')
|
||||
.replace(/\[[^\]]*]\([^)]+\)/g, ' ')
|
||||
const withoutHtml = withoutImagesAndLinks.replace(/<[^>]+>/g, ' ')
|
||||
const withoutMdSyntax = withoutHtml
|
||||
.replace(/^>{1}\s?.*$/gm, ' ')
|
||||
.replace(/^#{1,6}\s+/gm, ' ')
|
||||
.replace(/[*_~`>-]/g, ' ')
|
||||
.replace(/\|/g, ' ')
|
||||
return withoutMdSyntax.replace(/\s+/g, ' ').trim().length
|
||||
}
|
||||
|
||||
if (typeof window === 'undefined' || typeof globalThis.DOMParser === 'undefined') {
|
||||
console.warn(`[Moderation] SSR: no window/DOMParser, falling back for countText`)
|
||||
return fallback(markdown)
|
||||
}
|
||||
|
||||
try {
|
||||
const htmlString = renderHighlightedString(markdown)
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(htmlString, 'text/html')
|
||||
const walker = doc.createTreeWalker(doc.body || doc, NodeFilter.SHOW_TEXT)
|
||||
|
||||
const textList: string[] = []
|
||||
let node = walker.nextNode()
|
||||
while (node) {
|
||||
if (node.textContent) textList.push(node.textContent)
|
||||
node = walker.nextNode()
|
||||
}
|
||||
return textList.join(' ').replace(/\s+/g, ' ').trim().length
|
||||
} catch {
|
||||
return fallback(markdown)
|
||||
}
|
||||
return countMarkdownText(markdown)
|
||||
}
|
||||
|
||||
export const descriptionNags: Nag[] = [
|
||||
|
||||
@@ -54,17 +54,16 @@
|
||||
"@codemirror/language": "^6.9.3",
|
||||
"@codemirror/state": "^6.3.2",
|
||||
"@codemirror/view": "^6.22.1",
|
||||
"@comark/html": "^0.3.1",
|
||||
"@intercom/messenger-js-sdk": "^0.0.14",
|
||||
"@modrinth/api-client": "workspace:*",
|
||||
"@modrinth/assets": "workspace:*",
|
||||
"@modrinth/blog": "workspace:*",
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"@tanstack/vue-query": "5.90.7",
|
||||
"@tresjs/cientos": "^4.3.0",
|
||||
"@tresjs/core": "^4.3.4",
|
||||
"@tresjs/post-processing": "^2.4.0",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/markdown-it": "^14.1.1",
|
||||
"@types/three": "^0.172.0",
|
||||
"@vintl/how-ago": "^3.0.1",
|
||||
"@vueuse/core": "^11.1.0",
|
||||
@@ -73,27 +72,26 @@
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"ace-builds": "^1.43.5",
|
||||
"apexcharts": "^4.0.0",
|
||||
"comark": "^0.3.2",
|
||||
"dayjs": "^1.11.10",
|
||||
"dompurify": "^3.1.7",
|
||||
"es-toolkit": "^1.44.0",
|
||||
"flatpickr": "^4.6.13",
|
||||
"floating-vue": "^5.2.2",
|
||||
"fuse.js": "^6.6.2",
|
||||
"highlight.js": "^11.9.0",
|
||||
"intl-messageformat": "^10.7.7",
|
||||
"jszip": "^3.10.1",
|
||||
"lru-cache": "^11.2.4",
|
||||
"markdown-it": "^13.0.2",
|
||||
"motion-v": "^2.2.1",
|
||||
"postprocessing": "^6.37.6",
|
||||
"qrcode.vue": "^3.4.1",
|
||||
"shiki": "^4.0.2",
|
||||
"three": "^0.172.0",
|
||||
"vue-i18n": "^10.0.0",
|
||||
"vue-select": "4.0.0-beta.6",
|
||||
"vue-typed-virtual-list": "^1.0.10",
|
||||
"vue3-ace-editor": "^2.2.4",
|
||||
"vue3-apexcharts": "^1.5.2",
|
||||
"xss": "^1.0.14"
|
||||
"vue3-apexcharts": "^1.5.2"
|
||||
},
|
||||
"web-types": "../../web-types.json"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<component :is="tag" v-if="html" :class="bodyClass" v-html="html" />
|
||||
<component :is="tag" v-else-if="fallback" :class="bodyClass">{{ fallback }}</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computedAsync } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { renderHighlightedMarkdown, renderMarkdown } from '../../utils/markdown'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
markdown?: string | null
|
||||
inline?: boolean
|
||||
highlight?: boolean
|
||||
fallback?: string
|
||||
bodyClass?: unknown
|
||||
}>(),
|
||||
{
|
||||
markdown: '',
|
||||
inline: false,
|
||||
highlight: false,
|
||||
fallback: '',
|
||||
bodyClass: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
const rendered = computedAsync(async () => {
|
||||
const markdown = props.markdown ?? ''
|
||||
const html = props.highlight ? await renderHighlightedMarkdown(markdown) : await renderMarkdown(markdown)
|
||||
|
||||
if (!props.inline) return html
|
||||
|
||||
const match = html.match(/^<p>([\s\S]*)<\/p>\s*$/)
|
||||
return match ? match[1] : html
|
||||
}, '')
|
||||
|
||||
const html = computed(() => rendered.value)
|
||||
const tag = computed(() => (props.inline ? 'span' : 'div'))
|
||||
const bodyClass = computed(() => (props.inline ? props.bodyClass : ['markdown-body', props.bodyClass]))
|
||||
</script>
|
||||
@@ -39,11 +39,7 @@
|
||||
<span class="label__description"></span>
|
||||
</span>
|
||||
<div class="markdown-body-wrapper">
|
||||
<div
|
||||
style="width: 100%"
|
||||
class="markdown-body"
|
||||
v-html="renderHighlightedString(linkMarkdown)"
|
||||
/>
|
||||
<MarkdownBody style="width: 100%" :markdown="linkMarkdown" highlight />
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-4">
|
||||
<ButtonStyled type="outlined">
|
||||
@@ -137,11 +133,7 @@
|
||||
<span class="label__description"></span>
|
||||
</span>
|
||||
<div class="markdown-body-wrapper">
|
||||
<div
|
||||
style="width: 100%"
|
||||
class="markdown-body"
|
||||
v-html="renderHighlightedString(imageMarkdown)"
|
||||
/>
|
||||
<MarkdownBody style="width: 100%" :markdown="imageMarkdown" highlight />
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-4">
|
||||
<ButtonStyled type="outlined">
|
||||
@@ -197,11 +189,7 @@
|
||||
</span>
|
||||
|
||||
<div class="markdown-body-wrapper">
|
||||
<div
|
||||
style="width: 100%"
|
||||
class="markdown-body"
|
||||
v-html="renderHighlightedString(videoMarkdown)"
|
||||
/>
|
||||
<MarkdownBody style="width: 100%" :markdown="videoMarkdown" highlight />
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-4">
|
||||
<ButtonStyled type="outlined">
|
||||
@@ -289,14 +277,14 @@
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="markdown-body-wrapper">
|
||||
<div
|
||||
<MarkdownBody
|
||||
style="width: 100%"
|
||||
:style="{
|
||||
maxHeight: props.maxHeight ? `${props.maxHeight}px` : 'unset',
|
||||
overflowY: 'auto',
|
||||
}"
|
||||
class="markdown-body"
|
||||
v-html="renderHighlightedString(currentValue ?? '')"
|
||||
:markdown="currentValue ?? ''"
|
||||
highlight
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -330,7 +318,6 @@ import {
|
||||
YouTubeIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { markdownCommands, modrinthMarkdownEditorKeymap } from '@modrinth/utils/codemirror'
|
||||
import { renderHighlightedString } from '@modrinth/utils/highlightjs'
|
||||
import { type Component, computed, onBeforeUnmount, onMounted, ref, toRef, watch } from 'vue'
|
||||
|
||||
import { defineMessages, type MessageDescriptor, useVIntl } from '../../composables/i18n'
|
||||
@@ -340,6 +327,7 @@ import ButtonStyled from './ButtonStyled.vue'
|
||||
import Chips from './Chips.vue'
|
||||
import FileInput from './FileInput.vue'
|
||||
import IntlFormatted from './IntlFormatted.vue'
|
||||
import MarkdownBody from './MarkdownBody.vue'
|
||||
import StyledInput from './StyledInput.vue'
|
||||
import Toggle from './Toggle.vue'
|
||||
|
||||
|
||||
@@ -27,19 +27,19 @@
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<div v-if="message" class="markdown-body" v-html="renderString(message)" />
|
||||
<MarkdownBody v-if="message" :markdown="message" />
|
||||
</Admonition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { XIcon } from '@modrinth/assets'
|
||||
import { renderString } from '@modrinth/utils'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { defineMessages, type MessageDescriptor, useVIntl } from '../../composables/i18n'
|
||||
import Admonition from './Admonition.vue'
|
||||
import ButtonStyled from './ButtonStyled.vue'
|
||||
import CopyCode from './CopyCode.vue'
|
||||
import MarkdownBody from './MarkdownBody.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -50,6 +50,7 @@ export { default as LoadingBar } from './LoadingBar.vue'
|
||||
export { default as LoadingIndicator } from './LoadingIndicator.vue'
|
||||
export { default as ManySelect } from './ManySelect.vue'
|
||||
export { default as MarkdownEditor } from './MarkdownEditor.vue'
|
||||
export { default as MarkdownBody } from './MarkdownBody.vue'
|
||||
export type { MultiSelectOption } from './MultiSelect.vue'
|
||||
export { default as MultiSelect } from './MultiSelect.vue'
|
||||
export type { MaybeCtxFn, StageButtonConfig, StageConfigInput } from './MultiStageModal.vue'
|
||||
|
||||
@@ -36,20 +36,26 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-8 mt-3 rounded-2xl bg-bg-raised px-4 py-3">
|
||||
<div class="changelog-body" v-html="renderHighlightedString(entry.body)" />
|
||||
<MarkdownBody body-class="changelog-body" :markdown="entry.body" highlight />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { VersionEntry } from '@modrinth/blog/changelog'
|
||||
import { renderHighlightedString } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useFormatDateTime, useRelativeTime } from '../../composables'
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
import AutoLink from '../base/AutoLink.vue'
|
||||
import MarkdownBody from '../base/MarkdownBody.vue'
|
||||
|
||||
type VersionEntry = {
|
||||
date: dayjs.Dayjs
|
||||
product: 'web' | 'hosting' | 'app'
|
||||
version?: string
|
||||
body: string
|
||||
}
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
</template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<template v-if="description">
|
||||
<div
|
||||
<MarkdownBody
|
||||
v-if="markdown"
|
||||
class="markdown-body max-w-[35rem]"
|
||||
v-html="renderString(description)"
|
||||
body-class="max-w-[35rem]"
|
||||
:markdown="description"
|
||||
/>
|
||||
<p v-else class="max-w-[35rem] m-0">
|
||||
{{ description }}
|
||||
@@ -50,10 +50,10 @@
|
||||
|
||||
<script setup>
|
||||
import { TrashIcon, XIcon } from '@modrinth/assets'
|
||||
import { renderString } from '@modrinth/utils'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import ButtonStyled from '../base/ButtonStyled.vue'
|
||||
import MarkdownBody from '../base/MarkdownBody.vue'
|
||||
import StyledInput from '../base/StyledInput.vue'
|
||||
import NewModal from './NewModal.vue'
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="markdown-body" v-html="renderHighlightedString(description ?? '')" />
|
||||
<MarkdownBody :markdown="description ?? ''" highlight />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { renderHighlightedString } from '@modrinth/utils'
|
||||
import MarkdownBody from '../base/MarkdownBody.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
|
||||
+5
-3
@@ -177,8 +177,9 @@
|
||||
<div
|
||||
v-else-if="selectedVersion.changelog"
|
||||
class="markdown [&_img]:max-w-full [&_img]:h-auto"
|
||||
v-html="renderHighlightedString(selectedVersion.changelog)"
|
||||
/>
|
||||
>
|
||||
<MarkdownBody :markdown="selectedVersion.changelog" highlight />
|
||||
</div>
|
||||
<div v-else class="text-secondary italic">
|
||||
{{ formatMessage(messages.noChangelog) }}
|
||||
</div>
|
||||
@@ -248,12 +249,13 @@ import {
|
||||
TriangleAlertIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { capitalizeString, renderHighlightedString } from '@modrinth/utils'
|
||||
import { capitalizeString } from '@modrinth/utils'
|
||||
import { useTimeoutFn } from '@vueuse/core'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import Avatar from '#ui/components/base/Avatar.vue'
|
||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||
import MarkdownBody from '#ui/components/base/MarkdownBody.vue'
|
||||
import StyledInput from '#ui/components/base/StyledInput.vue'
|
||||
import NewModal from '#ui/components/modal/NewModal.vue'
|
||||
import VersionChannelIndicator from '#ui/components/version/VersionChannelIndicator.vue'
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -4,6 +4,7 @@ export * from './events'
|
||||
export * from './file-extensions'
|
||||
export * from './game-modes'
|
||||
export * from './loaders'
|
||||
export * from './markdown'
|
||||
export * from './notices'
|
||||
export * from './savable'
|
||||
export * from './search'
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './plain-text'
|
||||
export * from './render'
|
||||
export * from './security'
|
||||
@@ -0,0 +1,25 @@
|
||||
export function markdownToPlainText(markdown: string): string {
|
||||
if (!markdown) return ''
|
||||
|
||||
const withoutCode = markdown.replace(/```[\s\S]*?```/g, ' ').replace(/`[^`]*`/g, ' ')
|
||||
const withoutImagesAndLinks = withoutCode
|
||||
.replace(/!\[[^\]]*]\([^)]+\)/g, ' ')
|
||||
.replace(/\[([^\]]*)]\([^)]+\)/g, '$1')
|
||||
const withoutHtml = withoutImagesAndLinks.replace(/<[^>]+>/g, ' ')
|
||||
const withoutMarkdownSyntax = withoutHtml
|
||||
.replace(/^>{1}\s?.*$/gm, ' ')
|
||||
.replace(/^#{1,6}\s+/gm, ' ')
|
||||
.replace(/[*_~`>-]/g, ' ')
|
||||
.replace(/\|/g, ' ')
|
||||
|
||||
return withoutMarkdownSyntax.replace(/\s+/g, ' ').trim()
|
||||
}
|
||||
|
||||
export function countMarkdownText(markdown: string): number {
|
||||
return markdownToPlainText(markdown).length
|
||||
}
|
||||
|
||||
export function markdownHasImage(markdown: string): boolean {
|
||||
if (!markdown) return false
|
||||
return /!\[[^\]]*]\([^)]+\)/.test(markdown) || /<img[\s>]/i.test(markdown)
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { renderHTML } from '@comark/html'
|
||||
import { createParse } from 'comark'
|
||||
import highlight from 'comark/plugins/highlight'
|
||||
import { codeToHtml } from 'shiki'
|
||||
|
||||
import { modrinthMarkdownSecurity, type ModrinthMarkdownSecurityOptions } from './security'
|
||||
|
||||
export interface MarkdownRenderOptions extends ModrinthMarkdownSecurityOptions {
|
||||
highlightCode?: boolean
|
||||
}
|
||||
|
||||
export type MarkdownRenderer = (markdown: string) => Promise<string>
|
||||
|
||||
const shikiThemes = {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
}
|
||||
|
||||
function normalizeLanguage(language: string): string {
|
||||
const normalized = language.trim().toLowerCase()
|
||||
const aliases: Record<string, string> = {
|
||||
command: 'mcfunction',
|
||||
fxml: 'xml',
|
||||
htm: 'html',
|
||||
js: 'javascript',
|
||||
json5: 'json',
|
||||
kt: 'kotlin',
|
||||
kubejs: 'javascript',
|
||||
mcui: 'xml',
|
||||
py: 'python',
|
||||
sk: 'skript',
|
||||
xhtml: 'html',
|
||||
yml: 'yaml',
|
||||
}
|
||||
|
||||
return aliases[normalized] ?? normalized
|
||||
}
|
||||
|
||||
export function createMarkdownRenderer(options: MarkdownRenderOptions = {}): MarkdownRenderer {
|
||||
const plugins = []
|
||||
|
||||
if (options.highlightCode) {
|
||||
plugins.push(
|
||||
highlight({
|
||||
preStyles: false,
|
||||
registerDefaultLanguages: true,
|
||||
// TODO: Add Shiki grammars for skript and mcfunction before re-enabling those legacy Highlight.js aliases.
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
plugins.push(modrinthMarkdownSecurity(options))
|
||||
|
||||
const parse = createParse({
|
||||
autoClose: false,
|
||||
html: true,
|
||||
plugins,
|
||||
})
|
||||
|
||||
return async (markdown: string) => {
|
||||
if (!markdown) return ''
|
||||
|
||||
const tree = await parse(markdown)
|
||||
return renderHTML(tree)
|
||||
}
|
||||
}
|
||||
|
||||
const renderMarkdownDefault = createMarkdownRenderer()
|
||||
const renderHighlightedMarkdownDefault = createMarkdownRenderer({ highlightCode: true })
|
||||
|
||||
export async function renderMarkdown(markdown: string): Promise<string> {
|
||||
return renderMarkdownDefault(markdown)
|
||||
}
|
||||
|
||||
export async function renderHighlightedMarkdown(markdown: string): Promise<string> {
|
||||
return renderHighlightedMarkdownDefault(markdown)
|
||||
}
|
||||
|
||||
export async function renderMarkdownInline(markdown: string): Promise<string> {
|
||||
const html = await renderMarkdown(markdown)
|
||||
const match = html.match(/^<p>([\s\S]*)<\/p>\s*$/)
|
||||
return match ? match[1] : html
|
||||
}
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
return value
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
}
|
||||
|
||||
function plainCodeLines(code: string): string[] {
|
||||
return code.split('\n').map(escapeHtml)
|
||||
}
|
||||
|
||||
export async function highlightCodeLines(code: string, language: string): Promise<string[]> {
|
||||
if (!code) return []
|
||||
|
||||
try {
|
||||
const html = await codeToHtml(code, {
|
||||
lang: normalizeLanguage(language),
|
||||
themes: shikiThemes,
|
||||
})
|
||||
|
||||
if (typeof DOMParser === 'undefined') return plainCodeLines(code)
|
||||
|
||||
const document = new DOMParser().parseFromString(html, 'text/html')
|
||||
const lines = [...document.querySelectorAll('span.line')].map((line) => line.innerHTML)
|
||||
return lines.length > 0 ? lines : plainCodeLines(code)
|
||||
} catch {
|
||||
return plainCodeLines(code)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,543 @@
|
||||
import type { ComarkNode } from 'comark'
|
||||
import { defineComarkPlugin } from 'comark/parse'
|
||||
|
||||
type AttributeMap = Record<string, unknown>
|
||||
type MutableComarkElement = [string | null, AttributeMap, ...ComarkNode[]]
|
||||
|
||||
export interface ModrinthMarkdownSecurityOptions {
|
||||
baseUrl?: string
|
||||
stripBaseUrl?: string
|
||||
}
|
||||
|
||||
const ALLOWED_TAGS = new Set([
|
||||
'a',
|
||||
'abbr',
|
||||
'address',
|
||||
'area',
|
||||
'article',
|
||||
'aside',
|
||||
'audio',
|
||||
'b',
|
||||
'bdi',
|
||||
'bdo',
|
||||
'big',
|
||||
'blockquote',
|
||||
'br',
|
||||
'caption',
|
||||
'center',
|
||||
'cite',
|
||||
'code',
|
||||
'col',
|
||||
'colgroup',
|
||||
'dd',
|
||||
'del',
|
||||
'details',
|
||||
'div',
|
||||
'dl',
|
||||
'dt',
|
||||
'em',
|
||||
'figcaption',
|
||||
'figure',
|
||||
'font',
|
||||
'footer',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'header',
|
||||
'hr',
|
||||
'i',
|
||||
'iframe',
|
||||
'img',
|
||||
'input',
|
||||
'ins',
|
||||
'kbd',
|
||||
'li',
|
||||
'map',
|
||||
'mark',
|
||||
'nav',
|
||||
'ol',
|
||||
'p',
|
||||
'picture',
|
||||
'pre',
|
||||
's',
|
||||
'section',
|
||||
'small',
|
||||
'source',
|
||||
'span',
|
||||
'strike',
|
||||
'strong',
|
||||
'sub',
|
||||
'summary',
|
||||
'sup',
|
||||
'table',
|
||||
'tbody',
|
||||
'td',
|
||||
'tfoot',
|
||||
'th',
|
||||
'thead',
|
||||
'tr',
|
||||
'tt',
|
||||
'u',
|
||||
'ul',
|
||||
'video',
|
||||
])
|
||||
|
||||
const ALLOWED_ATTRIBUTES: Record<string, Set<string>> = {
|
||||
a: new Set(['href', 'rel', 'target', 'title']),
|
||||
abbr: new Set(['title']),
|
||||
area: new Set(['alt', 'coords', 'href', 'shape']),
|
||||
audio: new Set(['autoplay', 'controls', 'crossorigin', 'loop', 'muted', 'preload', 'src']),
|
||||
blockquote: new Set(['cite']),
|
||||
col: new Set(['align', 'span', 'valign', 'width']),
|
||||
colgroup: new Set(['align', 'span', 'valign', 'width']),
|
||||
del: new Set(['datetime']),
|
||||
details: new Set(['open']),
|
||||
font: new Set(['color', 'face', 'size']),
|
||||
h1: new Set(['id']),
|
||||
h2: new Set(['id']),
|
||||
h3: new Set(['id']),
|
||||
h4: new Set(['id']),
|
||||
h5: new Set(['id']),
|
||||
h6: new Set(['id']),
|
||||
iframe: new Set(['allowfullscreen', 'frameborder', 'height', 'src', 'width', 'start', 'end']),
|
||||
img: new Set(['align', 'alt', 'height', 'loading', 'src', 'style', 'title', 'usemap', 'width']),
|
||||
input: new Set(['checked', 'disabled', 'type']),
|
||||
ins: new Set(['datetime']),
|
||||
kbd: new Set(['id']),
|
||||
map: new Set(['name']),
|
||||
p: new Set(['align']),
|
||||
div: new Set(['align']),
|
||||
pre: new Set(['class', 'style', 'tabindex']),
|
||||
code: new Set(['class']),
|
||||
span: new Set(['class', 'style']),
|
||||
source: new Set(['media', 'sizes', 'src', 'srcset', 'type']),
|
||||
table: new Set(['align', 'border', 'valign', 'width']),
|
||||
tbody: new Set(['align', 'valign']),
|
||||
td: new Set(['align', 'colspan', 'rowspan', 'style', 'valign', 'width']),
|
||||
tfoot: new Set(['align', 'valign']),
|
||||
th: new Set(['align', 'colspan', 'rowspan', 'style', 'valign', 'width']),
|
||||
thead: new Set(['align', 'valign']),
|
||||
tr: new Set(['align', 'rowspan', 'valign']),
|
||||
video: new Set([
|
||||
'autoplay',
|
||||
'controls',
|
||||
'crossorigin',
|
||||
'height',
|
||||
'loop',
|
||||
'muted',
|
||||
'playsinline',
|
||||
'poster',
|
||||
'preload',
|
||||
'src',
|
||||
'width',
|
||||
]),
|
||||
}
|
||||
|
||||
const MEDIA_URL_TAGS = new Set(['audio', 'img', 'source', 'video'])
|
||||
const MEDIA_URL_ATTRIBUTES = new Set(['poster', 'src', 'srcset'])
|
||||
|
||||
const ALLOWED_MEDIA_HOSTNAMES = new Set([
|
||||
'imgur.com',
|
||||
'i.imgur.com',
|
||||
'cdn-raw.modrinth.com',
|
||||
'cdn.modrinth.com',
|
||||
'staging-cdn-raw.modrinth.com',
|
||||
'staging-cdn.modrinth.com',
|
||||
'github.com',
|
||||
'raw.githubusercontent.com',
|
||||
'img.shields.io',
|
||||
'i.postimg.cc',
|
||||
'wsrv.nl',
|
||||
'cf.way2muchnoise.eu',
|
||||
'bstats.org',
|
||||
])
|
||||
|
||||
const ALLOWED_MEDIA_HOSTNAME_SUFFIXES = ['.github.io']
|
||||
|
||||
const ALLOWED_IFRAME_SOURCES = [
|
||||
{
|
||||
url: /^https?:\/\/(www\.)?youtube(-nocookie)?\.com\/embed\/[a-zA-Z0-9_-]{11}/,
|
||||
allowedParameters: [/start=\d+/, /end=\d+/],
|
||||
},
|
||||
{
|
||||
url: /^https?:\/\/(www\.)?discord\.com\/widget/,
|
||||
allowedParameters: [/id=\d{18,19}/],
|
||||
},
|
||||
]
|
||||
|
||||
const BARE_URL_PATTERN = /https?:\/\/[^\s<]+/g
|
||||
|
||||
function isElement(node: ComarkNode): node is MutableComarkElement {
|
||||
return Array.isArray(node)
|
||||
}
|
||||
|
||||
function normalizeAttributeName(name: string): string {
|
||||
return name.startsWith(':') ? name.slice(1) : name.toLowerCase()
|
||||
}
|
||||
|
||||
function stringifyAttribute(value: unknown): string | undefined {
|
||||
if (typeof value === 'string') return value
|
||||
if (typeof value === 'number' || typeof value === 'boolean') return String(value)
|
||||
return undefined
|
||||
}
|
||||
|
||||
function normalizeUrl(value: string, options: ModrinthMarkdownSecurityOptions): string {
|
||||
if (!options.baseUrl) return value
|
||||
|
||||
try {
|
||||
const url = new URL(value, options.baseUrl)
|
||||
const href = url.href
|
||||
return options.stripBaseUrl && href.startsWith(options.stripBaseUrl)
|
||||
? href.slice(options.stripBaseUrl.length)
|
||||
: href
|
||||
} catch {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
function hasSafeUrlProtocol(value: string, allowDataImage = false): boolean {
|
||||
const trimmed = value.trim()
|
||||
if (!trimmed) return false
|
||||
if (trimmed === '#') return true
|
||||
if (
|
||||
trimmed.startsWith('/') ||
|
||||
trimmed.startsWith('./') ||
|
||||
trimmed.startsWith('../') ||
|
||||
trimmed.startsWith('#')
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(trimmed)
|
||||
if (['http:', 'https:', 'mailto:', 'tel:', 'ftp:'].includes(url.protocol)) return true
|
||||
return allowDataImage && url.protocol === 'data:' && trimmed.startsWith('data:image/')
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function isAllowedMediaHost(hostname: string): boolean {
|
||||
return (
|
||||
ALLOWED_MEDIA_HOSTNAMES.has(hostname) ||
|
||||
ALLOWED_MEDIA_HOSTNAME_SUFFIXES.some((suffix) => hostname.endsWith(suffix))
|
||||
)
|
||||
}
|
||||
|
||||
function sanitizeMediaUrl(value: string, attrName: string): string | undefined {
|
||||
const allowDataImage = attrName !== 'srcset'
|
||||
if (!hasSafeUrlProtocol(value, allowDataImage)) return undefined
|
||||
if (value.trim().startsWith('data:')) return value
|
||||
|
||||
try {
|
||||
const url = new URL(value)
|
||||
|
||||
if (url.hostname.includes('wsrv.nl')) {
|
||||
url.searchParams.delete('errorredirect')
|
||||
url.searchParams.delete('default')
|
||||
}
|
||||
|
||||
if (!isAllowedMediaHost(url.hostname)) {
|
||||
return `https://wsrv.nl/?url=${encodeURIComponent(url.toString().replaceAll('&', '&'))}&n=-1`
|
||||
}
|
||||
|
||||
return url.toString()
|
||||
} catch {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeSrcset(value: string): string | undefined {
|
||||
const srcset = value
|
||||
.split(',')
|
||||
.map((entry) => {
|
||||
const parts = entry.trim().split(/\s+/)
|
||||
const url = parts.shift()
|
||||
if (!url) return undefined
|
||||
|
||||
const sanitized = sanitizeMediaUrl(url, 'srcset')
|
||||
return sanitized ? [sanitized, ...parts].join(' ') : undefined
|
||||
})
|
||||
.filter((entry): entry is string => !!entry)
|
||||
.join(', ')
|
||||
|
||||
return srcset || undefined
|
||||
}
|
||||
|
||||
function sanitizeIframeSrc(value: string): string | undefined {
|
||||
try {
|
||||
const url = new URL(value)
|
||||
|
||||
for (const source of ALLOWED_IFRAME_SOURCES) {
|
||||
if (!source.url.test(url.href)) continue
|
||||
|
||||
const searchParams = new URLSearchParams(url.searchParams)
|
||||
url.searchParams.forEach((paramValue, key) => {
|
||||
if (!source.allowedParameters.some((param) => param.test(`${key}=${paramValue}`))) {
|
||||
searchParams.delete(key)
|
||||
}
|
||||
})
|
||||
|
||||
url.search = searchParams.toString()
|
||||
return url.toString()
|
||||
}
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function isSafeStyleValue(value: string): boolean {
|
||||
return !/[<>]/.test(value) && !/url\s*\(|expression\s*\(/i.test(value)
|
||||
}
|
||||
|
||||
function sanitizeStyle(value: string, tag: string): string | undefined {
|
||||
const declarations = value
|
||||
.split(';')
|
||||
.map((declaration) => {
|
||||
const [propertyRaw, ...valueParts] = declaration.split(':')
|
||||
const property = propertyRaw?.trim().toLowerCase()
|
||||
const propertyValue = valueParts.join(':').trim()
|
||||
|
||||
if (!property || !propertyValue) return undefined
|
||||
if (!isSafeStyleValue(propertyValue)) return undefined
|
||||
|
||||
if (property === 'image-rendering' && propertyValue === 'pixelated') {
|
||||
return `${property}: ${propertyValue}`
|
||||
}
|
||||
|
||||
if (property === 'text-align' && /^(center|left|right)$/.test(propertyValue)) {
|
||||
return `${property}: ${propertyValue}`
|
||||
}
|
||||
|
||||
if (property === 'float' && /^(left|right)$/.test(propertyValue)) {
|
||||
return `${property}: ${propertyValue}`
|
||||
}
|
||||
|
||||
if (tag === 'pre' && property.startsWith('--shiki-')) {
|
||||
return `${property}: ${propertyValue}`
|
||||
}
|
||||
|
||||
if (tag === 'span') {
|
||||
if (
|
||||
['color', 'background-color'].includes(property) &&
|
||||
/^(#[\da-f]{3,8}|rgb\([\d\s,.%]+\)|rgba\([\d\s,.%]+\)|hsl\([\d\s,.%]+\)|hsla\([\d\s,.%]+\)|var\(--shiki-[\w-]+\)|currentcolor|transparent|inherit)$/i.test(
|
||||
propertyValue,
|
||||
)
|
||||
) {
|
||||
return `${property}: ${propertyValue}`
|
||||
}
|
||||
|
||||
if (property === 'font-style' && /^(normal|italic)$/.test(propertyValue)) {
|
||||
return `${property}: ${propertyValue}`
|
||||
}
|
||||
|
||||
if (property === 'font-weight' && /^(normal|bold|[1-9]00)$/.test(propertyValue)) {
|
||||
return `${property}: ${propertyValue}`
|
||||
}
|
||||
|
||||
if (property === 'text-decoration' && /^(none|underline|line-through)$/.test(propertyValue)) {
|
||||
return `${property}: ${propertyValue}`
|
||||
}
|
||||
|
||||
if (property === 'display' && /^(inline|inline-block|block)$/.test(propertyValue)) {
|
||||
return `${property}: ${propertyValue}`
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
})
|
||||
.filter((declaration): declaration is string => !!declaration)
|
||||
|
||||
return declarations.length > 0 ? declarations.join('; ') : undefined
|
||||
}
|
||||
|
||||
function sanitizeClass(tag: string, value: string): string | undefined {
|
||||
const classes = value
|
||||
.split(/\s+/)
|
||||
.filter(
|
||||
(className) =>
|
||||
className.startsWith('language-') ||
|
||||
(tag === 'pre' &&
|
||||
/^(shiki|shiki-themes|material-theme-lighter|material-theme-palenight)$/.test(className)) ||
|
||||
(tag === 'span' && ['line', 'highlight'].includes(className)),
|
||||
)
|
||||
|
||||
return classes.length > 0 ? classes.join(' ') : undefined
|
||||
}
|
||||
|
||||
function sanitizeHref(value: string, options: ModrinthMarkdownSecurityOptions): string | undefined {
|
||||
const normalized = normalizeUrl(value, options)
|
||||
return hasSafeUrlProtocol(normalized) ? normalized : undefined
|
||||
}
|
||||
|
||||
function isExactModrinthLink(href: string): boolean {
|
||||
try {
|
||||
return new URL(href).hostname === 'modrinth.com'
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeAttribute(
|
||||
tag: string,
|
||||
rawName: string,
|
||||
rawValue: unknown,
|
||||
options: ModrinthMarkdownSecurityOptions,
|
||||
): [string, unknown] | undefined {
|
||||
const name = normalizeAttributeName(rawName)
|
||||
if (name.startsWith('on')) return undefined
|
||||
|
||||
const allowedAttributes = ALLOWED_ATTRIBUTES[tag]
|
||||
if (!allowedAttributes?.has(name)) return undefined
|
||||
|
||||
const value = stringifyAttribute(rawValue)
|
||||
if (value === undefined) return undefined
|
||||
|
||||
if (tag === 'iframe' && name === 'src') {
|
||||
const src = sanitizeIframeSrc(value)
|
||||
return src ? [name, src] : undefined
|
||||
}
|
||||
|
||||
if (tag === 'a' || tag === 'area') {
|
||||
if (name === 'href') {
|
||||
const href = sanitizeHref(value, options)
|
||||
return href ? [name, href] : undefined
|
||||
}
|
||||
if (name === 'target' && !['_blank', '_self', '_parent', '_top'].includes(value)) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
if (MEDIA_URL_TAGS.has(tag) && MEDIA_URL_ATTRIBUTES.has(name)) {
|
||||
if (name === 'srcset') {
|
||||
const srcset = sanitizeSrcset(normalizeUrl(value, options))
|
||||
return srcset ? [name, srcset] : undefined
|
||||
}
|
||||
|
||||
const normalized = normalizeUrl(value, options)
|
||||
const sanitized = sanitizeMediaUrl(normalized, name)
|
||||
return sanitized ? [name, sanitized] : undefined
|
||||
}
|
||||
|
||||
if (name === 'style') {
|
||||
const style = sanitizeStyle(value, tag)
|
||||
return style ? [name, style] : undefined
|
||||
}
|
||||
|
||||
if (name === 'class') {
|
||||
const className = sanitizeClass(tag, value)
|
||||
return className ? [name, className] : undefined
|
||||
}
|
||||
|
||||
if (name === 'tabindex' && !/^-?\d+$/.test(value)) return undefined
|
||||
|
||||
if (tag === 'input' && name === 'type' && value !== 'checkbox') return undefined
|
||||
|
||||
return [name, value]
|
||||
}
|
||||
|
||||
function sanitizeElement(
|
||||
element: MutableComarkElement,
|
||||
options: ModrinthMarkdownSecurityOptions,
|
||||
): false | void {
|
||||
const tag = element[0]
|
||||
if (typeof tag !== 'string' || !ALLOWED_TAGS.has(tag)) return false
|
||||
|
||||
const attrs = element[1] as AttributeMap
|
||||
const sanitizedAttrs: AttributeMap = {}
|
||||
|
||||
for (const [rawName, rawValue] of Object.entries(attrs)) {
|
||||
const sanitized = sanitizeAttribute(tag, rawName, rawValue, options)
|
||||
if (!sanitized) continue
|
||||
|
||||
const [name, value] = sanitized
|
||||
sanitizedAttrs[name] = value
|
||||
}
|
||||
|
||||
if (tag === 'a') {
|
||||
const href = stringifyAttribute(sanitizedAttrs.href)
|
||||
if (!href) return false
|
||||
if (!isExactModrinthLink(href)) {
|
||||
sanitizedAttrs.rel = 'noopener nofollow ugc'
|
||||
}
|
||||
}
|
||||
|
||||
if (tag === 'area' && !sanitizedAttrs.href) return false
|
||||
if (tag === 'iframe' && !sanitizedAttrs.src) return false
|
||||
|
||||
element[1] = sanitizedAttrs
|
||||
}
|
||||
|
||||
function splitTextByBareUrls(text: string): ComarkNode[] {
|
||||
const nodes: ComarkNode[] = []
|
||||
let lastIndex = 0
|
||||
|
||||
for (const match of text.matchAll(BARE_URL_PATTERN)) {
|
||||
const url = match[0].replace(/[),.;:!?]+$/, '')
|
||||
const index = match.index ?? 0
|
||||
|
||||
if (index > lastIndex) nodes.push(text.slice(lastIndex, index))
|
||||
nodes.push(['a', { href: url }, url])
|
||||
lastIndex = index + url.length
|
||||
}
|
||||
|
||||
if (lastIndex === 0) return [text]
|
||||
if (lastIndex < text.length) nodes.push(text.slice(lastIndex))
|
||||
return nodes
|
||||
}
|
||||
|
||||
function linkifyTextNodes(nodes: ComarkNode[], parentTag?: string): void {
|
||||
const shouldSkip = parentTag === 'a' || parentTag === 'code' || parentTag === 'pre'
|
||||
if (shouldSkip) return
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
|
||||
if (typeof node === 'string' && !shouldSkip) {
|
||||
const replacements = splitTextByBareUrls(node)
|
||||
if (replacements.length > 1) {
|
||||
nodes.splice(i, 1, ...replacements)
|
||||
i += replacements.length - 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (isElement(node)) {
|
||||
const children = node.slice(2)
|
||||
linkifyTextNodes(children, node[0] as string | undefined)
|
||||
node.splice(2, node.length - 2, ...children)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeNodes(nodes: ComarkNode[], options: ModrinthMarkdownSecurityOptions): void {
|
||||
for (let i = nodes.length - 1; i >= 0; i--) {
|
||||
const node = nodes[i]
|
||||
if (!isElement(node)) continue
|
||||
|
||||
const result = sanitizeElement(node, options)
|
||||
if (result === false) {
|
||||
nodes.splice(i, 1)
|
||||
continue
|
||||
}
|
||||
|
||||
const children = node.slice(2)
|
||||
sanitizeNodes(children, options)
|
||||
node.splice(2, node.length - 2, ...children)
|
||||
}
|
||||
}
|
||||
|
||||
export const modrinthMarkdownSecurity = defineComarkPlugin(
|
||||
(options: ModrinthMarkdownSecurityOptions = {}) => ({
|
||||
name: 'modrinth-markdown-security',
|
||||
post(state) {
|
||||
linkifyTextNodes(state.tree.nodes)
|
||||
sanitizeNodes(state.tree.nodes, options)
|
||||
},
|
||||
}),
|
||||
)
|
||||
@@ -1,106 +0,0 @@
|
||||
import hljs from 'highlight.js/lib/core'
|
||||
// Configs
|
||||
import gradle from 'highlight.js/lib/languages/gradle'
|
||||
// Coding
|
||||
import groovy from 'highlight.js/lib/languages/groovy'
|
||||
import ini from 'highlight.js/lib/languages/ini'
|
||||
import java from 'highlight.js/lib/languages/java'
|
||||
// Scripting
|
||||
import javascript from 'highlight.js/lib/languages/javascript'
|
||||
import json from 'highlight.js/lib/languages/json'
|
||||
import kotlin from 'highlight.js/lib/languages/kotlin'
|
||||
import lua from 'highlight.js/lib/languages/lua'
|
||||
import properties from 'highlight.js/lib/languages/properties'
|
||||
import python from 'highlight.js/lib/languages/python'
|
||||
import scala from 'highlight.js/lib/languages/scala'
|
||||
import xml from 'highlight.js/lib/languages/xml'
|
||||
import yaml from 'highlight.js/lib/languages/yaml'
|
||||
import mcfunction from 'highlightjs-mcfunction'
|
||||
|
||||
import { configuredXss, md } from '../parse'
|
||||
import skript from './skript'
|
||||
|
||||
/* REGISTRATION */
|
||||
// Scripting
|
||||
hljs.registerLanguage('javascript', javascript)
|
||||
hljs.registerLanguage('python', python)
|
||||
hljs.registerLanguage('lua', lua)
|
||||
hljs.registerLanguage('skript', skript)
|
||||
hljs.registerLanguage('mcfunction', mcfunction)
|
||||
// Coding
|
||||
hljs.registerLanguage('java', java)
|
||||
hljs.registerLanguage('kotlin', kotlin)
|
||||
hljs.registerLanguage('scala', scala)
|
||||
hljs.registerLanguage('groovy', groovy)
|
||||
// Configs
|
||||
hljs.registerLanguage('gradle', gradle)
|
||||
hljs.registerLanguage('json', json)
|
||||
hljs.registerLanguage('ini', ini)
|
||||
hljs.registerLanguage('yaml', yaml)
|
||||
hljs.registerLanguage('xml', xml)
|
||||
hljs.registerLanguage('properties', properties)
|
||||
|
||||
/* ALIASES */
|
||||
// Scripting
|
||||
hljs.registerAliases(['js'], { languageName: 'javascript' })
|
||||
hljs.registerAliases(['py'], { languageName: 'python' })
|
||||
hljs.registerAliases(['sk'], { languageName: 'skript' })
|
||||
hljs.registerAliases(['command'], { languageName: 'mcfunction' })
|
||||
hljs.registerAliases(['kubejs'], { languageName: 'javascript' })
|
||||
// Coding
|
||||
hljs.registerAliases(['kt'], { languageName: 'kotlin' })
|
||||
// Configs
|
||||
hljs.registerAliases(['json5'], { languageName: 'json' })
|
||||
hljs.registerAliases(['toml'], { languageName: 'ini' })
|
||||
hljs.registerAliases(['yml'], { languageName: 'yaml' })
|
||||
hljs.registerAliases(['html', 'htm', 'xhtml', 'mcui', 'fxml'], { languageName: 'xml' })
|
||||
|
||||
export { hljs }
|
||||
|
||||
export const renderHighlightedString = (string) =>
|
||||
configuredXss.process(
|
||||
md({
|
||||
highlight(str, lang) {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return hljs.highlight(str, { language: lang }).value
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
}).render(string),
|
||||
)
|
||||
|
||||
export const highlightCodeLines = (code: string, language: string): string[] => {
|
||||
if (!code) return []
|
||||
|
||||
if (!hljs.getLanguage(language)) {
|
||||
return code.split('\n')
|
||||
}
|
||||
|
||||
try {
|
||||
const highlighted = hljs.highlight(code, { language }).value
|
||||
const openTags: string[] = []
|
||||
|
||||
const processedHtml = highlighted.replace(/(<span [^>]+>)|(<\/span>)|(\n)/g, (match) => {
|
||||
if (match === '\n') {
|
||||
return '</span>'.repeat(openTags.length) + '\n' + openTags.join('')
|
||||
}
|
||||
|
||||
if (match === '</span>') {
|
||||
openTags.pop()
|
||||
} else {
|
||||
openTags.push(match)
|
||||
}
|
||||
|
||||
return match
|
||||
})
|
||||
|
||||
return processedHtml.split('\n')
|
||||
} catch {
|
||||
return code.split('\n')
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
import type { HLJSApi } from 'highlight.js'
|
||||
|
||||
/*
|
||||
Language: Skript
|
||||
Description: Skript language support for Minecraft server scripting
|
||||
Website: https:
|
||||
Category: scripting
|
||||
*/
|
||||
export default function (hljs: HLJSApi) {
|
||||
const CONTROL_KEYWORDS = {
|
||||
keyword: 'if else while loop return continue at stop cancel false true now',
|
||||
built_in: 'parse do',
|
||||
}
|
||||
|
||||
const ENTITIES = 'player players victim attacker sender loop-player shooter console'
|
||||
const CONDITION_OPERATORS =
|
||||
'contains has have is was were are does can cannot ' +
|
||||
"hasn't haven't isn't wasn't weren't aren't doesn't can't"
|
||||
|
||||
return {
|
||||
name: 'Skript',
|
||||
aliases: ['sk'],
|
||||
case_insensitive: true,
|
||||
keywords: CONTROL_KEYWORDS,
|
||||
contains: [
|
||||
{
|
||||
className: 'comment',
|
||||
begin: '(?<!#)#(?!#)',
|
||||
end: '$',
|
||||
relevance: 0,
|
||||
},
|
||||
{
|
||||
className: 'string',
|
||||
begin: '"',
|
||||
end: '"',
|
||||
illegal: '\\n',
|
||||
contains: [hljs.BACKSLASH_ESCAPE],
|
||||
},
|
||||
|
||||
{
|
||||
className: 'section',
|
||||
begin: '\\bon\\s+',
|
||||
end: ':',
|
||||
excludeEnd: false,
|
||||
keywords: 'on',
|
||||
relevance: 10,
|
||||
},
|
||||
|
||||
{
|
||||
className: 'section',
|
||||
begin: '\\bcommand\\s+/',
|
||||
end: ':',
|
||||
excludeEnd: false,
|
||||
relevance: 10,
|
||||
},
|
||||
|
||||
{
|
||||
className: 'variable',
|
||||
begin: '{',
|
||||
end: '}',
|
||||
contains: [
|
||||
{
|
||||
className: 'variable',
|
||||
begin: ':+',
|
||||
relevance: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
className: 'params',
|
||||
begin: '<',
|
||||
end: '>',
|
||||
relevance: 5,
|
||||
},
|
||||
|
||||
{
|
||||
className: 'function',
|
||||
begin: '\\b[a-zA-Z_][a-zA-Z0-9_]*(?=\\()',
|
||||
relevance: 0,
|
||||
},
|
||||
|
||||
{
|
||||
className: 'variable',
|
||||
begin: '\\b(loop|event)-[a-zA-Z]+\\b',
|
||||
relevance: 5,
|
||||
},
|
||||
|
||||
{
|
||||
className: 'number',
|
||||
variants: [
|
||||
{ begin: '\\b\\d+(\\.\\d+)?\\s+(tick|second|minute|hour|day)s?\\b' },
|
||||
|
||||
{ begin: '\\ba\\s+(tick|second|minute|hour|day)s?\\b' },
|
||||
|
||||
{ begin: '\\b(minecraft|mc|real|rl|irl)\\s+(tick|second|minute|hour|day)s?\\b' },
|
||||
],
|
||||
relevance: 0,
|
||||
},
|
||||
|
||||
hljs.NUMBER_MODE,
|
||||
|
||||
{
|
||||
className: 'built_in',
|
||||
begin: '\\b(' + ENTITIES + ')\\b',
|
||||
relevance: 0,
|
||||
},
|
||||
|
||||
{
|
||||
className: 'built_in',
|
||||
begin: "(uuid\\s+of|'s\\s+uuid|location\\s+of|'s\\s+location)",
|
||||
relevance: 0,
|
||||
},
|
||||
|
||||
{
|
||||
className: 'operator',
|
||||
begin: '\\b(' + CONDITION_OPERATORS.split(' ').join('|') + ')\\b',
|
||||
relevance: 0,
|
||||
},
|
||||
|
||||
{
|
||||
className: 'operator',
|
||||
begin: '::?',
|
||||
relevance: 0,
|
||||
},
|
||||
|
||||
{
|
||||
className: 'literal',
|
||||
begin: '\\b(true|false)\\b',
|
||||
relevance: 0,
|
||||
},
|
||||
|
||||
{
|
||||
className: 'keyword',
|
||||
begin: '\\b(stop|cancel|halt|enable|disable|trigger|server)\\b',
|
||||
relevance: 5,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
export * from './billing'
|
||||
export * from './highlightjs'
|
||||
export * from './licenses'
|
||||
export * from './parse'
|
||||
export * from './projects'
|
||||
export * from './servers'
|
||||
export * from './three/skin-rendering'
|
||||
|
||||
@@ -18,14 +18,9 @@
|
||||
"@codemirror/language": "^6.9.3",
|
||||
"@codemirror/state": "^6.3.2",
|
||||
"@codemirror/view": "^6.22.1",
|
||||
"@types/markdown-it": "^14.1.1",
|
||||
"@types/three": "^0.172.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"highlight.js": "^11.9.0",
|
||||
"highlightjs-mcfunction": "github:modrinth/better-highlightjs-mcfunction",
|
||||
"markdown-it": "^14.1.0",
|
||||
"ofetch": "^1.3.4",
|
||||
"three": "^0.172.0",
|
||||
"xss": "^1.0.14"
|
||||
"three": "^0.172.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import xss from 'xss'
|
||||
|
||||
// @ts-expect-error xss types don't reflect CJS default export shape
|
||||
const { escapeAttrValue, FilterXSS, safeAttrValue, whiteList } = xss
|
||||
|
||||
export const configuredXss = new FilterXSS({
|
||||
whiteList: {
|
||||
...whiteList,
|
||||
summary: [],
|
||||
h1: ['id'],
|
||||
h2: ['id'],
|
||||
h3: ['id'],
|
||||
h4: ['id'],
|
||||
h5: ['id'],
|
||||
h6: ['id'],
|
||||
kbd: ['id'],
|
||||
input: ['checked', 'disabled', 'type'],
|
||||
iframe: ['width', 'height', 'allowfullscreen', 'frameborder', 'start', 'end'],
|
||||
img: [...(whiteList.img || []), 'usemap', 'style', 'align'],
|
||||
map: ['name'],
|
||||
area: [...(whiteList.a || []), 'coords'],
|
||||
a: [...(whiteList.a || []), 'rel'],
|
||||
td: [...(whiteList.td || []), 'style'],
|
||||
th: [...(whiteList.th || []), 'style'],
|
||||
picture: [],
|
||||
source: ['media', 'sizes', 'src', 'srcset', 'type'],
|
||||
p: [...(whiteList.p || []), 'align'],
|
||||
div: [...(whiteList.p || []), 'align'],
|
||||
},
|
||||
css: {
|
||||
whiteList: {
|
||||
'image-rendering': /^pixelated$/,
|
||||
'text-align': /^center|left|right$/,
|
||||
float: /^left|right$/,
|
||||
},
|
||||
},
|
||||
onIgnoreTagAttr: (tag, name, value) => {
|
||||
// Allow iframes from acceptable sources
|
||||
if (tag === 'iframe' && name === 'src') {
|
||||
const allowedSources = [
|
||||
{
|
||||
url: /^https?:\/\/(www\.)?youtube(-nocookie)?\.com\/embed\/[a-zA-Z0-9_-]{11}/,
|
||||
allowedParameters: [/start=\d+/, /end=\d+/],
|
||||
},
|
||||
{
|
||||
url: /^https?:\/\/(www\.)?discord\.com\/widget/,
|
||||
allowedParameters: [/id=\d{18,19}/],
|
||||
},
|
||||
]
|
||||
|
||||
try {
|
||||
const url = new URL(value)
|
||||
|
||||
for (const source of allowedSources) {
|
||||
if (!source.url.test(url.href)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const newSearchParams = new URLSearchParams(url.searchParams)
|
||||
url.searchParams.forEach((value, key) => {
|
||||
if (!source.allowedParameters.some((param) => param.test(`${key}=${value}`))) {
|
||||
newSearchParams.delete(key)
|
||||
}
|
||||
})
|
||||
|
||||
url.search = newSearchParams.toString()
|
||||
return `${name}="${escapeAttrValue(url.toString())}"`
|
||||
}
|
||||
} catch {
|
||||
// ..
|
||||
}
|
||||
}
|
||||
|
||||
// For Highlight.JS
|
||||
if (name === 'class' && ['pre', 'code', 'span'].includes(tag)) {
|
||||
const allowedClasses: string[] = []
|
||||
for (const className of value.split(/\s/g)) {
|
||||
if (className.startsWith('hljs-') || className.startsWith('language-')) {
|
||||
allowedClasses.push(className)
|
||||
}
|
||||
}
|
||||
return `${name}="${escapeAttrValue(allowedClasses.join(' '))}"`
|
||||
}
|
||||
},
|
||||
safeAttrValue(tag, name, value, cssFilter) {
|
||||
if (
|
||||
(tag === 'img' || tag === 'video' || tag === 'audio' || tag === 'source') &&
|
||||
(name === 'src' || name === 'srcset' || name === 'poster') &&
|
||||
!value.startsWith('data:')
|
||||
) {
|
||||
try {
|
||||
const url = new URL(value)
|
||||
|
||||
if (url.hostname.includes('wsrv.nl')) {
|
||||
url.searchParams.delete('errorredirect')
|
||||
url.searchParams.delete('default')
|
||||
}
|
||||
|
||||
const allowedHostnames = [
|
||||
'imgur.com',
|
||||
'i.imgur.com',
|
||||
'cdn-raw.modrinth.com',
|
||||
'cdn.modrinth.com',
|
||||
'staging-cdn-raw.modrinth.com',
|
||||
'staging-cdn.modrinth.com',
|
||||
'github.com',
|
||||
'raw.githubusercontent.com',
|
||||
'img.shields.io',
|
||||
'i.postimg.cc',
|
||||
'wsrv.nl',
|
||||
'cf.way2muchnoise.eu',
|
||||
'bstats.org',
|
||||
]
|
||||
|
||||
const allowedHostnameSuffixes = ['.github.io']
|
||||
|
||||
if (
|
||||
!allowedHostnames.includes(url.hostname) &&
|
||||
!allowedHostnameSuffixes.some((suffix) => url.hostname.endsWith(suffix))
|
||||
) {
|
||||
return safeAttrValue(
|
||||
tag,
|
||||
name,
|
||||
`https://wsrv.nl/?url=${encodeURIComponent(
|
||||
url.toString().replaceAll('&', '&'),
|
||||
)}&n=-1`,
|
||||
cssFilter,
|
||||
)
|
||||
}
|
||||
return safeAttrValue(tag, name, url.toString(), cssFilter)
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
return safeAttrValue(tag, name, value, cssFilter)
|
||||
},
|
||||
})
|
||||
|
||||
export const md = (options = {}) => {
|
||||
const md = new MarkdownIt('default', {
|
||||
html: true,
|
||||
linkify: true,
|
||||
breaks: false,
|
||||
...options,
|
||||
})
|
||||
|
||||
const defaultLinkOpenRenderer =
|
||||
md.renderer.rules.link_open ||
|
||||
function (tokens, idx, options, _env, self) {
|
||||
return self.renderToken(tokens, idx, options)
|
||||
}
|
||||
|
||||
md.linkify.set({
|
||||
fuzzyLink: false,
|
||||
fuzzyIP: false,
|
||||
})
|
||||
|
||||
md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
|
||||
const token = tokens[idx]
|
||||
const index = token.attrIndex('href')
|
||||
|
||||
if (token.attrs && index !== -1) {
|
||||
const href = token.attrs[index][1]
|
||||
|
||||
try {
|
||||
const url = new URL(href)
|
||||
const allowedHostnames = ['modrinth.com']
|
||||
|
||||
if (allowedHostnames.includes(url.hostname)) {
|
||||
return defaultLinkOpenRenderer(tokens, idx, options, env, self)
|
||||
}
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
tokens[idx].attrSet('rel', 'noopener nofollow ugc')
|
||||
|
||||
return defaultLinkOpenRenderer(tokens, idx, options, env, self)
|
||||
}
|
||||
|
||||
return md
|
||||
}
|
||||
|
||||
export const renderString = (string: string) => configuredXss.process(md().render(string))
|
||||
Generated
+198
-120
@@ -125,6 +125,9 @@ importers:
|
||||
'@vueuse/core':
|
||||
specifier: ^11.1.0
|
||||
version: 11.3.0(vue@3.5.27(typescript@5.9.3))
|
||||
comark:
|
||||
specifier: ^0.3.2
|
||||
version: 0.3.2(shiki@4.0.2)
|
||||
dayjs:
|
||||
specifier: ^1.11.10
|
||||
version: 1.11.19
|
||||
@@ -320,9 +323,6 @@ importers:
|
||||
fuse.js:
|
||||
specifier: ^6.6.2
|
||||
version: 6.6.2
|
||||
highlight.js:
|
||||
specifier: ^11.7.0
|
||||
version: 11.11.1
|
||||
intl-messageformat:
|
||||
specifier: ^10.7.7
|
||||
version: 10.7.18
|
||||
@@ -341,9 +341,6 @@ importers:
|
||||
lru-cache:
|
||||
specifier: ^11.2.4
|
||||
version: 11.2.5
|
||||
markdown-it:
|
||||
specifier: 14.1.0
|
||||
version: 14.1.0
|
||||
pathe:
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2
|
||||
@@ -374,9 +371,6 @@ importers:
|
||||
vue3-apexcharts:
|
||||
specifier: ^1.5.2
|
||||
version: 1.10.0(apexcharts@4.7.0)(vue@3.5.27(typescript@5.9.3))
|
||||
xss:
|
||||
specifier: ^1.0.14
|
||||
version: 1.0.15
|
||||
devDependencies:
|
||||
'@formatjs/cli':
|
||||
specifier: ^6.2.12
|
||||
@@ -482,9 +476,9 @@ importers:
|
||||
|
||||
packages/blog:
|
||||
dependencies:
|
||||
'@modrinth/utils':
|
||||
'@modrinth/ui':
|
||||
specifier: workspace:*
|
||||
version: link:../utils
|
||||
version: link:../ui
|
||||
dayjs:
|
||||
specifier: ^1.11.10
|
||||
version: 1.11.19
|
||||
@@ -625,6 +619,9 @@ importers:
|
||||
'@codemirror/view':
|
||||
specifier: ^6.22.1
|
||||
version: 6.39.12
|
||||
'@comark/html':
|
||||
specifier: ^0.3.1
|
||||
version: 0.3.1(shiki@4.0.2)
|
||||
'@intercom/messenger-js-sdk':
|
||||
specifier: ^0.0.14
|
||||
version: 0.0.14
|
||||
@@ -634,9 +631,6 @@ importers:
|
||||
'@modrinth/assets':
|
||||
specifier: workspace:*
|
||||
version: link:../assets
|
||||
'@modrinth/blog':
|
||||
specifier: workspace:*
|
||||
version: link:../blog
|
||||
'@modrinth/utils':
|
||||
specifier: workspace:*
|
||||
version: link:../utils
|
||||
@@ -655,9 +649,6 @@ importers:
|
||||
'@types/dompurify':
|
||||
specifier: ^3.0.5
|
||||
version: 3.2.0
|
||||
'@types/markdown-it':
|
||||
specifier: ^14.1.1
|
||||
version: 14.1.2
|
||||
'@types/three':
|
||||
specifier: ^0.172.0
|
||||
version: 0.172.0
|
||||
@@ -682,6 +673,9 @@ importers:
|
||||
apexcharts:
|
||||
specifier: ^4.0.0
|
||||
version: 4.7.0
|
||||
comark:
|
||||
specifier: ^0.3.2
|
||||
version: 0.3.2(shiki@4.0.2)
|
||||
dayjs:
|
||||
specifier: ^1.11.10
|
||||
version: 1.11.19
|
||||
@@ -700,9 +694,6 @@ importers:
|
||||
fuse.js:
|
||||
specifier: ^6.6.2
|
||||
version: 6.6.2
|
||||
highlight.js:
|
||||
specifier: ^11.9.0
|
||||
version: 11.11.1
|
||||
intl-messageformat:
|
||||
specifier: ^10.7.7
|
||||
version: 10.7.18
|
||||
@@ -712,9 +703,6 @@ importers:
|
||||
lru-cache:
|
||||
specifier: ^11.2.4
|
||||
version: 11.2.5
|
||||
markdown-it:
|
||||
specifier: ^13.0.2
|
||||
version: 13.0.2
|
||||
motion-v:
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1(@vueuse/core@11.3.0(vue@3.5.27(typescript@5.9.3)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vue@3.5.27(typescript@5.9.3))
|
||||
@@ -724,6 +712,9 @@ importers:
|
||||
qrcode.vue:
|
||||
specifier: ^3.4.1
|
||||
version: 3.8.0(vue@3.5.27(typescript@5.9.3))
|
||||
shiki:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2
|
||||
three:
|
||||
specifier: ^0.172.0
|
||||
version: 0.172.0
|
||||
@@ -742,9 +733,6 @@ importers:
|
||||
vue3-apexcharts:
|
||||
specifier: ^1.5.2
|
||||
version: 1.10.0(apexcharts@4.7.0)(vue@3.5.27(typescript@5.9.3))
|
||||
xss:
|
||||
specifier: ^1.0.14
|
||||
version: 1.0.15
|
||||
devDependencies:
|
||||
'@formatjs/cli':
|
||||
specifier: ^6.2.12
|
||||
@@ -824,33 +812,18 @@ importers:
|
||||
'@codemirror/view':
|
||||
specifier: ^6.22.1
|
||||
version: 6.39.12
|
||||
'@types/markdown-it':
|
||||
specifier: ^14.1.1
|
||||
version: 14.1.2
|
||||
'@types/three':
|
||||
specifier: ^0.172.0
|
||||
version: 0.172.0
|
||||
dayjs:
|
||||
specifier: ^1.11.10
|
||||
version: 1.11.19
|
||||
highlight.js:
|
||||
specifier: ^11.9.0
|
||||
version: 11.11.1
|
||||
highlightjs-mcfunction:
|
||||
specifier: github:modrinth/better-highlightjs-mcfunction
|
||||
version: https://codeload.github.com/modrinth/better-highlightjs-mcfunction/tar.gz/aa999b763fd792ffb950d28347eeb6811c83ea8e
|
||||
markdown-it:
|
||||
specifier: ^14.1.0
|
||||
version: 14.1.0
|
||||
ofetch:
|
||||
specifier: ^1.3.4
|
||||
version: 1.5.1
|
||||
three:
|
||||
specifier: ^0.172.0
|
||||
version: 0.172.0
|
||||
xss:
|
||||
specifier: ^1.0.14
|
||||
version: 1.0.15
|
||||
devDependencies:
|
||||
'@modrinth/api-client':
|
||||
specifier: workspace:*
|
||||
@@ -1196,6 +1169,20 @@ packages:
|
||||
'@codemirror/view@6.39.12':
|
||||
resolution: {integrity: sha512-f+/VsHVn/kOA9lltk/GFzuYwVVAKmOnNjxbrhkk3tPHntFqjWeI2TbIXx006YkBkqC10wZ4NsnWXCQiFPeAISQ==}
|
||||
|
||||
'@comark/html@0.3.1':
|
||||
resolution: {integrity: sha512-vlFWZ8TMSaQ/WXwzktnZyPsm8XuE7GWEZ7ag2jXFL88Wf2ibSHvFgsoiDr9gDX9752haT4CU0Biph+5xXJ30Vg==}
|
||||
peerDependencies:
|
||||
beautiful-mermaid: '>=1.0'
|
||||
katex: '>=0.16'
|
||||
shiki: ^4.0.0
|
||||
peerDependenciesMeta:
|
||||
beautiful-mermaid:
|
||||
optional: true
|
||||
katex:
|
||||
optional: true
|
||||
shiki:
|
||||
optional: true
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -3985,36 +3972,64 @@ packages:
|
||||
'@shikijs/core@3.22.0':
|
||||
resolution: {integrity: sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==}
|
||||
|
||||
'@shikijs/core@4.0.2':
|
||||
resolution: {integrity: sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/engine-javascript@1.29.2':
|
||||
resolution: {integrity: sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==}
|
||||
|
||||
'@shikijs/engine-javascript@3.22.0':
|
||||
resolution: {integrity: sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==}
|
||||
|
||||
'@shikijs/engine-javascript@4.0.2':
|
||||
resolution: {integrity: sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/engine-oniguruma@1.29.2':
|
||||
resolution: {integrity: sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==}
|
||||
|
||||
'@shikijs/engine-oniguruma@3.22.0':
|
||||
resolution: {integrity: sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==}
|
||||
|
||||
'@shikijs/engine-oniguruma@4.0.2':
|
||||
resolution: {integrity: sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/langs@1.29.2':
|
||||
resolution: {integrity: sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==}
|
||||
|
||||
'@shikijs/langs@3.22.0':
|
||||
resolution: {integrity: sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==}
|
||||
|
||||
'@shikijs/langs@4.0.2':
|
||||
resolution: {integrity: sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/primitive@4.0.2':
|
||||
resolution: {integrity: sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/themes@1.29.2':
|
||||
resolution: {integrity: sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==}
|
||||
|
||||
'@shikijs/themes@3.22.0':
|
||||
resolution: {integrity: sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==}
|
||||
|
||||
'@shikijs/themes@4.0.2':
|
||||
resolution: {integrity: sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/types@1.29.2':
|
||||
resolution: {integrity: sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==}
|
||||
|
||||
'@shikijs/types@3.22.0':
|
||||
resolution: {integrity: sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==}
|
||||
|
||||
'@shikijs/types@4.0.2':
|
||||
resolution: {integrity: sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/vscode-textmate@10.0.2':
|
||||
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
|
||||
|
||||
@@ -4439,9 +4454,6 @@ packages:
|
||||
'@types/linkify-it@5.0.0':
|
||||
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
|
||||
|
||||
'@types/markdown-it@14.1.2':
|
||||
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
|
||||
|
||||
'@types/mdast@4.0.4':
|
||||
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
||||
|
||||
@@ -5492,6 +5504,20 @@ packages:
|
||||
colord@2.9.3:
|
||||
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
|
||||
|
||||
comark@0.3.2:
|
||||
resolution: {integrity: sha512-wZ16V3PPItX49EScg6yDswqQ24bsMAhujZUGIf1dBKMmZjqGBa4HmXDe+WZNeqNQSOd8v4AJJT1NKyrvgdf3dA==}
|
||||
peerDependencies:
|
||||
beautiful-mermaid: ^1.1.3
|
||||
katex: ^0.16.45
|
||||
shiki: ^4.0.0
|
||||
peerDependenciesMeta:
|
||||
beautiful-mermaid:
|
||||
optional: true
|
||||
katex:
|
||||
optional: true
|
||||
shiki:
|
||||
optional: true
|
||||
|
||||
comma-separated-tokens@2.0.3:
|
||||
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
||||
|
||||
@@ -5646,9 +5672,6 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
cssfilter@0.0.10:
|
||||
resolution: {integrity: sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==}
|
||||
|
||||
cssnano-preset-default@7.0.10:
|
||||
resolution: {integrity: sha512-6ZBjW0Lf1K1Z+0OKUAUpEN62tSXmYChXWi2NAA0afxEVsj9a+MbcB1l5qel6BHJHmULai2fCGRthCeKSFbScpA==}
|
||||
engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
|
||||
@@ -5806,19 +5829,35 @@ packages:
|
||||
dom-serializer@2.0.0:
|
||||
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
|
||||
|
||||
dom-serializer@3.1.1:
|
||||
resolution: {integrity: sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
domelementtype@2.3.0:
|
||||
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
||||
|
||||
domelementtype@3.0.0:
|
||||
resolution: {integrity: sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
domhandler@5.0.3:
|
||||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
domhandler@6.0.1:
|
||||
resolution: {integrity: sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
dompurify@3.3.1:
|
||||
resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==}
|
||||
|
||||
domutils@3.2.2:
|
||||
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
||||
|
||||
domutils@4.0.2:
|
||||
resolution: {integrity: sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
dot-case@3.0.4:
|
||||
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
|
||||
|
||||
@@ -5895,10 +5934,6 @@ packages:
|
||||
entities@2.2.0:
|
||||
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
|
||||
|
||||
entities@3.0.1:
|
||||
resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
@@ -5911,6 +5946,10 @@ packages:
|
||||
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
entities@8.0.0:
|
||||
resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
error-ex@1.3.4:
|
||||
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
|
||||
|
||||
@@ -6564,14 +6603,6 @@ packages:
|
||||
hey-listen@1.0.8:
|
||||
resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==}
|
||||
|
||||
highlight.js@11.11.1:
|
||||
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
highlightjs-mcfunction@https://codeload.github.com/modrinth/better-highlightjs-mcfunction/tar.gz/aa999b763fd792ffb950d28347eeb6811c83ea8e:
|
||||
resolution: {tarball: https://codeload.github.com/modrinth/better-highlightjs-mcfunction/tar.gz/aa999b763fd792ffb950d28347eeb6811c83ea8e}
|
||||
version: 1.0.0
|
||||
|
||||
hookable@5.5.3:
|
||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
|
||||
@@ -6599,6 +6630,10 @@ packages:
|
||||
html-whitespace-sensitive-tag-names@3.0.1:
|
||||
resolution: {integrity: sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==}
|
||||
|
||||
htmlparser2@12.0.0:
|
||||
resolution: {integrity: sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
htmlparser2@8.0.2:
|
||||
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
||||
|
||||
@@ -7150,9 +7185,6 @@ packages:
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
linkify-it@4.0.1:
|
||||
resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==}
|
||||
|
||||
linkify-it@5.0.0:
|
||||
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
|
||||
|
||||
@@ -7247,18 +7279,13 @@ packages:
|
||||
magicast@0.5.1:
|
||||
resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==}
|
||||
|
||||
markdown-exit@1.0.0-beta.9:
|
||||
resolution: {integrity: sha512-5tzrMKMF367amyBly131vm6eGuWRL2DjBqWaFmPzPbLyuxP0XOmyyyroOAIXuBAMF/3kZbbfqOxvW/SotqKqbQ==}
|
||||
|
||||
markdown-extensions@2.0.0:
|
||||
resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
markdown-it@13.0.2:
|
||||
resolution: {integrity: sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==}
|
||||
hasBin: true
|
||||
|
||||
markdown-it@14.1.0:
|
||||
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
|
||||
hasBin: true
|
||||
|
||||
markdown-table@3.0.4:
|
||||
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
|
||||
|
||||
@@ -7334,9 +7361,6 @@ packages:
|
||||
mdn-data@2.12.2:
|
||||
resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
|
||||
|
||||
mdurl@1.0.1:
|
||||
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
|
||||
|
||||
mdurl@2.0.0:
|
||||
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
||||
|
||||
@@ -8790,6 +8814,10 @@ packages:
|
||||
shiki@3.22.0:
|
||||
resolution: {integrity: sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==}
|
||||
|
||||
shiki@4.0.2:
|
||||
resolution: {integrity: sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
side-channel-list@1.0.0:
|
||||
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -9277,9 +9305,6 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
uc.micro@1.0.6:
|
||||
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
|
||||
|
||||
uc.micro@2.1.0:
|
||||
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||
|
||||
@@ -10092,11 +10117,6 @@ packages:
|
||||
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
||||
xss@1.0.15:
|
||||
resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
hasBin: true
|
||||
|
||||
xtend@4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
@@ -10667,6 +10687,12 @@ snapshots:
|
||||
style-mod: 4.1.3
|
||||
w3c-keyname: 2.2.8
|
||||
|
||||
'@comark/html@0.3.1(shiki@4.0.2)':
|
||||
dependencies:
|
||||
comark: 0.3.2(shiki@4.0.2)
|
||||
optionalDependencies:
|
||||
shiki: 4.0.2
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
@@ -13169,6 +13195,14 @@ snapshots:
|
||||
'@types/hast': 3.0.4
|
||||
hast-util-to-html: 9.0.5
|
||||
|
||||
'@shikijs/core@4.0.2':
|
||||
dependencies:
|
||||
'@shikijs/primitive': 4.0.2
|
||||
'@shikijs/types': 4.0.2
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
hast-util-to-html: 9.0.5
|
||||
|
||||
'@shikijs/engine-javascript@1.29.2':
|
||||
dependencies:
|
||||
'@shikijs/types': 1.29.2
|
||||
@@ -13181,6 +13215,12 @@ snapshots:
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
oniguruma-to-es: 4.3.4
|
||||
|
||||
'@shikijs/engine-javascript@4.0.2':
|
||||
dependencies:
|
||||
'@shikijs/types': 4.0.2
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
oniguruma-to-es: 4.3.4
|
||||
|
||||
'@shikijs/engine-oniguruma@1.29.2':
|
||||
dependencies:
|
||||
'@shikijs/types': 1.29.2
|
||||
@@ -13191,6 +13231,11 @@ snapshots:
|
||||
'@shikijs/types': 3.22.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
|
||||
'@shikijs/engine-oniguruma@4.0.2':
|
||||
dependencies:
|
||||
'@shikijs/types': 4.0.2
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
|
||||
'@shikijs/langs@1.29.2':
|
||||
dependencies:
|
||||
'@shikijs/types': 1.29.2
|
||||
@@ -13199,6 +13244,16 @@ snapshots:
|
||||
dependencies:
|
||||
'@shikijs/types': 3.22.0
|
||||
|
||||
'@shikijs/langs@4.0.2':
|
||||
dependencies:
|
||||
'@shikijs/types': 4.0.2
|
||||
|
||||
'@shikijs/primitive@4.0.2':
|
||||
dependencies:
|
||||
'@shikijs/types': 4.0.2
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
'@shikijs/themes@1.29.2':
|
||||
dependencies:
|
||||
'@shikijs/types': 1.29.2
|
||||
@@ -13207,6 +13262,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@shikijs/types': 3.22.0
|
||||
|
||||
'@shikijs/themes@4.0.2':
|
||||
dependencies:
|
||||
'@shikijs/types': 4.0.2
|
||||
|
||||
'@shikijs/types@1.29.2':
|
||||
dependencies:
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
@@ -13217,6 +13276,11 @@ snapshots:
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
'@shikijs/types@4.0.2':
|
||||
dependencies:
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
'@shikijs/vscode-textmate@10.0.2': {}
|
||||
|
||||
'@sindresorhus/is@7.2.0': {}
|
||||
@@ -13622,11 +13686,6 @@ snapshots:
|
||||
|
||||
'@types/linkify-it@5.0.0': {}
|
||||
|
||||
'@types/markdown-it@14.1.2':
|
||||
dependencies:
|
||||
'@types/linkify-it': 5.0.0
|
||||
'@types/mdurl': 2.0.0
|
||||
|
||||
'@types/mdast@4.0.4':
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
@@ -14961,6 +15020,15 @@ snapshots:
|
||||
|
||||
colord@2.9.3: {}
|
||||
|
||||
comark@0.3.2(shiki@4.0.2):
|
||||
dependencies:
|
||||
entities: 8.0.0
|
||||
htmlparser2: 12.0.0
|
||||
js-yaml: 4.1.1
|
||||
markdown-exit: 1.0.0-beta.9
|
||||
optionalDependencies:
|
||||
shiki: 4.0.2
|
||||
|
||||
comma-separated-tokens@2.0.3: {}
|
||||
|
||||
commander@10.0.1: {}
|
||||
@@ -15097,8 +15165,6 @@ snapshots:
|
||||
|
||||
cssesc@3.0.0: {}
|
||||
|
||||
cssfilter@0.0.10: {}
|
||||
|
||||
cssnano-preset-default@7.0.10(postcss@8.5.6):
|
||||
dependencies:
|
||||
browserslist: 4.28.1
|
||||
@@ -15226,12 +15292,24 @@ snapshots:
|
||||
domhandler: 5.0.3
|
||||
entities: 4.5.0
|
||||
|
||||
dom-serializer@3.1.1:
|
||||
dependencies:
|
||||
domelementtype: 3.0.0
|
||||
domhandler: 6.0.1
|
||||
entities: 8.0.0
|
||||
|
||||
domelementtype@2.3.0: {}
|
||||
|
||||
domelementtype@3.0.0: {}
|
||||
|
||||
domhandler@5.0.3:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
domhandler@6.0.1:
|
||||
dependencies:
|
||||
domelementtype: 3.0.0
|
||||
|
||||
dompurify@3.3.1:
|
||||
optionalDependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
@@ -15242,6 +15320,12 @@ snapshots:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
|
||||
domutils@4.0.2:
|
||||
dependencies:
|
||||
dom-serializer: 3.1.1
|
||||
domelementtype: 3.0.0
|
||||
domhandler: 6.0.1
|
||||
|
||||
dot-case@3.0.4:
|
||||
dependencies:
|
||||
no-case: 3.0.4
|
||||
@@ -15308,14 +15392,14 @@ snapshots:
|
||||
|
||||
entities@2.2.0: {}
|
||||
|
||||
entities@3.0.1: {}
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
entities@6.0.1: {}
|
||||
|
||||
entities@7.0.1: {}
|
||||
|
||||
entities@8.0.0: {}
|
||||
|
||||
error-ex@1.3.4:
|
||||
dependencies:
|
||||
is-arrayish: 0.2.1
|
||||
@@ -16414,10 +16498,6 @@ snapshots:
|
||||
|
||||
hey-listen@1.0.8: {}
|
||||
|
||||
highlight.js@11.11.1: {}
|
||||
|
||||
highlightjs-mcfunction@https://codeload.github.com/modrinth/better-highlightjs-mcfunction/tar.gz/aa999b763fd792ffb950d28347eeb6811c83ea8e: {}
|
||||
|
||||
hookable@5.5.3: {}
|
||||
|
||||
hookable@6.0.1: {}
|
||||
@@ -16448,6 +16528,13 @@ snapshots:
|
||||
|
||||
html-whitespace-sensitive-tag-names@3.0.1: {}
|
||||
|
||||
htmlparser2@12.0.0:
|
||||
dependencies:
|
||||
domelementtype: 3.0.0
|
||||
domhandler: 6.0.1
|
||||
domutils: 4.0.2
|
||||
entities: 8.0.0
|
||||
|
||||
htmlparser2@8.0.2:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
@@ -16908,10 +16995,6 @@ snapshots:
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
linkify-it@4.0.1:
|
||||
dependencies:
|
||||
uc.micro: 1.0.6
|
||||
|
||||
linkify-it@5.0.0:
|
||||
dependencies:
|
||||
uc.micro: 2.1.0
|
||||
@@ -17022,25 +17105,18 @@ snapshots:
|
||||
'@babel/types': 7.29.0
|
||||
source-map-js: 1.2.1
|
||||
|
||||
markdown-extensions@2.0.0: {}
|
||||
|
||||
markdown-it@13.0.2:
|
||||
markdown-exit@1.0.0-beta.9:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
entities: 3.0.1
|
||||
linkify-it: 4.0.1
|
||||
mdurl: 1.0.1
|
||||
uc.micro: 1.0.6
|
||||
|
||||
markdown-it@14.1.0:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
entities: 4.5.0
|
||||
'@types/linkify-it': 5.0.0
|
||||
'@types/mdurl': 2.0.0
|
||||
entities: 7.0.1
|
||||
linkify-it: 5.0.0
|
||||
mdurl: 2.0.0
|
||||
punycode.js: 2.3.1
|
||||
uc.micro: 2.1.0
|
||||
|
||||
markdown-extensions@2.0.0: {}
|
||||
|
||||
markdown-table@3.0.4: {}
|
||||
|
||||
marked@7.0.4: {}
|
||||
@@ -17236,8 +17312,6 @@ snapshots:
|
||||
|
||||
mdn-data@2.12.2: {}
|
||||
|
||||
mdurl@1.0.1: {}
|
||||
|
||||
mdurl@2.0.0: {}
|
||||
|
||||
merge-stream@2.0.0: {}
|
||||
@@ -19249,6 +19323,17 @@ snapshots:
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
shiki@4.0.2:
|
||||
dependencies:
|
||||
'@shikijs/core': 4.0.2
|
||||
'@shikijs/engine-javascript': 4.0.2
|
||||
'@shikijs/engine-oniguruma': 4.0.2
|
||||
'@shikijs/langs': 4.0.2
|
||||
'@shikijs/themes': 4.0.2
|
||||
'@shikijs/types': 4.0.2
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
side-channel-list@1.0.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -19755,8 +19840,6 @@ snapshots:
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
uc.micro@1.0.6: {}
|
||||
|
||||
uc.micro@2.1.0: {}
|
||||
|
||||
ufo@1.6.3: {}
|
||||
@@ -20563,11 +20646,6 @@ snapshots:
|
||||
|
||||
xmlbuilder@11.0.1: {}
|
||||
|
||||
xss@1.0.15:
|
||||
dependencies:
|
||||
commander: 2.20.3
|
||||
cssfilter: 0.0.10
|
||||
|
||||
xtend@4.0.2: {}
|
||||
|
||||
xxhash-wasm@1.1.0: {}
|
||||
|
||||
Reference in New Issue
Block a user