2 Commits

Author SHA1 Message Date
922d31b7df v1.0.5: Fix history viewer - ContentConverter JsonElement type bug
- ContentConverter.tipTapToHtml() had same JsonObject.asMap() bug as tiptapToMarkdown
- All as? String casts silently returned null, producing empty HTML
- Now uses Gson TypeToken<Map<String, Any?>> for proper deserialization
- Added contentToHtml() that handles String (HTML), Map (TipTap), and JSON string
- Fixed heading level cast (Gson deserializes numbers as Double, not Int)
- VersionViewerActivity handles all content types correctly
2026-07-27 17:39:48 -06:00
ae57a0bf09 v1.0.4: Fix tiptapToMarkdown JsonElement type casting
- tiptapToMarkdown used JsonObject.asMap() which returns Map<String, JsonElement>
- All 'as? String' casts silently failed (JsonPrimitive != String), returning empty
- Now uses Gson TypeToken<Map<String, Any?>> for proper raw type deserialization
- contentToMarkdown now handles Map types directly without serialize roundtrip
2026-07-27 17:32:47 -06:00
4 changed files with 67 additions and 19 deletions

View File

@@ -20,8 +20,8 @@ android {
applicationId = "com.docmost.app"
minSdk = 26
targetSdk = 34
versionCode = 4
versionName = "1.0.3"
versionCode = 6
versionName = "1.0.5"
}
signingConfigs {

View File

@@ -1877,9 +1877,9 @@ class PageEditorActivity : AppCompatActivity() {
private fun tiptapToMarkdown(tiptapJson: String): String {
return try {
val doc = gson.fromJson(tiptapJson, com.google.gson.JsonObject::class.java).asMap()
@Suppress("UNCHECKED_CAST")
extractText(doc as Map<String, Any?>)
val type = object : com.google.gson.reflect.TypeToken<Map<String, Any?>>() {}.type
val doc: Map<String, Any?> = gson.fromJson(tiptapJson, type)
extractText(doc)
} catch (e: Exception) {
tiptapJson
}
@@ -2492,6 +2492,15 @@ class PageEditorActivity : AppCompatActivity() {
content
}
}
if (content is Map<*, *>) {
@Suppress("UNCHECKED_CAST")
return try {
extractText(content as Map<String, Any?>)
} catch (e: Exception) {
android.util.Log.e("PageEditor", "contentToMarkdown map error: ${e.message}")
""
}
}
return try {
val json = gson.toJson(content)
if (json.isNotEmpty() && json != "null") {

View File

@@ -109,13 +109,26 @@ class VersionViewerActivity : AppCompatActivity() {
CoroutineScope(Dispatchers.IO).launch {
try {
val history = api.getPageHistoryInfo(pageId, historyId)
val htmlContent = if (history.content != null) {
val gson = Gson()
val json = gson.toJson(history.content)
originalTipTapJson = json
ContentConverter.tipTapToHtml(json)
val htmlContent: String
if (history.content != null) {
val content = history.content
if (content is String && content.trimStart().startsWith("<")) {
htmlContent = content
originalTipTapJson = ""
} else if (content is Map<*, *>) {
val gson = Gson()
originalTipTapJson = gson.toJson(content)
htmlContent = ContentConverter.contentToHtml(content)
} else if (content is String) {
originalTipTapJson = content
htmlContent = ContentConverter.tipTapToHtml(content)
} else {
htmlContent = "<p>Sin contenido</p>"
originalTipTapJson = ""
}
} else {
"<p>Sin contenido</p>"
htmlContent = "<p>Sin contenido</p>"
originalTipTapJson = ""
}
withContext(Dispatchers.Main) {

View File

@@ -1,16 +1,16 @@
package com.docmost.app.utils
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.reflect.TypeToken
object ContentConverter {
private val gson = Gson()
fun tipTapJsonToHtml(tipTapJson: String): String {
return try {
val doc = gson.fromJson(tipTapJson, JsonObject::class.java).asMap()
@Suppress("UNCHECKED_CAST")
nodeToHtml(doc as Map<String, Any?>)
val type = object : TypeToken<Map<String, Any?>>() {}.type
val doc: Map<String, Any?> = gson.fromJson(tipTapJson, type)
nodeToHtml(doc)
} catch (e: Exception) {
tipTapJson
}
@@ -18,14 +18,34 @@ object ContentConverter {
fun tipTapToHtml(tipTapJson: String): String {
return try {
val doc = gson.fromJson(tipTapJson, JsonObject::class.java).asMap()
@Suppress("UNCHECKED_CAST")
nodeToHtml(doc as Map<String, Any?>)
val type = object : TypeToken<Map<String, Any?>>() {}.type
val doc: Map<String, Any?> = gson.fromJson(tipTapJson, type)
nodeToHtml(doc)
} catch (e: Exception) {
tipTapJson
}
}
fun contentToHtml(content: Any?): String {
if (content == null) return "<p>Sin contenido</p>"
if (content is String) {
return if (content.trimStart().startsWith("<")) {
content
} else {
tipTapToHtml(content)
}
}
if (content is Map<*, *>) {
@Suppress("UNCHECKED_CAST")
return try {
nodeToHtml(content as Map<String, Any?>)
} catch (e: Exception) {
"<p>Error converting content</p>"
}
}
return "<p>Sin contenido</p>"
}
private fun nodeToHtml(node: Map<String, Any?>): String {
val type = node["type"] as? String
@@ -74,7 +94,13 @@ object ContentConverter {
sb.append("</p>")
}
"heading" -> {
val level = (node["attrs"] as? Map<*, *>)?.get("level") as? Int ?: 1
val levelRaw = (node["attrs"] as? Map<*, *>)?.get("level")
val level = when (levelRaw) {
is Int -> levelRaw
is Double -> levelRaw.toInt()
is Number -> levelRaw.toInt()
else -> 1
}
sb.append("<h$level>")
content?.forEach { child ->
if (child is Map<*, *>) {