From c8cb3c4affcf5999afe22a622649334318488f4e Mon Sep 17 00:00:00 2001 From: Infi Date: Thu, 2 Jan 2025 03:51:38 +0100 Subject: [PATCH] feat(jbm): support for katex and tables Signed-off-by: Infi --- app/src/main/assets/katex/katex.html | 54 ++++ app/src/main/assets/markdown/markdown.html | 206 ++++++++++++++ .../revolt/markdown/jbm/FallbackRenderer.kt | 159 +++++++++++ .../chat/revolt/markdown/jbm/JBMRenderer.kt | 76 ++++-- .../chat/revolt/markdown/jbm/KatexRenderer.kt | 134 +++++++++ .../revolt/markdown/jbm/MentionResolver.kt | 32 +++ scripts/download_deps.ts | 254 +++++++++++++++++- 7 files changed, 888 insertions(+), 27 deletions(-) create mode 100644 app/src/main/assets/katex/katex.html create mode 100644 app/src/main/assets/markdown/markdown.html create mode 100644 app/src/main/java/chat/revolt/markdown/jbm/FallbackRenderer.kt create mode 100644 app/src/main/java/chat/revolt/markdown/jbm/KatexRenderer.kt create mode 100644 app/src/main/java/chat/revolt/markdown/jbm/MentionResolver.kt diff --git a/app/src/main/assets/katex/katex.html b/app/src/main/assets/katex/katex.html new file mode 100644 index 00000000..ac5baaca --- /dev/null +++ b/app/src/main/assets/katex/katex.html @@ -0,0 +1,54 @@ + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/app/src/main/assets/markdown/markdown.html b/app/src/main/assets/markdown/markdown.html new file mode 100644 index 00000000..a4f2794b --- /dev/null +++ b/app/src/main/assets/markdown/markdown.html @@ -0,0 +1,206 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/markdown/jbm/FallbackRenderer.kt b/app/src/main/java/chat/revolt/markdown/jbm/FallbackRenderer.kt new file mode 100644 index 00000000..5d0e287a --- /dev/null +++ b/app/src/main/java/chat/revolt/markdown/jbm/FallbackRenderer.kt @@ -0,0 +1,159 @@ +import android.annotation.SuppressLint +import android.content.Intent +import android.net.Uri +import android.webkit.JavascriptInterface +import android.webkit.WebChromeClient +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.browser.customtabs.CustomTabColorSchemeParams +import androidx.browser.customtabs.CustomTabsIntent +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.viewinterop.AndroidView +import androidx.webkit.WebViewAssetLoader +import chat.revolt.activities.InviteActivity +import chat.revolt.api.REVOLT_APP +import chat.revolt.api.REVOLT_FILES +import chat.revolt.api.RevoltAPI +import chat.revolt.api.internals.ResourceLocations +import chat.revolt.markdown.jbm.LocalJBMarkdownTreeState +import chat.revolt.markdown.jbm.MentionResolver +import chat.revolt.markdown.jbm.asHexString + +@SuppressLint("SetJavaScriptEnabled") +@Composable +fun FallbackRenderer(content: String, modifier: Modifier = Modifier) { + val colors = MaterialTheme.colorScheme + val mdState = LocalJBMarkdownTreeState.current + + AndroidView( + modifier = modifier, + factory = { context -> + WebView(context).apply { + val assetLoader = WebViewAssetLoader.Builder() + .setDomain(Uri.parse(REVOLT_APP).host!!) + .addPathHandler( + "/_android_assets/", + WebViewAssetLoader.AssetsPathHandler(context) + ) + .addPathHandler( + "/_android_res/", + WebViewAssetLoader.ResourcesPathHandler(context) + ) + .build() + + webChromeClient = object : WebChromeClient() {} + webViewClient = object : WebViewClient() { + override fun shouldInterceptRequest( + view: WebView?, + request: WebResourceRequest? + ): WebResourceResponse? { + return request?.let { assetLoader.shouldInterceptRequest(it.url) } + } + + override fun shouldOverrideUrlLoading( + view: WebView?, + webResourceRequest: WebResourceRequest + ): Boolean { + // Capture clicks on invite links + if (webResourceRequest.url.host == "rvlt.gg" || + ( + webResourceRequest.url.host?.endsWith("revolt.chat") == true && webResourceRequest.url.path?.startsWith( + "/invite" + ) == true + ) + ) { + val intent = Intent( + context, + InviteActivity::class.java + ).setAction(Intent.ACTION_VIEW) + + intent.data = webResourceRequest.url + context.startActivity(intent) + + return true + } + + // Otherwise, open the link in the browser using androidx.browser + val customTab = CustomTabsIntent.Builder() + .setShowTitle(true) + .setDefaultColorSchemeParams( + CustomTabColorSchemeParams.Builder() + .setToolbarColor(colors.background.toArgb()) + .build() + ) + .build() + customTab.launchUrl(context, webResourceRequest.url) + + // Prevent the WebView from navigating to the URL + return true + } + } + + settings.apply { + javaScriptEnabled = true + setSupportZoom(false) + setSupportMultipleWindows(false) + isVerticalScrollBarEnabled = false + isHorizontalScrollBarEnabled = false + cacheMode = WebSettings.LOAD_NO_CACHE + } + + addJavascriptInterface( + object { + @JavascriptInterface + fun getSource(): String { + return content + } + + @JavascriptInterface + fun getForegroundColour(): String { + return colors.onBackground.asHexString() + } + + @JavascriptInterface + fun getPrimaryColour(): String { + return colors.primary.asHexString() + } + + @JavascriptInterface + fun getMentionBackgroundColour(): String { + return colors.primary.copy(alpha = 0.2f).asHexString() + } + + @JavascriptInterface + fun getCustomEmoteUrl(emoteId: String): String { + return "$REVOLT_FILES/emojis/$emoteId/original" + } + + @JavascriptInterface + fun resolveUserMention(userId: String): String { + return MentionResolver.resolveUser(userId, mdState.currentServer) + } + + @JavascriptInterface + fun userAvatar(userId: String): String { + return ResourceLocations.userAvatarUrl(RevoltAPI.userCache[userId]) + } + + @JavascriptInterface + fun resolveChannelMention(channelId: String): String { + return MentionResolver.resolveChannel(channelId) + } + }, + "Bridge" + ) + setBackgroundColor(android.graphics.Color.TRANSPARENT) + + loadUrl( + "$REVOLT_APP/_android_assets/markdown/markdown.html" + ) + } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/markdown/jbm/JBMRenderer.kt b/app/src/main/java/chat/revolt/markdown/jbm/JBMRenderer.kt index ff2a942f..87c169b9 100644 --- a/app/src/main/java/chat/revolt/markdown/jbm/JBMRenderer.kt +++ b/app/src/main/java/chat/revolt/markdown/jbm/JBMRenderer.kt @@ -1,5 +1,6 @@ package chat.revolt.markdown.jbm +import FallbackRenderer import android.content.Intent import android.content.res.Configuration import android.util.Log @@ -22,6 +23,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -89,7 +91,6 @@ import dev.snipme.highlights.model.ColorHighlight import dev.snipme.highlights.model.SyntaxLanguage import dev.snipme.highlights.model.SyntaxThemes import kotlinx.coroutines.launch -import logcat.logcat import org.intellij.markdown.MarkdownElementTypes import org.intellij.markdown.MarkdownTokenTypes import org.intellij.markdown.ast.ASTNode @@ -205,18 +206,11 @@ private fun annotateText( ) ) - val member = state.currentServer?.let { serverId -> - RevoltAPI.members.getMember(serverId, userId) - } - val mentionDisplay = member?.nickname - ?: RevoltAPI.userCache[userId]?.username - ?: "<@$userId>" - - appendInlineContent(JBMAnnotations.JBMBackgroundRoundingStart.tag) + append(" ") appendInlineContent(JBMAnnotations.UserAvatar.tag, userId) append(" ") - append(mentionDisplay) - appendInlineContent(JBMAnnotations.JBMBackgroundRoundingEnd.tag) + append(MentionResolver.resolveUser(userId, state.currentServer)) + append(" ") pop() pop() @@ -243,13 +237,7 @@ private fun annotateText( ) ) - val channel = RevoltAPI.channelCache[channelId] - val mentionDisplay = channel?.name?.let { name -> "#$name" } - ?: "<#$channelId>" - - appendInlineContent(JBMAnnotations.JBMBackgroundRoundingStart.tag) - append(mentionDisplay) - appendInlineContent(JBMAnnotations.JBMBackgroundRoundingEnd.tag) + append(MentionResolver.resolveChannel(channelId)) pop() pop() @@ -950,12 +938,17 @@ private fun JBMBlock(node: ASTNode, modifier: Modifier, nestingCounter: Int = 0) MarkdownElementTypes.HTML_BLOCK, MarkdownElementTypes.LINK_DEFINITION, MarkdownTokenTypes.WHITE_SPACE -> { - CompositionLocalProvider( - LocalTextStyle provides LocalTextStyle.current.copy( - fontSize = LocalTextStyle.current.fontSize * state.fontSizeMultiplier - ) - ) { - JBMText(node, modifier) + // If the only child is a BLOCK_MATH we render it instead + if (node.children.size == 1 && node.children[0].type == GFMElementTypes.BLOCK_MATH) { + JBMBlock(node.children[0], modifier) + } else { + CompositionLocalProvider( + LocalTextStyle provides LocalTextStyle.current.copy( + fontSize = LocalTextStyle.current.fontSize * state.fontSizeMultiplier + ) + ) { + JBMText(node, modifier) + } } } @@ -1074,6 +1067,41 @@ private fun JBMBlock(node: ASTNode, modifier: Modifier, nestingCounter: Int = 0) } } + GFMElementTypes.BLOCK_MATH -> { + // No use using Katex in embedded because we don't want to + // create WebViews when embedded + if (!LocalJBMarkdownTreeState.current.embedded) { + val mathContent = + try { + node.getTextInNode(state.sourceText).toString().removeSurrounding("$$") + } catch (e: Exception) { + "" + } + KatexRenderer(mathContent, modifier) + } + } + + GFMElementTypes.TABLE -> { + // Dito BLOCK_MATH + if (!LocalJBMarkdownTreeState.current.embedded) { + val tableContent = try { + node.getTextInNode(state.sourceText).toString() + } catch (e: Exception) { + "" + } + + FallbackRenderer(tableContent, modifier) + } + } + + MarkdownTokenTypes.HORIZONTAL_RULE -> { + HorizontalDivider( + color = colorScheme.onSurface, + thickness = 1.dp, + modifier = modifier.padding(vertical = 8.dp) + ) + } + else -> { Text( text = buildAnnotatedString { diff --git a/app/src/main/java/chat/revolt/markdown/jbm/KatexRenderer.kt b/app/src/main/java/chat/revolt/markdown/jbm/KatexRenderer.kt new file mode 100644 index 00000000..0ee336db --- /dev/null +++ b/app/src/main/java/chat/revolt/markdown/jbm/KatexRenderer.kt @@ -0,0 +1,134 @@ +package chat.revolt.markdown.jbm + +import android.annotation.SuppressLint +import android.content.Intent +import android.net.Uri +import android.webkit.JavascriptInterface +import android.webkit.WebChromeClient +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.browser.customtabs.CustomTabColorSchemeParams +import androidx.browser.customtabs.CustomTabsIntent +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.viewinterop.AndroidView +import androidx.webkit.WebViewAssetLoader +import chat.revolt.activities.InviteActivity +import chat.revolt.api.REVOLT_APP + +internal fun Color.asHexString(): String { + val argb = toArgb() + val alpha = (argb shr 24 and 0xff) / 255.0f + val red = argb shr 16 and 0xff + val green = argb shr 8 and 0xff + val blue = argb and 0xff + return String.format("#%02x%02x%02x%02x", red, green, blue, (alpha * 255).toInt()) +} + +@SuppressLint("SetJavaScriptEnabled") +@Composable +fun KatexRenderer(content: String, modifier: Modifier = Modifier) { + val colors = MaterialTheme.colorScheme + + AndroidView( + modifier = modifier, + factory = { context -> + WebView(context).apply { + val assetLoader = WebViewAssetLoader.Builder() + .setDomain(Uri.parse(REVOLT_APP).host!!) + .addPathHandler( + "/_android_assets/", + WebViewAssetLoader.AssetsPathHandler(context) + ) + .addPathHandler( + "/_android_res/", + WebViewAssetLoader.ResourcesPathHandler(context) + ) + .build() + + webChromeClient = object : WebChromeClient() {} + webViewClient = object : WebViewClient() { + override fun shouldInterceptRequest( + view: WebView?, + request: WebResourceRequest? + ): WebResourceResponse? { + return request?.let { assetLoader.shouldInterceptRequest(it.url) } + } + + override fun shouldOverrideUrlLoading( + view: WebView?, + webResourceRequest: WebResourceRequest + ): Boolean { + // Capture clicks on invite links + if (webResourceRequest.url.host == "rvlt.gg" || + ( + webResourceRequest.url.host?.endsWith("revolt.chat") == true && webResourceRequest.url.path?.startsWith( + "/invite" + ) == true + ) + ) { + val intent = Intent( + context, + InviteActivity::class.java + ).setAction(Intent.ACTION_VIEW) + + intent.data = webResourceRequest.url + context.startActivity(intent) + + return true + } + + // Otherwise, open the link in the browser using androidx.browser + val customTab = CustomTabsIntent.Builder() + .setShowTitle(true) + .setDefaultColorSchemeParams( + CustomTabColorSchemeParams.Builder() + .setToolbarColor(colors.background.toArgb()) + .build() + ) + .build() + customTab.launchUrl(context, webResourceRequest.url) + + // Prevent the WebView from navigating to the URL + return true + } + } + + settings.apply { + javaScriptEnabled = true + setSupportZoom(false) + setSupportMultipleWindows(false) + isVerticalScrollBarEnabled = false + isHorizontalScrollBarEnabled = false + cacheMode = WebSettings.LOAD_NO_CACHE + } + + addJavascriptInterface( + object { + @JavascriptInterface + fun getSource(): String { + return content + } + + @JavascriptInterface + fun getForegroundColour(): String { + return colors.onBackground.asHexString() + } + }, + "Bridge" + ) + setBackgroundColor(android.graphics.Color.TRANSPARENT) + + loadUrl( + "$REVOLT_APP/_android_assets/katex/katex.html" + ) + } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/markdown/jbm/MentionResolver.kt b/app/src/main/java/chat/revolt/markdown/jbm/MentionResolver.kt new file mode 100644 index 00000000..1874863a --- /dev/null +++ b/app/src/main/java/chat/revolt/markdown/jbm/MentionResolver.kt @@ -0,0 +1,32 @@ +package chat.revolt.markdown.jbm + +import chat.revolt.api.RevoltAPI + +object MentionResolver { + /** + * Resolves a user mention to its fancy representation. + * Note that this uses the new format without a leading @ unless the user is not found. + * + * @param userId The user ID to resolve. + * @param serverId The server ID to resolve the user in. + * @return The resolved user mention. + */ + fun resolveUser(userId: String, serverId: String? = null): String { + val maybeMember = serverId?.let { RevoltAPI.members.getMember(serverId, userId) } + return maybeMember?.nickname + ?: RevoltAPI.userCache[userId]?.username + ?: "<@$userId>" + } + + /** + * Resolves a channel mention to its fancy representation. + * + * @param channelId The channel ID to resolve. + * @param serverId The server ID to resolve the channel in. + * @return The resolved channel mention. + */ + fun resolveChannel(channelId: String): String { + val channel = RevoltAPI.channelCache[channelId] + return channel?.name?.let { name -> "#$name" } ?: "<#$channelId>" + } +} \ No newline at end of file diff --git a/scripts/download_deps.ts b/scripts/download_deps.ts index bba8c760..6476353b 100644 --- a/scripts/download_deps.ts +++ b/scripts/download_deps.ts @@ -46,20 +46,268 @@ const deps = [ url: "https://cdn.jsdelivr.net/npm/katex@0.16.19/dist/katex.min.js", }, { - file: "micromark.bundle.js", - url: "https://esm.sh/v135/micromark@4.0.1/es2022/micromark.bundle.mjs", + file: "fonts/KaTeX_AMS-Regular.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_AMS-Regular.ttf", }, + { + file: "fonts/KaTeX_AMS-Regular.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_AMS-Regular.woff", + }, + { + file: "fonts/KaTeX_AMS-Regular.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_AMS-Regular.woff2", + }, + { + file: "fonts/KaTeX_Caligraphic-Bold.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Caligraphic-Bold.ttf", + }, + { + file: "fonts/KaTeX_Caligraphic-Bold.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Caligraphic-Bold.woff", + }, + { + file: "fonts/KaTeX_Caligraphic-Bold.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Caligraphic-Bold.woff2", + }, + { + file: "fonts/KaTeX_Caligraphic-Regular.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Caligraphic-Regular.ttf", + }, + { + file: "fonts/KaTeX_Caligraphic-Regular.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Caligraphic-Regular.woff", + }, + { + file: "fonts/KaTeX_Caligraphic-Regular.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Caligraphic-Regular.woff2", + }, + { + file: "fonts/KaTeX_Fraktur-Bold.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Fraktur-Bold.ttf", + }, + { + file: "fonts/KaTeX_Fraktur-Bold.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Fraktur-Bold.woff", + }, + { + file: "fonts/KaTeX_Fraktur-Bold.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Fraktur-Bold.woff2", + }, + { + file: "fonts/KaTeX_Fraktur-Regular.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Fraktur-Regular.ttf", + }, + { + file: "fonts/KaTeX_Fraktur-Regular.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Fraktur-Regular.woff", + }, + { + file: "fonts/KaTeX_Fraktur-Regular.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Fraktur-Regular.woff2", + }, + { + file: "fonts/KaTeX_Main-Bold.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Main-Bold.ttf", + }, + { + file: "fonts/KaTeX_Main-Bold.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Main-Bold.woff", + }, + { + file: "fonts/KaTeX_Main-Bold.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Main-Bold.woff2", + }, + { + file: "fonts/KaTeX_Main-BoldItalic.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Main-BoldItalic.ttf", + }, + { + file: "fonts/KaTeX_Main-BoldItalic.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Main-BoldItalic.woff", + }, + { + file: "fonts/KaTeX_Main-BoldItalic.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Main-BoldItalic.woff2", + }, + { + file: "fonts/KaTeX_Main-Italic.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Main-Italic.ttf", + }, + { + file: "fonts/KaTeX_Main-Italic.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Main-Italic.woff", + }, + { + file: "fonts/KaTeX_Main-Italic.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Main-Italic.woff2", + }, + { + file: "fonts/KaTeX_Main-Regular.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Main-Regular.ttf", + }, + { + file: "fonts/KaTeX_Main-Regular.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Main-Regular.woff", + }, + { + file: "fonts/KaTeX_Main-Regular.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Main-Regular.woff2", + }, + { + file: "fonts/KaTeX_Math-BoldItalic.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Math-BoldItalic.ttf", + }, + { + file: "fonts/KaTeX_Math-BoldItalic.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Math-BoldItalic.woff", + }, + { + file: "fonts/KaTeX_Math-BoldItalic.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Math-BoldItalic.woff2", + }, + { + file: "fonts/KaTeX_Math-Italic.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Math-Italic.ttf", + }, + { + file: "fonts/KaTeX_Math-Italic.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Math-Italic.woff", + }, + { + file: "fonts/KaTeX_Math-Italic.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Math-Italic.woff2", + }, + { + file: "fonts/KaTeX_SansSerif-Bold.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_SansSerif-Bold.ttf", + }, + { + file: "fonts/KaTeX_SansSerif-Bold.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_SansSerif-Bold.woff", + }, + { + file: "fonts/KaTeX_SansSerif-Bold.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_SansSerif-Bold.woff2", + }, + { + file: "fonts/KaTeX_SansSerif-Italic.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_SansSerif-Italic.ttf", + }, + { + file: "fonts/KaTeX_SansSerif-Italic.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_SansSerif-Italic.woff", + }, + { + file: "fonts/KaTeX_SansSerif-Italic.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_SansSerif-Italic.woff2", + }, + { + file: "fonts/KaTeX_SansSerif-Regular.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_SansSerif-Regular.ttf", + }, + { + file: "fonts/KaTeX_SansSerif-Regular.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_SansSerif-Regular.woff", + }, + { + file: "fonts/KaTeX_SansSerif-Regular.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_SansSerif-Regular.woff2", + }, + { + file: "fonts/KaTeX_Script-Regular.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Script-Regular.ttf", + }, + { + file: "fonts/KaTeX_Script-Regular.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Script-Regular.woff", + }, + { + file: "fonts/KaTeX_Script-Regular.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Script-Regular.woff2", + }, + { + file: "fonts/KaTeX_Size1-Regular.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Size1-Regular.ttf", + }, + { + file: "fonts/KaTeX_Size1-Regular.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Size1-Regular.woff", + }, + { + file: "fonts/KaTeX_Size1-Regular.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Size1-Regular.woff2", + }, + { + file: "fonts/KaTeX_Size2-Regular.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Size2-Regular.ttf", + }, + { + file: "fonts/KaTeX_Size2-Regular.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Size2-Regular.woff", + }, + { + file: "fonts/KaTeX_Size2-Regular.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Size2-Regular.woff2", + }, + { + file: "fonts/KaTeX_Size3-Regular.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Size3-Regular.ttf", + }, + { + file: "fonts/KaTeX_Size3-Regular.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Size3-Regular.woff", + }, + { + file: "fonts/KaTeX_Size3-Regular.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Size3-Regular.woff2", + }, + { + file: "fonts/KaTeX_Size4-Regular.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Size4-Regular.ttf", + }, + { + file: "fonts/KaTeX_Size4-Regular.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Size4-Regular.woff", + }, + { + file: "fonts/KaTeX_Size4-Regular.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Size4-Regular.woff2", + }, + { + file: "fonts/KaTeX_Typewriter-Regular.ttf", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Typewriter-Regular.ttf", + }, + { + file: "fonts/KaTeX_Typewriter-Regular.woff", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Typewriter-Regular.woff", + }, + { + file: "fonts/KaTeX_Typewriter-Regular.woff2", + url: "https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/fonts/KaTeX_Typewriter-Regular.woff2", + }, + { + file: "micromark.bundle.js", + url: "https://esm.sh/v135/micromark@3.2.0/es2022/micromark.bundle.mjs", + }, + { + file: "micromark-gfm.bundle.js", + url: "https://esm.sh/v135/micromark-extension-gfm@3.0.0/es2022/micromark-extension-gfm.bundle.mjs", + } ] -console.log("Will download the following files:") for (const dep of deps) { console.log(`- ${dep.file} from ${dep.url}`) } + +console.log("Will download the above files.") if (!confirm("Continue?")) { console.log("Aborted.") Deno.exit(0) } +const fontsFolder = resolve(outputFolder, "fonts") +Deno.mkdirSync(fontsFolder, { recursive: true }) + for (const dep of deps) { const response = await fetch(dep.url) const data = await response.arrayBuffer()