use markwon

This commit is contained in:
k0shk0sh 2019-07-20 23:01:52 +02:00
parent ae0f802e1b
commit ee2c95dec4
17 changed files with 447 additions and 28 deletions

View File

@ -114,6 +114,8 @@ android {
configurations {
all*.exclude module: 'javax.annotation'
all*.exclude group: 'org.jetbrains', module: 'annotations-java5'
}
}

View File

@ -0,0 +1,20 @@
package com.fastaccess.github.platform.markwon.emoji
import org.commonmark.node.CustomNode
import org.commonmark.node.Delimited
/**
* Created by kosh on 20/08/2017.
*/
class Emoji : CustomNode(), Delimited {
var emoji: String? = null
override fun getOpeningDelimiter(): String = DELIMITER
override fun getClosingDelimiter(): String = DELIMITER
companion object {
private const val DELIMITER = ":"
}
}

View File

@ -0,0 +1,29 @@
package com.fastaccess.github.platform.markwon.emoji
import com.fastaccess.github.platform.markwon.emoji.internal.EmojiDelimiterProcessor
import com.fastaccess.github.platform.markwon.emoji.internal.EmojiNodeRenderer
import org.commonmark.Extension
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
/**
* Created by kosh on 20/08/2017.
*/
class EmojiExtension private constructor() : Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension {
override fun extend(parserBuilder: Parser.Builder) {
parserBuilder.customDelimiterProcessor(EmojiDelimiterProcessor())
}
override fun extend(rendererBuilder: HtmlRenderer.Builder) {
rendererBuilder.nodeRendererFactory { EmojiNodeRenderer(it) }
}
companion object {
fun create(): Extension {
return EmojiExtension()
}
}
}

View File

@ -0,0 +1,40 @@
package com.fastaccess.github.platform.markwon.emoji.internal
import com.fastaccess.github.platform.markwon.emoji.Emoji
import org.commonmark.node.Text
import org.commonmark.parser.delimiter.DelimiterProcessor
import org.commonmark.parser.delimiter.DelimiterRun
class EmojiDelimiterProcessor : DelimiterProcessor {
override fun getOpeningCharacter(): Char = ':'
override fun getClosingCharacter(): Char = ':'
override fun getMinLength(): Int = 1
override fun getDelimiterUse(
opener: DelimiterRun,
closer: DelimiterRun
): Int = if (opener.length() >= 1 && closer.length() >= 1) {
1
} else {
0
}
override fun process(
opener: Text,
closer: Text,
delimiterCount: Int
) {
var emoji: Emoji? = null
val text = opener.next
if (text is Text) {
emoji = Emoji()
emoji.emoji = text.literal
text.unlink()
}
if (emoji != null) opener.insertAfter(emoji)
}
}

View File

@ -0,0 +1,29 @@
package com.fastaccess.github.platform.markwon.emoji.internal
import com.fastaccess.github.platform.markwon.emoji.Emoji
import org.commonmark.node.Node
import org.commonmark.renderer.NodeRenderer
import org.commonmark.renderer.html.HtmlNodeRendererContext
import org.commonmark.renderer.html.HtmlWriter
class EmojiNodeRenderer(private val context: HtmlNodeRendererContext) : NodeRenderer {
private val html: HtmlWriter = context.writer
override fun getNodeTypes(): Set<Class<out Node>> = setOf<Class<out Node>>(Emoji::class.java)
override fun render(node: Node) {
val attributes = context.extendAttributes(node, "emoji", emptyMap())
html.tag("emoji", attributes)
renderChildren(node)
html.tag("/emoji")
}
private fun renderChildren(parent: Node) {
var node: Node? = parent.firstChild
while (node != null) {
val next = node.next
context.render(node)
node = next
}
}
}

View File

@ -0,0 +1,27 @@
package com.fastaccess.github.platform.markwon.hashtag
import org.commonmark.node.CustomNode
import org.commonmark.node.Delimited
/**
* Created by kosh on 20/08/2017.
*/
class HashTag : CustomNode(), Delimited {
var url: String? = null
get() = DELIMITER + field!!
override fun getOpeningDelimiter(): String {
return DELIMITER
}
override fun getClosingDelimiter(): String {
return " "
}
companion object {
private const val DELIMITER = "#"
}
}

View File

@ -0,0 +1,29 @@
package com.fastaccess.github.platform.markwon.hashtag
import com.fastaccess.github.platform.markwon.hashtag.internal.HashTagDelimiterProcessor
import com.fastaccess.github.platform.markwon.hashtag.internal.HashTagNodeRenderer
import org.commonmark.Extension
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
/**
* Created by kosh on 20/08/2017.
*/
class HashTagExtension private constructor() : Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension {
override fun extend(parserBuilder: Parser.Builder) {
parserBuilder.customDelimiterProcessor(HashTagDelimiterProcessor())
}
override fun extend(rendererBuilder: HtmlRenderer.Builder) {
rendererBuilder.nodeRendererFactory { HashTagNodeRenderer(it) }
}
companion object {
fun create(): Extension {
return HashTagExtension()
}
}
}

View File

@ -0,0 +1,42 @@
package com.fastaccess.github.platform.markwon.hashtag.internal
import com.fastaccess.github.platform.markwon.hashtag.HashTag
import org.commonmark.node.Node
import org.commonmark.node.Text
import org.commonmark.parser.delimiter.DelimiterProcessor
import org.commonmark.parser.delimiter.DelimiterRun
class HashTagDelimiterProcessor : DelimiterProcessor {
override fun getOpeningCharacter(): Char = '#'
override fun getClosingCharacter(): Char = ' '
override fun getMinLength(): Int = 1
override fun getDelimiterUse(
opener: DelimiterRun,
closer: DelimiterRun
): Int = if (opener.length() >= 1 && closer.length() >= 1) {
1
} else {
0
}
override fun process(
opener: Text,
closer: Text,
delimiterCount: Int
) {
val hashTag = HashTag()
var tmp: Node? = opener.next
if (tmp != null) {
hashTag.url = (tmp as Text).literal
}
while (tmp != null && tmp !== closer) {
val next = tmp.next
hashTag.appendChild(tmp)
tmp = next
}
opener.insertAfter(hashTag)
}
}

View File

@ -0,0 +1,31 @@
package com.fastaccess.github.platform.markwon.hashtag.internal
import com.fastaccess.github.platform.markwon.hashtag.HashTag
import org.commonmark.node.Node
import org.commonmark.renderer.NodeRenderer
import org.commonmark.renderer.html.HtmlNodeRendererContext
import org.commonmark.renderer.html.HtmlWriter
class HashTagNodeRenderer(private val context: HtmlNodeRendererContext) : NodeRenderer {
private val html: HtmlWriter = context.writer
override fun getNodeTypes(): Set<Class<out Node>> {
return setOf<Class<out Node>>(HashTag::class.java)
}
override fun render(node: Node) {
val attributes = context.extendAttributes(node, "hashtag", emptyMap())
html.tag("hashtag", attributes)
renderChildren(node)
html.tag("/hashtag")
}
private fun renderChildren(parent: Node) {
var node: Node? = parent.firstChild
while (node != null) {
val next = node.next
context.render(node)
node = next
}
}
}

View File

@ -0,0 +1,24 @@
package com.fastaccess.github.platform.markwon.mention
import com.fastaccess.github.utils.GITHUB_LINK
import org.commonmark.node.CustomNode
import org.commonmark.node.Delimited
/**
* Created by kosh on 20/08/2017.
*/
class Mention : CustomNode(), Delimited {
var url: String? = null
get() = BASE_URL + field!!
override fun getOpeningDelimiter(): String = DELIMITER
override fun getClosingDelimiter(): String = " "
companion object {
private const val BASE_URL = GITHUB_LINK
private const val DELIMITER = "@"
}
}

View File

@ -0,0 +1,31 @@
package com.fastaccess.github.platform.markwon.mention
import com.fastaccess.github.platform.markwon.mention.internal.MentionDelimiterProcessor
import com.fastaccess.github.platform.markwon.mention.internal.MentionNodeRenderer
import org.commonmark.Extension
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlNodeRendererFactory
import org.commonmark.renderer.html.HtmlRenderer
/**
* Created by kosh on 20/08/2017.
*/
class MentionExtension private constructor() : Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension {
override fun extend(parserBuilder: Parser.Builder) {
parserBuilder.customDelimiterProcessor(MentionDelimiterProcessor())
}
override fun extend(rendererBuilder: HtmlRenderer.Builder) {
rendererBuilder.nodeRendererFactory(HtmlNodeRendererFactory { MentionNodeRenderer(it) })
}
companion object {
fun create(): Extension {
return MentionExtension()
}
}
}

View File

@ -0,0 +1,44 @@
package com.fastaccess.github.platform.markwon.mention.internal
import com.fastaccess.github.platform.markwon.mention.Mention
import org.commonmark.node.Node
import org.commonmark.node.Text
import org.commonmark.parser.delimiter.DelimiterProcessor
import org.commonmark.parser.delimiter.DelimiterRun
class MentionDelimiterProcessor : DelimiterProcessor {
override fun getOpeningCharacter(): Char = '@'
override fun getClosingCharacter(): Char = ' '
override fun getMinLength(): Int = 1
override fun getDelimiterUse(
opener: DelimiterRun,
closer: DelimiterRun
): Int = if (opener.length() >= 1 && closer.length() >= 1) {
1
} else {
0
}
override fun process(
opener: Text,
closer: Text,
delimiterCount: Int
) {
val mention = Mention()
var tmp: Node? = opener.next
if (tmp != null) {
mention.url = (tmp as Text).literal
}
while (tmp != null && tmp !== closer) {
val next = tmp.next
mention.appendChild(tmp)
tmp = next
}
opener.insertAfter(mention)
}
}

View File

@ -0,0 +1,31 @@
package com.fastaccess.github.platform.markwon.mention.internal
import com.fastaccess.github.platform.markwon.mention.Mention
import org.commonmark.node.Node
import org.commonmark.renderer.NodeRenderer
import org.commonmark.renderer.html.HtmlNodeRendererContext
import org.commonmark.renderer.html.HtmlWriter
class MentionNodeRenderer(private val context: HtmlNodeRendererContext) : NodeRenderer {
private val html: HtmlWriter = context.writer
override fun getNodeTypes(): Set<Class<out Node>> {
return setOf<Class<out Node>>(Mention::class.java)
}
override fun render(node: Node) {
val attributes = context.extendAttributes(node, "mention", emptyMap())
html.tag("mention", attributes)
renderChildren(node)
html.tag("/mention")
}
private fun renderChildren(parent: Node) {
var node: Node? = parent.firstChild
while (node != null) {
val next = node.next
context.render(node)
node = next
}
}
}

View File

@ -7,7 +7,6 @@ import androidx.core.view.isVisible
import com.fastaccess.data.model.CommentAuthorAssociation
import com.fastaccess.data.model.CommentModel
import com.fastaccess.data.model.getEmoji
import com.fastaccess.github.R
import com.fastaccess.github.base.engine.ThemeEngine
import com.fastaccess.github.extensions.timeAgo
import com.fastaccess.github.ui.adapter.base.BaseViewHolder
@ -17,6 +16,7 @@ import com.fastaccess.markdown.spans.drawable.DrawableGetter
import kotlinx.android.synthetic.main.comment_row_item.view.*
import net.nightwhistler.htmlspanner.HtmlSpanner
/**
* Created by Kosh on 12.10.18.
*/
@ -26,8 +26,10 @@ class CommentViewHolder(
private val htmlSpanner: HtmlSpanner,
private val theme: Int,
private val callback: (position: Int) -> Unit
) : BaseViewHolder<CommentModel?>(LayoutInflater.from(parent.context)
.inflate(R.layout.comment_row_item, parent, false)) {
) : BaseViewHolder<CommentModel?>(
LayoutInflater.from(parent.context)
.inflate(com.fastaccess.github.R.layout.comment_row_item, parent, false)
) {
@SuppressLint("SetTextI18n")
override fun bind(item: CommentModel?) {
@ -44,7 +46,7 @@ class CommentViewHolder(
"${model.authorAssociation?.value?.toLowerCase()?.replace("_", "")} ${model.updatedAt?.timeAgo()}"
}
MarkdownProvider.loadIntoTextView(htmlSpanner, description, model.bodyHTML ?: "", ThemeEngine.getCodeBackground(theme),
MarkdownProvider.loadIntoTextView(htmlSpanner, description, model.body ?: "", ThemeEngine.getCodeBackground(theme),
ThemeEngine.isLightTheme(theme))
addEmoji.setOnClickListener {

View File

@ -240,7 +240,7 @@ class IssueFragment : BaseFragment(), LockUnlockFragment.OnLockReasonSelected,
"${model.authorAssociation?.toLowerCase()?.replace("_", "")} ${model.updatedAt?.timeAgo()}"
}
MarkdownProvider.loadIntoTextView(
htmlSpanner, description, model.bodyHTML ?: "", ThemeEngine.getCodeBackground(theme),
htmlSpanner, description, model.body ?: "", ThemeEngine.getCodeBackground(theme),
ThemeEngine.isLightTheme(theme)
)
state.text = model.state?.toLowerCase()

View File

@ -26,6 +26,7 @@ ext {
pandora = 'v1.2.2'
deepLinkDispatch = '4.1.0'
commonmark = '0.12.1'
markwon_version = '4.0.2'
kotlin = [
@ -127,7 +128,19 @@ ext {
"com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:${commonmark}",
"com.atlassian.commonmark:commonmark-ext-gfm-tables:${commonmark}",
"com.atlassian.commonmark:commonmark-ext-ins:${commonmark}",
"com.atlassian.commonmark:commonmark-ext-yaml-front-matter:${commonmark}"
"com.atlassian.commonmark:commonmark-ext-yaml-front-matter:${commonmark}",
"io.noties.markwon:core:$markwon_version",
"io.noties.markwon:ext-latex:$markwon_version",
"io.noties.markwon:ext-strikethrough:$markwon_version",
"io.noties.markwon:ext-tables:$markwon_version",
"io.noties.markwon:ext-tasklist:$markwon_version",
"io.noties.markwon:html:$markwon_version",
"io.noties.markwon:image-glide:$markwon_version",
"io.noties.markwon:recycler:$markwon_version",
"io.noties.markwon:recycler-table:$markwon_version",
"io.noties.markwon:simple-ext:$markwon_version",
"io.noties.markwon:syntax-highlight:$markwon_version",
"io.noties:prism4j:2.0.0"
]
@ -137,7 +150,8 @@ ext {
"com.github.bumptech.glide:compiler:$glide",
"androidx.room:room-runtime:2.1.0",
"com.evernote:android-state-processor:$androidState",
"com.airbnb:deeplinkdispatch-processor:$deepLinkDispatch"
"com.airbnb:deeplinkdispatch-processor:$deepLinkDispatch",
"io.noties:prism4j-bundler:2.0.0"
]
testing = [

View File

@ -1,14 +1,22 @@
package com.fastaccess.markdown
import android.text.method.LinkMovementMethod
import android.text.method.ScrollingMovementMethod
import android.widget.TextView
import androidx.core.text.HtmlCompat
import androidx.core.view.doOnPreDraw
import com.fastaccess.github.extensions.generateTextColor
import com.fastaccess.markdown.spans.DrawableHandler
import com.fastaccess.markdown.spans.HrHandler
import com.fastaccess.markdown.spans.PreTagHandler
import com.fastaccess.markdown.spans.QuoteHandler
import io.noties.markwon.Markwon
import io.noties.markwon.ext.latex.JLatexMathPlugin
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
import io.noties.markwon.ext.tables.TablePlugin
import io.noties.markwon.ext.tasklist.TaskListPlugin
import io.noties.markwon.html.HtmlPlugin
import io.noties.markwon.image.glide.GlideImagesPlugin
import io.noties.markwon.movement.MovementMethodPlugin
import io.noties.markwon.syntax.Prism4jThemeDarkula
import io.noties.markwon.syntax.Prism4jThemeDefault
import io.noties.markwon.syntax.SyntaxHighlightPlugin
import io.noties.prism4j.Prism4j
import io.noties.prism4j.annotations.PrismBundle
import net.nightwhistler.htmlspanner.HtmlSpanner
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
@ -16,6 +24,7 @@ import org.commonmark.renderer.html.HtmlRenderer
/**
* Created by Kosh on 02.02.19.
*/
@PrismBundle(includeAll = true)
object MarkdownProvider {
private const val TOGGLE_START = "<span class=\"email-hidden-toggle\">"
@ -65,19 +74,26 @@ object MarkdownProvider {
isLightTheme: Boolean,
onLinkClicked: ((link: String) -> Unit)? = null
) {
val linkMovementMethod = LinkMovementMethod.getInstance()
textView.movementMethod = linkMovementMethod
htmlSpanner.registerHandler("pre", PreTagHandler(windowBackground, true, isLightTheme))
htmlSpanner.registerHandler("code", PreTagHandler(windowBackground, false, isLightTheme))
htmlSpanner.registerHandler("img", DrawableHandler(textView, width))
htmlSpanner.registerHandler("blockquote", QuoteHandler(windowBackground))
htmlSpanner.registerHandler("hr", HrHandler(windowBackground, width))
val tableHandler = net.nightwhistler.htmlspanner.handlers.TableHandler()
tableHandler.setTableWidth(width)
tableHandler.setTextSize(20f)
tableHandler.setTextColor(windowBackground.generateTextColor())
htmlSpanner.registerHandler("table", tableHandler)
textView.text = htmlSpanner.fromHtml(format(html).toString())
val context = textView.context
Markwon.builder(context)
.usePlugin(JLatexMathPlugin.create(textView.textSize - 20))
.usePlugin(TaskListPlugin.create(context))
.usePlugin(HtmlPlugin.create())
.usePlugin(GlideImagesPlugin.create(context))
.usePlugin(TablePlugin.create(context))
.usePlugin(StrikethroughPlugin.create())
.usePlugin(MovementMethodPlugin.create(ScrollingMovementMethod.getInstance()))
.usePlugin(
SyntaxHighlightPlugin.create(
Prism4j(GrammarLocatorDef()), if (isLightTheme) {
Prism4jThemeDefault.create()
} else {
Prism4jThemeDarkula.create()
}
)
)
.build()
.setMarkdown(textView, html)
}
//https://github.com/k0shk0sh/GitHubSdk/blob/master/library/src/main/java/com/meisolsson/githubsdk/core/HtmlUtils.java
@ -93,7 +109,11 @@ object MarkdownProvider {
return formatted
}
private fun strip(input: StringBuilder, prefix: String, suffix: String) {
private fun strip(
input: StringBuilder,
prefix: String,
suffix: String
) {
var start = input.indexOf(prefix)
while (start != -1) {
var end = input.indexOf(suffix, start + prefix.length)
@ -104,7 +124,11 @@ object MarkdownProvider {
}
}
private fun replace(input: StringBuilder, from: String, to: String): Boolean {
private fun replace(
input: StringBuilder,
from: String,
to: String
): Boolean {
var start = input.indexOf(from)
if (start == -1) return false
val fromLength = from.length