Merge pull request #4838 from vector-im/feature/bma/math_final
Support LaTex Math
This commit is contained in:
commit
3ec2a09639
|
@ -0,0 +1 @@
|
||||||
|
Add labs support for rendering LaTeX maths (MSC2191)
|
|
@ -95,6 +95,8 @@ ext.libs = [
|
||||||
],
|
],
|
||||||
markwon : [
|
markwon : [
|
||||||
'core' : "io.noties.markwon:core:$markwon",
|
'core' : "io.noties.markwon:core:$markwon",
|
||||||
|
'extLatex' : "io.noties.markwon:ext-latex:$markwon",
|
||||||
|
'inlineParser' : "io.noties.markwon:inline-parser:$markwon",
|
||||||
'html' : "io.noties.markwon:html:$markwon"
|
'html' : "io.noties.markwon:html:$markwon"
|
||||||
],
|
],
|
||||||
airbnb : [
|
airbnb : [
|
||||||
|
|
|
@ -179,6 +179,7 @@ ext.groups = [
|
||||||
'org.sonatype.oss',
|
'org.sonatype.oss',
|
||||||
'org.testng',
|
'org.testng',
|
||||||
'org.threeten',
|
'org.threeten',
|
||||||
|
'ru.noties',
|
||||||
'xerces',
|
'xerces',
|
||||||
'xml-apis',
|
'xml-apis',
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.commonmark.ext.maths
|
||||||
|
|
||||||
|
import org.commonmark.node.CustomBlock
|
||||||
|
|
||||||
|
class DisplayMaths(private val delimiter: DisplayDelimiter) : CustomBlock() {
|
||||||
|
enum class DisplayDelimiter {
|
||||||
|
DOUBLE_DOLLAR,
|
||||||
|
SQUARE_BRACKET_ESCAPED
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.commonmark.ext.maths
|
||||||
|
|
||||||
|
import org.commonmark.node.CustomNode
|
||||||
|
import org.commonmark.node.Delimited
|
||||||
|
|
||||||
|
class InlineMaths(private val delimiter: InlineDelimiter) : CustomNode(), Delimited {
|
||||||
|
enum class InlineDelimiter {
|
||||||
|
SINGLE_DOLLAR,
|
||||||
|
ROUND_BRACKET_ESCAPED
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOpeningDelimiter(): String {
|
||||||
|
return when (delimiter) {
|
||||||
|
InlineDelimiter.SINGLE_DOLLAR -> "$"
|
||||||
|
InlineDelimiter.ROUND_BRACKET_ESCAPED -> "\\("
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getClosingDelimiter(): String {
|
||||||
|
return when (delimiter) {
|
||||||
|
InlineDelimiter.SINGLE_DOLLAR -> "$"
|
||||||
|
InlineDelimiter.ROUND_BRACKET_ESCAPED -> "\\)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.commonmark.ext.maths
|
||||||
|
|
||||||
|
import org.commonmark.Extension
|
||||||
|
import org.commonmark.ext.maths.internal.DollarMathsDelimiterProcessor
|
||||||
|
import org.commonmark.ext.maths.internal.MathsHtmlNodeRenderer
|
||||||
|
import org.commonmark.parser.Parser
|
||||||
|
import org.commonmark.renderer.html.HtmlRenderer
|
||||||
|
|
||||||
|
class MathsExtension private constructor() : Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension {
|
||||||
|
override fun extend(parserBuilder: Parser.Builder) {
|
||||||
|
parserBuilder.customDelimiterProcessor(DollarMathsDelimiterProcessor())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun extend(rendererBuilder: HtmlRenderer.Builder) {
|
||||||
|
rendererBuilder.nodeRendererFactory { context -> MathsHtmlNodeRenderer(context) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(): Extension {
|
||||||
|
return MathsExtension()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.commonmark.ext.maths.internal
|
||||||
|
|
||||||
|
import org.commonmark.ext.maths.DisplayMaths
|
||||||
|
import org.commonmark.ext.maths.InlineMaths
|
||||||
|
import org.commonmark.node.Text
|
||||||
|
import org.commonmark.parser.delimiter.DelimiterProcessor
|
||||||
|
import org.commonmark.parser.delimiter.DelimiterRun
|
||||||
|
|
||||||
|
class DollarMathsDelimiterProcessor : DelimiterProcessor {
|
||||||
|
override fun getOpeningCharacter() = '$'
|
||||||
|
|
||||||
|
override fun getClosingCharacter() = '$'
|
||||||
|
|
||||||
|
override fun getMinLength() = 1
|
||||||
|
|
||||||
|
override fun getDelimiterUse(opener: DelimiterRun, closer: DelimiterRun): Int {
|
||||||
|
return if (opener.length() == 1 && closer.length() == 1) 1 // inline
|
||||||
|
else if (opener.length() == 2 && closer.length() == 2) 2 // display
|
||||||
|
else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun process(opener: Text, closer: Text, delimiterUse: Int) {
|
||||||
|
val maths = if (delimiterUse == 1) {
|
||||||
|
InlineMaths(InlineMaths.InlineDelimiter.SINGLE_DOLLAR)
|
||||||
|
} else {
|
||||||
|
DisplayMaths(DisplayMaths.DisplayDelimiter.DOUBLE_DOLLAR)
|
||||||
|
}
|
||||||
|
var tmp = opener.next
|
||||||
|
while (tmp != null && tmp !== closer) {
|
||||||
|
val next = tmp.next
|
||||||
|
maths.appendChild(tmp)
|
||||||
|
tmp = next
|
||||||
|
}
|
||||||
|
opener.insertAfter(maths)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.commonmark.ext.maths.internal
|
||||||
|
|
||||||
|
import org.commonmark.ext.maths.DisplayMaths
|
||||||
|
import org.commonmark.node.Node
|
||||||
|
import org.commonmark.node.Text
|
||||||
|
import org.commonmark.renderer.html.HtmlNodeRendererContext
|
||||||
|
import org.commonmark.renderer.html.HtmlWriter
|
||||||
|
import java.util.Collections
|
||||||
|
|
||||||
|
class MathsHtmlNodeRenderer(private val context: HtmlNodeRendererContext) : MathsNodeRenderer() {
|
||||||
|
private val html: HtmlWriter = context.writer
|
||||||
|
override fun render(node: Node) {
|
||||||
|
val display = node.javaClass == DisplayMaths::class.java
|
||||||
|
val contents = node.firstChild // should be the only child
|
||||||
|
val latex = (contents as Text).literal
|
||||||
|
val attributes = context.extendAttributes(node, if (display) "div" else "span", Collections.singletonMap("data-mx-maths",
|
||||||
|
latex))
|
||||||
|
html.tag(if (display) "div" else "span", attributes)
|
||||||
|
html.tag("code")
|
||||||
|
context.render(contents)
|
||||||
|
html.tag("/code")
|
||||||
|
html.tag(if (display) "/div" else "/span")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.commonmark.ext.maths.internal
|
||||||
|
|
||||||
|
import org.commonmark.ext.maths.InlineMaths
|
||||||
|
import org.commonmark.ext.maths.DisplayMaths
|
||||||
|
import org.commonmark.ext.maths.internal.MathsNodeRenderer
|
||||||
|
import org.commonmark.node.Node
|
||||||
|
import org.commonmark.renderer.NodeRenderer
|
||||||
|
import java.util.HashSet
|
||||||
|
|
||||||
|
abstract class MathsNodeRenderer : NodeRenderer {
|
||||||
|
override fun getNodeTypes(): Set<Class<out Node>> {
|
||||||
|
val types: MutableSet<Class<out Node>> = HashSet()
|
||||||
|
types.add(InlineMaths::class.java)
|
||||||
|
types.add(DisplayMaths::class.java)
|
||||||
|
return types
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.session.room
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
import org.commonmark.Extension
|
||||||
|
import org.commonmark.ext.maths.MathsExtension
|
||||||
import org.commonmark.parser.Parser
|
import org.commonmark.parser.Parser
|
||||||
import org.commonmark.renderer.html.HtmlRenderer
|
import org.commonmark.renderer.html.HtmlRenderer
|
||||||
import org.matrix.android.sdk.api.session.file.FileService
|
import org.matrix.android.sdk.api.session.file.FileService
|
||||||
|
@ -104,6 +106,7 @@ internal abstract class RoomModule {
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
companion object {
|
companion object {
|
||||||
|
private val extensions : List<Extension> = listOf(MathsExtension.create())
|
||||||
@Provides
|
@Provides
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@SessionScope
|
@SessionScope
|
||||||
|
@ -121,7 +124,7 @@ internal abstract class RoomModule {
|
||||||
@Provides
|
@Provides
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun providesParser(): Parser {
|
fun providesParser(): Parser {
|
||||||
return Parser.builder().build()
|
return Parser.builder().extensions(extensions).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -129,6 +132,7 @@ internal abstract class RoomModule {
|
||||||
fun providesHtmlRenderer(): HtmlRenderer {
|
fun providesHtmlRenderer(): HtmlRenderer {
|
||||||
return HtmlRenderer
|
return HtmlRenderer
|
||||||
.builder()
|
.builder()
|
||||||
|
.extensions(extensions)
|
||||||
.softbreak("<br />")
|
.softbreak("<br />")
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ internal class MarkdownParser @Inject constructor(
|
||||||
private val textPillsUtils: TextPillsUtils
|
private val textPillsUtils: TextPillsUtils
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val mdSpecialChars = "[`_\\-*>.\\[\\]#~]".toRegex()
|
private val mdSpecialChars = "[`_\\-*>.\\[\\]#~$]".toRegex()
|
||||||
|
|
||||||
fun parse(text: CharSequence): TextContent {
|
fun parse(text: CharSequence): TextContent {
|
||||||
val source = textPillsUtils.processSpecialSpansToMarkdown(text) ?: text.toString()
|
val source = textPillsUtils.processSpecialSpansToMarkdown(text) ?: text.toString()
|
||||||
|
|
|
@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1
|
||||||
# android\.text\.TextUtils
|
# android\.text\.TextUtils
|
||||||
|
|
||||||
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
||||||
enum class===115
|
enum class===117
|
||||||
|
|
||||||
### Do not import temporary legacy classes
|
### Do not import temporary legacy classes
|
||||||
import org.matrix.android.sdk.internal.legacy.riot===3
|
import org.matrix.android.sdk.internal.legacy.riot===3
|
||||||
|
|
|
@ -389,6 +389,8 @@ dependencies {
|
||||||
implementation libs.google.material
|
implementation libs.google.material
|
||||||
implementation 'me.gujun.android:span:1.7'
|
implementation 'me.gujun.android:span:1.7'
|
||||||
implementation libs.markwon.core
|
implementation libs.markwon.core
|
||||||
|
implementation libs.markwon.extLatex
|
||||||
|
implementation libs.markwon.inlineParser
|
||||||
implementation libs.markwon.html
|
implementation libs.markwon.html
|
||||||
implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.5.2'
|
implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.5.2'
|
||||||
implementation 'me.saket:better-link-movement-method:2.2.0'
|
implementation 'me.saket:better-link-movement-method:2.2.0'
|
||||||
|
|
|
@ -512,6 +512,7 @@ class MessageItemFactory @Inject constructor(
|
||||||
)
|
)
|
||||||
.useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
|
.useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
|
||||||
.bindingOptions(bindingOptions)
|
.bindingOptions(bindingOptions)
|
||||||
|
.markwonPlugins(htmlRenderer.get().plugins)
|
||||||
.searchForPills(isFormatted)
|
.searchForPills(isFormatted)
|
||||||
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
||||||
.imageContentRenderer(imageContentRenderer)
|
.imageContentRenderer(imageContentRenderer)
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.detail.timeline.item
|
package im.vector.app.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.text.Spanned
|
||||||
import android.text.method.MovementMethod
|
import android.text.method.MovementMethod
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
import androidx.core.text.PrecomputedTextCompat
|
import androidx.core.text.PrecomputedTextCompat
|
||||||
|
@ -33,6 +34,7 @@ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
||||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlUiState
|
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlUiState
|
||||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlView
|
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlView
|
||||||
import im.vector.app.features.media.ImageContentRenderer
|
import im.vector.app.features.media.ImageContentRenderer
|
||||||
|
import io.noties.markwon.MarkwonPlugin
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
||||||
|
@ -62,6 +64,9 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
||||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||||
var movementMethod: MovementMethod? = null
|
var movementMethod: MovementMethod? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||||
|
var markwonPlugins: (List<MarkwonPlugin>)? = null
|
||||||
|
|
||||||
private val previewUrlViewUpdater = PreviewUrlViewUpdater()
|
private val previewUrlViewUpdater = PreviewUrlViewUpdater()
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
|
@ -95,6 +100,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
markwonPlugins?.forEach { plugin -> plugin.beforeSetText(holder.messageView, message?.charSequence as Spanned) }
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
holder.messageView.movementMethod = movementMethod
|
holder.messageView.movementMethod = movementMethod
|
||||||
renderSendState(holder.messageView, holder.messageView)
|
renderSendState(holder.messageView, holder.messageView)
|
||||||
|
@ -106,6 +112,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
||||||
} else {
|
} else {
|
||||||
holder.messageView.text = message?.charSequence
|
holder.messageView.text = message?.charSequence
|
||||||
}
|
}
|
||||||
|
markwonPlugins?.forEach { plugin -> plugin.afterSetText(holder.messageView) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unbind(holder: Holder) {
|
override fun unbind(holder: Holder) {
|
||||||
|
|
|
@ -20,24 +20,57 @@ import android.content.Context
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import androidx.core.text.toSpannable
|
import androidx.core.text.toSpannable
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import io.noties.markwon.AbstractMarkwonPlugin
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
|
import io.noties.markwon.MarkwonPlugin
|
||||||
|
import io.noties.markwon.PrecomputedFutureTextSetterCompat
|
||||||
|
import io.noties.markwon.ext.latex.JLatexMathPlugin
|
||||||
|
import io.noties.markwon.ext.latex.JLatexMathTheme
|
||||||
import io.noties.markwon.html.HtmlPlugin
|
import io.noties.markwon.html.HtmlPlugin
|
||||||
|
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin
|
||||||
import org.commonmark.node.Node
|
import org.commonmark.node.Node
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class EventHtmlRenderer @Inject constructor(htmlConfigure: MatrixHtmlPluginConfigure,
|
class EventHtmlRenderer @Inject constructor(
|
||||||
context: Context) {
|
htmlConfigure: MatrixHtmlPluginConfigure,
|
||||||
|
context: Context,
|
||||||
|
vectorPreferences: VectorPreferences
|
||||||
|
) {
|
||||||
|
|
||||||
interface PostProcessor {
|
interface PostProcessor {
|
||||||
fun afterRender(renderedText: Spannable)
|
fun afterRender(renderedText: Spannable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val markwon = Markwon.builder(context)
|
private val builder = Markwon.builder(context)
|
||||||
.usePlugin(HtmlPlugin.create(htmlConfigure))
|
.usePlugin(HtmlPlugin.create(htmlConfigure))
|
||||||
.build()
|
|
||||||
|
private val markwon = if (vectorPreferences.latexMathsIsEnabled()) {
|
||||||
|
builder
|
||||||
|
.usePlugin(object : AbstractMarkwonPlugin() { // Markwon expects maths to be in a specific format: https://noties.io/Markwon/docs/v4/ext-latex
|
||||||
|
override fun processMarkdown(markdown: String): String {
|
||||||
|
return markdown
|
||||||
|
.replace(Regex("""<span\s+data-mx-maths="([^"]*)">.*?</span>""")) {
|
||||||
|
matchResult -> "$$" + matchResult.groupValues[1] + "$$"
|
||||||
|
}
|
||||||
|
.replace(Regex("""<div\s+data-mx-maths="([^"]*)">.*?</div>""")) {
|
||||||
|
matchResult -> "\n$$\n" + matchResult.groupValues[1] + "\n$$\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.usePlugin(MarkwonInlineParserPlugin.create())
|
||||||
|
.usePlugin(JLatexMathPlugin.create(44F) { builder ->
|
||||||
|
builder.inlinesEnabled(true)
|
||||||
|
builder.theme().inlinePadding(JLatexMathTheme.Padding.symmetric(24, 8))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
}.textSetter(PrecomputedFutureTextSetterCompat.create()).build()
|
||||||
|
|
||||||
|
val plugins: List<MarkwonPlugin> = markwon.plugins
|
||||||
|
|
||||||
fun parse(text: String): Node {
|
fun parse(text: String): Node {
|
||||||
return markwon.parse(text)
|
return markwon.parse(text)
|
||||||
|
|
|
@ -97,6 +97,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||||
private const val SETTINGS_SEND_MESSAGE_WITH_ENTER = "SETTINGS_SEND_MESSAGE_WITH_ENTER"
|
private const val SETTINGS_SEND_MESSAGE_WITH_ENTER = "SETTINGS_SEND_MESSAGE_WITH_ENTER"
|
||||||
private const val SETTINGS_ENABLE_CHAT_EFFECTS = "SETTINGS_ENABLE_CHAT_EFFECTS"
|
private const val SETTINGS_ENABLE_CHAT_EFFECTS = "SETTINGS_ENABLE_CHAT_EFFECTS"
|
||||||
private const val SETTINGS_SHOW_EMOJI_KEYBOARD = "SETTINGS_SHOW_EMOJI_KEYBOARD"
|
private const val SETTINGS_SHOW_EMOJI_KEYBOARD = "SETTINGS_SHOW_EMOJI_KEYBOARD"
|
||||||
|
private const val SETTINGS_LABS_ENABLE_LATEX_MATHS = "SETTINGS_LABS_ENABLE_LATEX_MATHS"
|
||||||
|
|
||||||
// Room directory
|
// Room directory
|
||||||
private const val SETTINGS_ROOM_DIRECTORY_SHOW_ALL_PUBLIC_ROOMS = "SETTINGS_ROOM_DIRECTORY_SHOW_ALL_PUBLIC_ROOMS"
|
private const val SETTINGS_ROOM_DIRECTORY_SHOW_ALL_PUBLIC_ROOMS = "SETTINGS_ROOM_DIRECTORY_SHOW_ALL_PUBLIC_ROOMS"
|
||||||
|
@ -334,6 +335,10 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||||
return defaultPrefs.getBoolean(SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB, false)
|
return defaultPrefs.getBoolean(SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun latexMathsIsEnabled(): Boolean {
|
||||||
|
return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_LATEX_MATHS, false)
|
||||||
|
}
|
||||||
|
|
||||||
fun failFast(): Boolean {
|
fun failFast(): Boolean {
|
||||||
return BuildConfig.DEBUG || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false))
|
return BuildConfig.DEBUG || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3636,6 +3636,9 @@
|
||||||
<!-- %s will be replaced by the value of link_this_email_settings_link and styled as a link -->
|
<!-- %s will be replaced by the value of link_this_email_settings_link and styled as a link -->
|
||||||
<string name="link_this_email_with_your_account">%s in Settings to receive invites directly in Element.</string>
|
<string name="link_this_email_with_your_account">%s in Settings to receive invites directly in Element.</string>
|
||||||
|
|
||||||
|
<string name="labs_enable_latex_maths">Enable LaTeX mathematics</string>
|
||||||
|
<string name="restart_the_application_to_apply_changes">Restart the application for the change to take effect.</string>
|
||||||
|
|
||||||
<!-- Poll -->
|
<!-- Poll -->
|
||||||
<string name="create_poll_title">Create Poll</string>
|
<string name="create_poll_title">Create Poll</string>
|
||||||
<string name="create_poll_question_title">Poll question or topic</string>
|
<string name="create_poll_question_title">Poll question or topic</string>
|
||||||
|
|
|
@ -47,8 +47,14 @@
|
||||||
<im.vector.app.core.preference.VectorSwitchPreference
|
<im.vector.app.core.preference.VectorSwitchPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE"
|
android:key="SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE"
|
||||||
android:title="@string/labs_use_restricted_join_rule"
|
android:summary="@string/labs_use_restricted_join_rule_desc"
|
||||||
android:summary="@string/labs_use_restricted_join_rule_desc"/>
|
android:title="@string/labs_use_restricted_join_rule" />
|
||||||
|
|
||||||
|
<im.vector.app.core.preference.VectorSwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="SETTINGS_LABS_ENABLE_LATEX_MATHS"
|
||||||
|
android:summary="@string/restart_the_application_to_apply_changes"
|
||||||
|
android:title="@string/labs_enable_latex_maths" />
|
||||||
<!--</im.vector.app.core.preference.VectorPreferenceCategory>-->
|
<!--</im.vector.app.core.preference.VectorPreferenceCategory>-->
|
||||||
|
|
||||||
<im.vector.app.core.preference.VectorSwitchPreference
|
<im.vector.app.core.preference.VectorSwitchPreference
|
||||||
|
|
Loading…
Reference in New Issue