feat(jbm): support for katex and tables
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
0cd2bc14de
commit
c8cb3c4aff
|
|
@ -0,0 +1,54 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<title></title>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Inter";
|
||||||
|
src: url("/_android_res/font/inter_regular.ttf");
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Inter";
|
||||||
|
src: url("/_android_res/font/inter_bold.ttf");
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Fragment Mono";
|
||||||
|
src: url("/_android_res/font/fragmentmono_regular.ttf");
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, code {
|
||||||
|
font-family: "Fragment Mono", monospace;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="/_android_assets/embedded/katex.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="/_android_assets/embedded/katex.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="katex"></div>
|
||||||
|
<script>
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
katex.render(Bridge.getSource(), document.querySelector("#katex"), {
|
||||||
|
throwOnError: false,
|
||||||
|
maxExpand: 50
|
||||||
|
})
|
||||||
|
document.body.style.color = Bridge.getForegroundColour()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<title></title>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Inter";
|
||||||
|
src: url("/_android_res/font/inter_regular.ttf");
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Inter Display";
|
||||||
|
src: url("/_android_res/font/inter_display_semibold.ttf");
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Inter";
|
||||||
|
src: url("/_android_res/font/inter_bold.ttf");
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Fragment Mono";
|
||||||
|
src: url("/_android_res/font/fragmentmono_regular.ttf");
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No margins anywhere we don't want them */
|
||||||
|
body, html, div, table {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link, a:visited {
|
||||||
|
color: {primary};
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, code {
|
||||||
|
font-family: "Fragment Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
#markdown {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
hyphens: auto;
|
||||||
|
max-width: 100vw;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#markdown img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#markdown h2 {
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-family: "Inter Display", sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
#markdown p, #markdown li {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#markdown table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#markdown table, #markdown th, #markdown td {
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#markdown th, #markdown td {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.user-avatar {
|
||||||
|
width: calc(1.5em * 0.9);
|
||||||
|
height: calc(1.5em * 0.9);
|
||||||
|
vertical-align: middle;
|
||||||
|
border-radius: 9999px;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script defer type="module">
|
||||||
|
import {micromark} from "/_android_assets/embedded/micromark.bundle.js"
|
||||||
|
import {gfmHtml, gfm} from "/_android_assets/embedded/micromark-gfm.bundle.js"
|
||||||
|
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
document.body.style.color = Bridge.getForegroundColour()
|
||||||
|
const dynStyles = `
|
||||||
|
#markdown a {
|
||||||
|
color: ${Bridge.getPrimaryColour()};
|
||||||
|
}
|
||||||
|
#markdown table {
|
||||||
|
border-color: ${Bridge.getForegroundColour()};
|
||||||
|
}
|
||||||
|
#markdown th, #markdown td {
|
||||||
|
border-color: ${Bridge.getForegroundColour()};
|
||||||
|
}
|
||||||
|
a.mention {
|
||||||
|
color: ${Bridge.getPrimaryColour()};
|
||||||
|
background-color: ${Bridge.getMentionBackgroundColour()};
|
||||||
|
padding: 0.25em;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const style = document.createElement("style")
|
||||||
|
style.innerHTML = dynStyles
|
||||||
|
document.head.appendChild(style)
|
||||||
|
|
||||||
|
const htmlSource = micromark(Bridge.getSource(), {
|
||||||
|
extensions: [gfm()],
|
||||||
|
htmlExtensions: [gfmHtml()]
|
||||||
|
})
|
||||||
|
const divWithMarkdown = document.createElement("div")
|
||||||
|
divWithMarkdown.id = "markdown"
|
||||||
|
divWithMarkdown.innerHTML = htmlSource
|
||||||
|
|
||||||
|
// Remove all image tags, as they are not supported
|
||||||
|
divWithMarkdown.querySelectorAll("img").forEach(img => img.remove())
|
||||||
|
// For all <a> tags, rewrite any that include the private _android_assets and _android_res
|
||||||
|
// directories
|
||||||
|
divWithMarkdown.querySelectorAll("a").forEach(a => {
|
||||||
|
if (a.href.includes("_android_assets") || a.href.includes("_android_res")) {
|
||||||
|
a.href = "#"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const ULID_REGEXP = /[0-9A-HJKMNP-TV-Z]{26}/g
|
||||||
|
// Custom emote regex, a ULID wrapped in colons
|
||||||
|
const CUSTOM_EMOTE_REGEXP = new RegExp(`:${ULID_REGEXP.source}:`, "g")
|
||||||
|
// User mention regex, a ULID wrapped in angle brackets with a @ before the ULID
|
||||||
|
const USER_MENTION_REGEXP = new RegExp(`<@${ULID_REGEXP.source}>`, "g")
|
||||||
|
// Channel mention regex, a ULID wrapped in angle brackets with a # before the ULID
|
||||||
|
const CHANNEL_MENTION_REGEXP = new RegExp(`<#${ULID_REGEXP.source}>`, "g")
|
||||||
|
|
||||||
|
// For anything that contains text, replace the custom emote and user/channel mentions
|
||||||
|
// First, replace the custom emotes
|
||||||
|
divWithMarkdown.querySelectorAll("p, li, h1, h2, h3, h4, h5, h6, th, td").forEach(element => {
|
||||||
|
element.innerHTML = element.innerHTML
|
||||||
|
.replace(CUSTOM_EMOTE_REGEXP, match => {
|
||||||
|
const ulid = match.slice(1, -1)
|
||||||
|
const img = document.createElement("img")
|
||||||
|
img.src = Bridge.getCustomEmoteUrl(ulid)
|
||||||
|
img.style.verticalAlign = "middle"
|
||||||
|
img.style.width = "1.5em"
|
||||||
|
img.style.height = "1.5em"
|
||||||
|
// Screen readers must ignore the image as we cannot reliably provide a name
|
||||||
|
img.alt = ""
|
||||||
|
img.setAttribute("aria-hidden", "true")
|
||||||
|
return img.outerHTML
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Next we replace the user mentions. Micromark thinks they are emails so we search
|
||||||
|
// for <a> tags that lead to @<ULID>
|
||||||
|
divWithMarkdown.querySelectorAll("a").forEach(a => {
|
||||||
|
if (a.href.startsWith("mailto:")) {
|
||||||
|
const remainingIsUlid = a.href.slice(7).match(ULID_REGEXP)
|
||||||
|
if (remainingIsUlid) {
|
||||||
|
const ulid = remainingIsUlid[0]
|
||||||
|
|
||||||
|
// First we empty the <a> tag
|
||||||
|
a.href = "#"
|
||||||
|
a.innerHTML = ""
|
||||||
|
|
||||||
|
// Then we add an image with the user's avatar
|
||||||
|
const img = document.createElement("img")
|
||||||
|
img.src = Bridge.userAvatar(ulid)
|
||||||
|
img.classList.add("user-avatar")
|
||||||
|
a.appendChild(img)
|
||||||
|
|
||||||
|
// Finally we put the user's name
|
||||||
|
const textNode = document.createTextNode(Bridge.resolveUserMention(ulid))
|
||||||
|
a.appendChild(textNode)
|
||||||
|
|
||||||
|
// We also add a class to the <a> tag so we can style it
|
||||||
|
a.classList.add("mention")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
document.body.appendChild(divWithMarkdown)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package chat.revolt.markdown.jbm
|
package chat.revolt.markdown.jbm
|
||||||
|
|
||||||
|
import FallbackRenderer
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
@ -22,6 +23,7 @@ import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.text.InlineTextContent
|
import androidx.compose.foundation.text.InlineTextContent
|
||||||
import androidx.compose.foundation.text.appendInlineContent
|
import androidx.compose.foundation.text.appendInlineContent
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
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.SyntaxLanguage
|
||||||
import dev.snipme.highlights.model.SyntaxThemes
|
import dev.snipme.highlights.model.SyntaxThemes
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.logcat
|
|
||||||
import org.intellij.markdown.MarkdownElementTypes
|
import org.intellij.markdown.MarkdownElementTypes
|
||||||
import org.intellij.markdown.MarkdownTokenTypes
|
import org.intellij.markdown.MarkdownTokenTypes
|
||||||
import org.intellij.markdown.ast.ASTNode
|
import org.intellij.markdown.ast.ASTNode
|
||||||
|
|
@ -205,18 +206,11 @@ private fun annotateText(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val member = state.currentServer?.let { serverId ->
|
append(" ")
|
||||||
RevoltAPI.members.getMember(serverId, userId)
|
|
||||||
}
|
|
||||||
val mentionDisplay = member?.nickname
|
|
||||||
?: RevoltAPI.userCache[userId]?.username
|
|
||||||
?: "<@$userId>"
|
|
||||||
|
|
||||||
appendInlineContent(JBMAnnotations.JBMBackgroundRoundingStart.tag)
|
|
||||||
appendInlineContent(JBMAnnotations.UserAvatar.tag, userId)
|
appendInlineContent(JBMAnnotations.UserAvatar.tag, userId)
|
||||||
append(" ")
|
append(" ")
|
||||||
append(mentionDisplay)
|
append(MentionResolver.resolveUser(userId, state.currentServer))
|
||||||
appendInlineContent(JBMAnnotations.JBMBackgroundRoundingEnd.tag)
|
append(" ")
|
||||||
|
|
||||||
pop()
|
pop()
|
||||||
pop()
|
pop()
|
||||||
|
|
@ -243,13 +237,7 @@ private fun annotateText(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val channel = RevoltAPI.channelCache[channelId]
|
append(MentionResolver.resolveChannel(channelId))
|
||||||
val mentionDisplay = channel?.name?.let { name -> "#$name" }
|
|
||||||
?: "<#$channelId>"
|
|
||||||
|
|
||||||
appendInlineContent(JBMAnnotations.JBMBackgroundRoundingStart.tag)
|
|
||||||
append(mentionDisplay)
|
|
||||||
appendInlineContent(JBMAnnotations.JBMBackgroundRoundingEnd.tag)
|
|
||||||
|
|
||||||
pop()
|
pop()
|
||||||
pop()
|
pop()
|
||||||
|
|
@ -950,6 +938,10 @@ private fun JBMBlock(node: ASTNode, modifier: Modifier, nestingCounter: Int = 0)
|
||||||
MarkdownElementTypes.HTML_BLOCK,
|
MarkdownElementTypes.HTML_BLOCK,
|
||||||
MarkdownElementTypes.LINK_DEFINITION,
|
MarkdownElementTypes.LINK_DEFINITION,
|
||||||
MarkdownTokenTypes.WHITE_SPACE -> {
|
MarkdownTokenTypes.WHITE_SPACE -> {
|
||||||
|
// 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(
|
CompositionLocalProvider(
|
||||||
LocalTextStyle provides LocalTextStyle.current.copy(
|
LocalTextStyle provides LocalTextStyle.current.copy(
|
||||||
fontSize = LocalTextStyle.current.fontSize * state.fontSizeMultiplier
|
fontSize = LocalTextStyle.current.fontSize * state.fontSizeMultiplier
|
||||||
|
|
@ -958,6 +950,7 @@ private fun JBMBlock(node: ASTNode, modifier: Modifier, nestingCounter: Int = 0)
|
||||||
JBMText(node, modifier)
|
JBMText(node, modifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MarkdownElementTypes.ATX_1,
|
MarkdownElementTypes.ATX_1,
|
||||||
MarkdownElementTypes.ATX_2,
|
MarkdownElementTypes.ATX_2,
|
||||||
|
|
@ -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 -> {
|
else -> {
|
||||||
Text(
|
Text(
|
||||||
text = buildAnnotatedString {
|
text = buildAnnotatedString {
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -46,20 +46,268 @@ const deps = [
|
||||||
url: "https://cdn.jsdelivr.net/npm/katex@0.16.19/dist/katex.min.js",
|
url: "https://cdn.jsdelivr.net/npm/katex@0.16.19/dist/katex.min.js",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: "micromark.bundle.js",
|
file: "fonts/KaTeX_AMS-Regular.ttf",
|
||||||
url: "https://esm.sh/v135/micromark@4.0.1/es2022/micromark.bundle.mjs",
|
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) {
|
for (const dep of deps) {
|
||||||
console.log(`- ${dep.file} from ${dep.url}`)
|
console.log(`- ${dep.file} from ${dep.url}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Will download the above files.")
|
||||||
if (!confirm("Continue?")) {
|
if (!confirm("Continue?")) {
|
||||||
console.log("Aborted.")
|
console.log("Aborted.")
|
||||||
Deno.exit(0)
|
Deno.exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fontsFolder = resolve(outputFolder, "fonts")
|
||||||
|
Deno.mkdirSync(fontsFolder, { recursive: true })
|
||||||
|
|
||||||
for (const dep of deps) {
|
for (const dep of deps) {
|
||||||
const response = await fetch(dep.url)
|
const response = await fetch(dep.url)
|
||||||
const data = await response.arrayBuffer()
|
const data = await response.arrayBuffer()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue