mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-29 18:43:25 +02:00
Refactoring to move markdown parser away from the Compose Class
This commit is contained in:
@@ -11,7 +11,6 @@ import com.vitorpamplona.amethyst.ui.components.ZoomableUrlImage
|
|||||||
import com.vitorpamplona.amethyst.ui.components.ZoomableUrlVideo
|
import com.vitorpamplona.amethyst.ui.components.ZoomableUrlVideo
|
||||||
import com.vitorpamplona.amethyst.ui.components.hashTagsPattern
|
import com.vitorpamplona.amethyst.ui.components.hashTagsPattern
|
||||||
import com.vitorpamplona.amethyst.ui.components.imageExtensions
|
import com.vitorpamplona.amethyst.ui.components.imageExtensions
|
||||||
import com.vitorpamplona.amethyst.ui.components.startsWithNIP19Scheme
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.tagIndex
|
import com.vitorpamplona.amethyst.ui.components.tagIndex
|
||||||
import com.vitorpamplona.amethyst.ui.components.videoExtensions
|
import com.vitorpamplona.amethyst.ui.components.videoExtensions
|
||||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||||
@@ -290,3 +289,9 @@ class SchemelessUrlSegment(segment: String, val url: String, val extras: String?
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class RegularTextSegment(segment: String) : Segment(segment)
|
class RegularTextSegment(segment: String) : Segment(segment)
|
||||||
|
|
||||||
|
fun startsWithNIP19Scheme(word: String): Boolean {
|
||||||
|
val cleaned = word.lowercase().removePrefix("@").removePrefix("nostr:").removePrefix("@")
|
||||||
|
|
||||||
|
return listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1").any { cleaned.startsWith(it) }
|
||||||
|
}
|
||||||
|
@@ -82,6 +82,7 @@ import com.vitorpamplona.amethyst.model.User
|
|||||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||||
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
||||||
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
|
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
|
||||||
|
import com.vitorpamplona.amethyst.service.startsWithNIP19Scheme
|
||||||
import com.vitorpamplona.amethyst.ui.components.*
|
import com.vitorpamplona.amethyst.ui.components.*
|
||||||
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.note.CancelIcon
|
import com.vitorpamplona.amethyst.ui.note.CancelIcon
|
||||||
|
@@ -0,0 +1,154 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.Patterns
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
|
import com.vitorpamplona.amethyst.service.startsWithNIP19Scheme
|
||||||
|
import com.vitorpamplona.quartz.encoders.Nip19
|
||||||
|
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||||
|
|
||||||
|
class MarkdownParser {
|
||||||
|
private fun getDisplayNameAndNIP19FromTag(tag: String, tags: ImmutableListOfLists<String>): Pair<String, String>? {
|
||||||
|
val matcher = tagIndex.matcher(tag)
|
||||||
|
val (index, suffix) = try {
|
||||||
|
matcher.find()
|
||||||
|
Pair(matcher.group(1)?.toInt(), matcher.group(2) ?: "")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w("Tag Parser", "Couldn't link tag $tag", e)
|
||||||
|
Pair(null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index != null && index >= 0 && index < tags.lists.size) {
|
||||||
|
val tag = tags.lists[index]
|
||||||
|
|
||||||
|
if (tag.size > 1) {
|
||||||
|
if (tag[0] == "p") {
|
||||||
|
LocalCache.checkGetOrCreateUser(tag[1])?.let {
|
||||||
|
return Pair(it.toBestDisplayName(), it.pubkeyNpub())
|
||||||
|
}
|
||||||
|
} else if (tag[0] == "e" || tag[0] == "a") {
|
||||||
|
LocalCache.checkGetOrCreateNote(tag[1])?.let {
|
||||||
|
return Pair(it.idDisplayNote(), it.toNEvent())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDisplayNameFromNip19(nip19: Nip19.Return): Pair<String, String>? {
|
||||||
|
if (nip19.type == Nip19.Type.USER) {
|
||||||
|
LocalCache.users[nip19.hex]?.let {
|
||||||
|
return Pair(it.toBestDisplayName(), it.pubkeyNpub())
|
||||||
|
}
|
||||||
|
} else if (nip19.type == Nip19.Type.NOTE) {
|
||||||
|
LocalCache.notes[nip19.hex]?.let {
|
||||||
|
return Pair(it.idDisplayNote(), it.toNEvent())
|
||||||
|
}
|
||||||
|
} else if (nip19.type == Nip19.Type.ADDRESS) {
|
||||||
|
LocalCache.addressables[nip19.hex]?.let {
|
||||||
|
return Pair(it.idDisplayNote(), it.toNEvent())
|
||||||
|
}
|
||||||
|
} else if (nip19.type == Nip19.Type.EVENT) {
|
||||||
|
LocalCache.notes[nip19.hex]?.let {
|
||||||
|
return Pair(it.idDisplayNote(), it.toNEvent())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun returnNIP19References(content: String, tags: ImmutableListOfLists<String>?): List<Nip19.Return> {
|
||||||
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
val listOfReferences = mutableListOf<Nip19.Return>()
|
||||||
|
content.split('\n').forEach { paragraph ->
|
||||||
|
paragraph.split(' ').forEach { word: String ->
|
||||||
|
if (startsWithNIP19Scheme(word)) {
|
||||||
|
val parsedNip19 = Nip19.uriToRoute(word)
|
||||||
|
parsedNip19?.let {
|
||||||
|
listOfReferences.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tags?.lists?.forEach {
|
||||||
|
if (it[0] == "p" && it.size > 1) {
|
||||||
|
listOfReferences.add(Nip19.Return(Nip19.Type.USER, it[1], null, null, null, ""))
|
||||||
|
} else if (it[0] == "e" && it.size > 1) {
|
||||||
|
listOfReferences.add(Nip19.Return(Nip19.Type.NOTE, it[1], null, null, null, ""))
|
||||||
|
} else if (it[0] == "a" && it.size > 1) {
|
||||||
|
listOfReferences.add(Nip19.Return(Nip19.Type.ADDRESS, it[1], null, null, null, ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return listOfReferences
|
||||||
|
}
|
||||||
|
|
||||||
|
fun returnMarkdownWithSpecialContent(content: String, tags: ImmutableListOfLists<String>?): String {
|
||||||
|
var returnContent = ""
|
||||||
|
content.split('\n').forEach { paragraph ->
|
||||||
|
paragraph.split(' ').forEach { word: String ->
|
||||||
|
if (isValidURL(word)) {
|
||||||
|
val removedParamsFromUrl = word.split("?")[0].lowercase()
|
||||||
|
if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) {
|
||||||
|
returnContent += " "
|
||||||
|
} else {
|
||||||
|
returnContent += "[$word]($word) "
|
||||||
|
}
|
||||||
|
} else if (Patterns.EMAIL_ADDRESS.matcher(word).matches()) {
|
||||||
|
returnContent += "[$word](mailto:$word) "
|
||||||
|
} else if (Patterns.PHONE.matcher(word).matches() && word.length > 6) {
|
||||||
|
returnContent += "[$word](tel:$word) "
|
||||||
|
} else if (startsWithNIP19Scheme(word)) {
|
||||||
|
val parsedNip19 = Nip19.uriToRoute(word)
|
||||||
|
returnContent += if (parsedNip19 !== null) {
|
||||||
|
val pair = getDisplayNameFromNip19(parsedNip19)
|
||||||
|
if (pair != null) {
|
||||||
|
val (displayName, nip19) = pair
|
||||||
|
"[$displayName](nostr:$nip19) "
|
||||||
|
} else {
|
||||||
|
"$word "
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"$word "
|
||||||
|
}
|
||||||
|
} else if (word.startsWith("#")) {
|
||||||
|
if (tagIndex.matcher(word).matches() && tags != null) {
|
||||||
|
val pair = getDisplayNameAndNIP19FromTag(word, tags)
|
||||||
|
if (pair != null) {
|
||||||
|
returnContent += "[${pair.first}](nostr:${pair.second}) "
|
||||||
|
} else {
|
||||||
|
returnContent += "$word "
|
||||||
|
}
|
||||||
|
} else if (hashTagsPattern.matcher(word).matches()) {
|
||||||
|
val hashtagMatcher = hashTagsPattern.matcher(word)
|
||||||
|
|
||||||
|
val (myTag, mySuffix) = try {
|
||||||
|
hashtagMatcher.find()
|
||||||
|
Pair(hashtagMatcher.group(1), hashtagMatcher.group(2))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("Hashtag Parser", "Couldn't link hashtag $word", e)
|
||||||
|
Pair(null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (myTag != null) {
|
||||||
|
returnContent += "[#$myTag](nostr:Hashtag?id=$myTag)$mySuffix "
|
||||||
|
} else {
|
||||||
|
returnContent += "$word "
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
returnContent += "$word "
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
returnContent += "$word "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
returnContent += "\n"
|
||||||
|
}
|
||||||
|
return returnContent
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import android.util.Patterns
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
@@ -113,7 +111,7 @@ fun RichTextViewer(
|
|||||||
) {
|
) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
if (remember(content) { isMarkdown(content) }) {
|
if (remember(content) { isMarkdown(content) }) {
|
||||||
RenderContentAsMarkdown(content, tags, nav)
|
RenderContentAsMarkdown(content, tags, accountViewModel, nav)
|
||||||
} else {
|
} else {
|
||||||
RenderRegular(content, tags, canPreview, backgroundColor, accountViewModel, nav)
|
RenderRegular(content, tags, canPreview, backgroundColor, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
@@ -320,7 +318,7 @@ fun RenderCustomEmoji(word: String, state: RichTextViewerState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderContentAsMarkdown(content: String, tags: ImmutableListOfLists<String>?, nav: (String) -> Unit) {
|
private fun RenderContentAsMarkdown(content: String, tags: ImmutableListOfLists<String>?, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val uri = LocalUriHandler.current
|
val uri = LocalUriHandler.current
|
||||||
val onClick = remember {
|
val onClick = remember {
|
||||||
{ link: String ->
|
{ link: String ->
|
||||||
@@ -338,7 +336,7 @@ private fun RenderContentAsMarkdown(content: String, tags: ImmutableListOfLists<
|
|||||||
MaterialRichText(
|
MaterialRichText(
|
||||||
style = MaterialTheme.colors.markdownStyle
|
style = MaterialTheme.colors.markdownStyle
|
||||||
) {
|
) {
|
||||||
RefreshableContent(content, tags) {
|
RefreshableContent(content, tags, accountViewModel) {
|
||||||
Markdown(
|
Markdown(
|
||||||
content = it,
|
content = it,
|
||||||
markdownParseOptions = MarkdownParseOptions.Default,
|
markdownParseOptions = MarkdownParseOptions.Default,
|
||||||
@@ -350,13 +348,14 @@ private fun RenderContentAsMarkdown(content: String, tags: ImmutableListOfLists<
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RefreshableContent(content: String, tags: ImmutableListOfLists<String>?, onCompose: @Composable (String) -> Unit) {
|
private fun RefreshableContent(content: String, tags: ImmutableListOfLists<String>?, accountViewModel: AccountViewModel, onCompose: @Composable (String) -> Unit) {
|
||||||
var markdownWithSpecialContent by remember(content) { mutableStateOf<String?>(content) }
|
var markdownWithSpecialContent by remember(content) { mutableStateOf<String?>(content) }
|
||||||
|
|
||||||
ObserverAllNIP19References(content, tags) {
|
ObserverAllNIP19References(content, tags, accountViewModel) {
|
||||||
val newMarkdownWithSpecialContent = returnMarkdownWithSpecialContent(content, tags)
|
accountViewModel.returnMarkdownWithSpecialContent(content, tags) { newMarkdownWithSpecialContent ->
|
||||||
if (markdownWithSpecialContent != newMarkdownWithSpecialContent) {
|
if (markdownWithSpecialContent != newMarkdownWithSpecialContent) {
|
||||||
markdownWithSpecialContent = newMarkdownWithSpecialContent
|
markdownWithSpecialContent = newMarkdownWithSpecialContent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,47 +365,47 @@ private fun RefreshableContent(content: String, tags: ImmutableListOfLists<Strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ObserverAllNIP19References(content: String, tags: ImmutableListOfLists<String>?, onRefresh: () -> Unit) {
|
fun ObserverAllNIP19References(content: String, tags: ImmutableListOfLists<String>?, accountViewModel: AccountViewModel, onRefresh: () -> Unit) {
|
||||||
var nip19References by remember(content) { mutableStateOf<List<Nip19.Return>>(emptyList()) }
|
var nip19References by remember(content) { mutableStateOf<List<Nip19.Return>>(emptyList()) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = content) {
|
LaunchedEffect(key1 = content) {
|
||||||
launch(Dispatchers.IO) {
|
accountViewModel.returnNIP19References(content, tags) {
|
||||||
nip19References = returnNIP19References(content, tags)
|
nip19References = it
|
||||||
onRefresh()
|
onRefresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nip19References.forEach {
|
nip19References.forEach {
|
||||||
ObserveNIP19(it, onRefresh)
|
ObserveNIP19(it, accountViewModel, onRefresh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ObserveNIP19(
|
fun ObserveNIP19(
|
||||||
it: Nip19.Return,
|
it: Nip19.Return,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
onRefresh: () -> Unit
|
onRefresh: () -> Unit
|
||||||
) {
|
) {
|
||||||
if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
|
if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
|
||||||
ObserveNIP19Event(it, onRefresh)
|
ObserveNIP19Event(it, accountViewModel, onRefresh)
|
||||||
} else if (it.type == Nip19.Type.USER) {
|
} else if (it.type == Nip19.Type.USER) {
|
||||||
ObserveNIP19User(it, onRefresh)
|
ObserveNIP19User(it, accountViewModel, onRefresh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ObserveNIP19Event(
|
private fun ObserveNIP19Event(
|
||||||
it: Nip19.Return,
|
it: Nip19.Return,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
onRefresh: () -> Unit
|
onRefresh: () -> Unit
|
||||||
) {
|
) {
|
||||||
var baseNote by remember(it) { mutableStateOf<Note?>(LocalCache.getNoteIfExists(it.hex)) }
|
var baseNote by remember(it) { mutableStateOf<Note?>(accountViewModel.getNoteIfExists(it.hex)) }
|
||||||
|
|
||||||
if (baseNote == null) {
|
if (baseNote == null) {
|
||||||
LaunchedEffect(key1 = it.hex) {
|
LaunchedEffect(key1 = it.hex) {
|
||||||
launch(Dispatchers.IO) {
|
if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
|
||||||
if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
|
accountViewModel.checkGetOrCreateNote(it.hex)?.let { note ->
|
||||||
LocalCache.checkGetOrCreateNote(it.hex)?.let { note ->
|
launch(Dispatchers.Main) { baseNote = note }
|
||||||
launch(Dispatchers.Main) { baseNote = note }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,9 +422,7 @@ fun ObserveNote(note: Note, onRefresh: () -> Unit) {
|
|||||||
|
|
||||||
LaunchedEffect(key1 = loadedNoteId) {
|
LaunchedEffect(key1 = loadedNoteId) {
|
||||||
if (loadedNoteId != null) {
|
if (loadedNoteId != null) {
|
||||||
launch(Dispatchers.IO) {
|
onRefresh()
|
||||||
onRefresh()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,17 +430,16 @@ fun ObserveNote(note: Note, onRefresh: () -> Unit) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ObserveNIP19User(
|
private fun ObserveNIP19User(
|
||||||
it: Nip19.Return,
|
it: Nip19.Return,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
onRefresh: () -> Unit
|
onRefresh: () -> Unit
|
||||||
) {
|
) {
|
||||||
var baseUser by remember(it) { mutableStateOf<User?>(LocalCache.getUserIfExists(it.hex)) }
|
var baseUser by remember(it) { mutableStateOf<User?>(accountViewModel.getUserIfExists(it.hex)) }
|
||||||
|
|
||||||
if (baseUser == null) {
|
if (baseUser == null) {
|
||||||
LaunchedEffect(key1 = it.hex) {
|
LaunchedEffect(key1 = it.hex) {
|
||||||
launch(Dispatchers.IO) {
|
if (it.type == Nip19.Type.USER) {
|
||||||
if (it.type == Nip19.Type.USER) {
|
accountViewModel.checkGetOrCreateUser(it.hex)?.let { user ->
|
||||||
LocalCache.checkGetOrCreateUser(it.hex)?.let { user ->
|
launch(Dispatchers.Main) { baseUser = user }
|
||||||
launch(Dispatchers.Main) { baseUser = user }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,160 +456,11 @@ private fun ObserveUser(user: User, onRefresh: () -> Unit) {
|
|||||||
|
|
||||||
LaunchedEffect(key1 = loadedUserMetaId) {
|
LaunchedEffect(key1 = loadedUserMetaId) {
|
||||||
if (loadedUserMetaId != null) {
|
if (loadedUserMetaId != null) {
|
||||||
launch(Dispatchers.IO) {
|
onRefresh()
|
||||||
onRefresh()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDisplayNameAndNIP19FromTag(tag: String, tags: ImmutableListOfLists<String>): Pair<String, String>? {
|
|
||||||
val matcher = tagIndex.matcher(tag)
|
|
||||||
val (index, suffix) = try {
|
|
||||||
matcher.find()
|
|
||||||
Pair(matcher.group(1)?.toInt(), matcher.group(2) ?: "")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w("Tag Parser", "Couldn't link tag $tag", e)
|
|
||||||
Pair(null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index != null && index >= 0 && index < tags.lists.size) {
|
|
||||||
val tag = tags.lists[index]
|
|
||||||
|
|
||||||
if (tag.size > 1) {
|
|
||||||
if (tag[0] == "p") {
|
|
||||||
LocalCache.checkGetOrCreateUser(tag[1])?.let {
|
|
||||||
return Pair(it.toBestDisplayName(), it.pubkeyNpub())
|
|
||||||
}
|
|
||||||
} else if (tag[0] == "e" || tag[0] == "a") {
|
|
||||||
LocalCache.checkGetOrCreateNote(tag[1])?.let {
|
|
||||||
return Pair(it.idDisplayNote(), it.toNEvent())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDisplayNameFromNip19(nip19: Nip19.Return): Pair<String, String>? {
|
|
||||||
if (nip19.type == Nip19.Type.USER) {
|
|
||||||
LocalCache.users[nip19.hex]?.let {
|
|
||||||
return Pair(it.toBestDisplayName(), it.pubkeyNpub())
|
|
||||||
}
|
|
||||||
} else if (nip19.type == Nip19.Type.NOTE) {
|
|
||||||
LocalCache.notes[nip19.hex]?.let {
|
|
||||||
return Pair(it.idDisplayNote(), it.toNEvent())
|
|
||||||
}
|
|
||||||
} else if (nip19.type == Nip19.Type.ADDRESS) {
|
|
||||||
LocalCache.addressables[nip19.hex]?.let {
|
|
||||||
return Pair(it.idDisplayNote(), it.toNEvent())
|
|
||||||
}
|
|
||||||
} else if (nip19.type == Nip19.Type.EVENT) {
|
|
||||||
LocalCache.notes[nip19.hex]?.let {
|
|
||||||
return Pair(it.idDisplayNote(), it.toNEvent())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun returnNIP19References(content: String, tags: ImmutableListOfLists<String>?): List<Nip19.Return> {
|
|
||||||
val listOfReferences = mutableListOf<Nip19.Return>()
|
|
||||||
content.split('\n').forEach { paragraph ->
|
|
||||||
paragraph.split(' ').forEach { word: String ->
|
|
||||||
if (startsWithNIP19Scheme(word)) {
|
|
||||||
val parsedNip19 = Nip19.uriToRoute(word)
|
|
||||||
parsedNip19?.let {
|
|
||||||
listOfReferences.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tags?.lists?.forEach {
|
|
||||||
if (it[0] == "p" && it.size > 1) {
|
|
||||||
listOfReferences.add(Nip19.Return(Nip19.Type.USER, it[1], null, null, null, ""))
|
|
||||||
} else if (it[0] == "e" && it.size > 1) {
|
|
||||||
listOfReferences.add(Nip19.Return(Nip19.Type.NOTE, it[1], null, null, null, ""))
|
|
||||||
} else if (it[0] == "a" && it.size > 1) {
|
|
||||||
listOfReferences.add(Nip19.Return(Nip19.Type.ADDRESS, it[1], null, null, null, ""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return listOfReferences
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun returnMarkdownWithSpecialContent(content: String, tags: ImmutableListOfLists<String>?): String {
|
|
||||||
var returnContent = ""
|
|
||||||
content.split('\n').forEach { paragraph ->
|
|
||||||
paragraph.split(' ').forEach { word: String ->
|
|
||||||
if (isValidURL(word)) {
|
|
||||||
val removedParamsFromUrl = word.split("?")[0].lowercase()
|
|
||||||
if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) {
|
|
||||||
returnContent += " "
|
|
||||||
} else {
|
|
||||||
returnContent += "[$word]($word) "
|
|
||||||
}
|
|
||||||
} else if (Patterns.EMAIL_ADDRESS.matcher(word).matches()) {
|
|
||||||
returnContent += "[$word](mailto:$word) "
|
|
||||||
} else if (Patterns.PHONE.matcher(word).matches() && word.length > 6) {
|
|
||||||
returnContent += "[$word](tel:$word) "
|
|
||||||
} else if (startsWithNIP19Scheme(word)) {
|
|
||||||
val parsedNip19 = Nip19.uriToRoute(word)
|
|
||||||
returnContent += if (parsedNip19 !== null) {
|
|
||||||
val pair = getDisplayNameFromNip19(parsedNip19)
|
|
||||||
if (pair != null) {
|
|
||||||
val (displayName, nip19) = pair
|
|
||||||
"[$displayName](nostr:$nip19) "
|
|
||||||
} else {
|
|
||||||
"$word "
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"$word "
|
|
||||||
}
|
|
||||||
} else if (word.startsWith("#")) {
|
|
||||||
if (tagIndex.matcher(word).matches() && tags != null) {
|
|
||||||
val pair = getDisplayNameAndNIP19FromTag(word, tags)
|
|
||||||
if (pair != null) {
|
|
||||||
returnContent += "[${pair.first}](nostr:${pair.second}) "
|
|
||||||
} else {
|
|
||||||
returnContent += "$word "
|
|
||||||
}
|
|
||||||
} else if (hashTagsPattern.matcher(word).matches()) {
|
|
||||||
val hashtagMatcher = hashTagsPattern.matcher(word)
|
|
||||||
|
|
||||||
val (myTag, mySuffix) = try {
|
|
||||||
hashtagMatcher.find()
|
|
||||||
Pair(hashtagMatcher.group(1), hashtagMatcher.group(2))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("Hashtag Parser", "Couldn't link hashtag $word", e)
|
|
||||||
Pair(null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (myTag != null) {
|
|
||||||
returnContent += "[#$myTag](nostr:Hashtag?id=$myTag)$mySuffix "
|
|
||||||
} else {
|
|
||||||
returnContent += "$word "
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
returnContent += "$word "
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
returnContent += "$word "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
returnContent += "\n"
|
|
||||||
}
|
|
||||||
return returnContent
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startsWithNIP19Scheme(word: String): Boolean {
|
|
||||||
val cleaned = word.lowercase().removePrefix("@").removePrefix("nostr:").removePrefix("@")
|
|
||||||
|
|
||||||
return listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1").any { cleaned.startsWith(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class LoadedBechLink(val baseNote: Note?, val nip19: Nip19.Return)
|
data class LoadedBechLink(val baseNote: Note?, val nip19: Nip19.Return)
|
||||||
|
|
||||||
|
@@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.service.OnlineChecker
|
|||||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.ui.actions.Dao
|
import com.vitorpamplona.amethyst.ui.actions.Dao
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.MarkdownParser
|
||||||
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
|
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus
|
import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus
|
||||||
@@ -34,8 +35,10 @@ import com.vitorpamplona.amethyst.ui.note.showAmount
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.CombinedZap
|
import com.vitorpamplona.amethyst.ui.screen.CombinedZap
|
||||||
import com.vitorpamplona.quartz.encoders.ATag
|
import com.vitorpamplona.quartz.encoders.ATag
|
||||||
import com.vitorpamplona.quartz.encoders.HexKey
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
|
import com.vitorpamplona.quartz.encoders.Nip19
|
||||||
import com.vitorpamplona.quartz.events.Event
|
import com.vitorpamplona.quartz.events.Event
|
||||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||||
|
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||||
import com.vitorpamplona.quartz.events.Participant
|
import com.vitorpamplona.quartz.events.Participant
|
||||||
@@ -677,6 +680,18 @@ class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun returnNIP19References(content: String, tags: ImmutableListOfLists<String>?, onNewReferences: (List<Nip19.Return>) -> Unit) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
onNewReferences(MarkdownParser().returnNIP19References(content, tags))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun returnMarkdownWithSpecialContent(content: String, tags: ImmutableListOfLists<String>?, onNewContent: (String) -> Unit) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
onNewContent(MarkdownParser().returnMarkdownWithSpecialContent(content, tags))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Factory(val account: Account) : ViewModelProvider.Factory {
|
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||||
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
|
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
|
||||||
return AccountViewModel(account) as AccountViewModel
|
return AccountViewModel(account) as AccountViewModel
|
||||||
|
Reference in New Issue
Block a user