From f265498ed3c475d206721f43556563d68913042e Mon Sep 17 00:00:00 2001 From: Nino Date: Mon, 27 Jul 2026 20:56:59 -0600 Subject: [PATCH] v1.0.7: Fix false offline modifications causing 'untitled' pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause chain: 1. JS _pendingSet cleared prematurely in onReady (0ms), letting onChange fire during initial content load and set userEdited=true 2. onAutoSave didn't check isEditorReadOnly, firing even for read-only pages 3. Markdown round-trip (extractText → markdownToBlocks → blocksToMarkdown) produced different output, triggering false change detection 4. onSaveContent passed null for title when no title changes, which CachedApi converted to empty string, causing server to set title to 'untitled' Fixes: - JS: Remove premature _pendingSet clearing in onReady; increase timeout to 500ms - Kotlin: onAutoSave checks isEditorReadOnly (no auto-save in read-only mode) - Kotlin: Save operations always pass current title (never null) to prevent empty title being sent to server - Kotlin: Add isContentLoading flag to suppress onContentChanged during initial content load, preventing false currentPageContent updates - Added APK versioned naming for Obtanium compatibility --- app/build.gradle.kts | 11 +++++++++-- app/src/main/assets/editor/index.html | 11 ++++------- .../com/docmost/app/ui/PageEditorActivity.kt | 16 +++++++++++----- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1e76a0c..1e7b162 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,8 +20,8 @@ android { applicationId = "com.docmost.app" minSdk = 26 targetSdk = 34 - versionCode = 7 - versionName = "1.0.6" + versionCode = 8 + versionName = "1.0.7" } signingConfigs { @@ -58,6 +58,13 @@ android { viewBinding = true buildConfig = true } + + applicationVariants.all { + outputs.all { + val output = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl + output.outputFileName = "Docmost-app-v${versionName}-${buildType.name}.apk" + } + } } dependencies { diff --git a/app/src/main/assets/editor/index.html b/app/src/main/assets/editor/index.html index bc25e4b..c498de9 100644 --- a/app/src/main/assets/editor/index.html +++ b/app/src/main/assets/editor/index.html @@ -531,12 +531,9 @@ } else { console.log('AndroidInterface not available yet'); } - // Android controla la carga de contenido via setContent() - // No cargar pendingAndroidContent automáticamente - // Clear pending set flag on next tick after initialization - setTimeout(function() { - window._pendingSet = false; - }, 0); + // _pendingSet is cleared by setContent() after 100ms + // Do NOT clear it here — doing so prematurely lets onChange + // fire during initial content load and sets userEdited=true // Configurar observer para detectar cambios en bloques vacíos setTimeout(function() { setupBackspaceHandler(); @@ -617,7 +614,7 @@ window.setContentLock = false; setTimeout(function() { window._pendingSet = false; - }, 100); + }, 500); }; window.getContent = function() { diff --git a/app/src/main/java/com/docmost/app/ui/PageEditorActivity.kt b/app/src/main/java/com/docmost/app/ui/PageEditorActivity.kt index c38bf1c..8297b1e 100644 --- a/app/src/main/java/com/docmost/app/ui/PageEditorActivity.kt +++ b/app/src/main/java/com/docmost/app/ui/PageEditorActivity.kt @@ -283,6 +283,7 @@ class PageEditorActivity : AppCompatActivity() { } private var isEditorRendered = false + private var isContentLoading = false private var pendingContent: String? = null private var isTitleVisible = true private var isAnimating = false @@ -627,6 +628,7 @@ class PageEditorActivity : AppCompatActivity() { @JavascriptInterface fun onContentChanged(markdown: String) { + if (isContentLoading) return currentPageContent = markdown } @@ -640,12 +642,15 @@ class PageEditorActivity : AppCompatActivity() { loadEditorContentInternal(content) pendingContent = null } + Handler(Looper.getMainLooper()).postDelayed({ + isContentLoading = false + }, 600) } } @JavascriptInterface fun onAutoSave(markdown: String) { - if (!canEdit) return + if (!canEdit || isEditorReadOnly) return runOnUiThread { val title = binding.etTitle.text.toString().trim() @@ -698,14 +703,14 @@ class PageEditorActivity : AppCompatActivity() { try { cachedApi.updatePage( currentPageId, markdown, - if (hasTitleChanges) title else null, + title, if (hasEmojiChanges) currentEmoji else null ) val cachedPage = pageCacheManager.getCachedPage(currentPageId) if (cachedPage != null) { pageCacheManager.cachePage(cachedPage.copy( - title = if (hasTitleChanges) title else cachedPage.title, + title = title, icon = if (hasEmojiChanges) currentEmoji else cachedPage.icon, content = markdown )) @@ -740,7 +745,7 @@ class PageEditorActivity : AppCompatActivity() { try { cachedApi.updatePage( currentPageId, null, - if (hasTitleChanges) title else null, + title, if (hasEmojiChanges) currentEmoji else null ) } catch (e: Exception) { @@ -768,7 +773,7 @@ class PageEditorActivity : AppCompatActivity() { cachedApi.updatePage( pageId!!, if (hasBodyChanges) markdown else null, - if (hasTitleChanges) title else null, + title, if (pageEmoji != originalEmoji) pageEmoji else null ) withContext(Dispatchers.Main) { @@ -1004,6 +1009,7 @@ class PageEditorActivity : AppCompatActivity() { private fun loadEditorContent(markdown: String) { if (BuildConfig.DEBUG) android.util.Log.d("PageEditor", "loadEditorContent called with ${markdown.length} chars") + isContentLoading = true if (isEditorRendered) { loadEditorContentInternal(markdown) } else {