package com.peachpasswords.ui.vault import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.sp /* * Note bodies are authored in the browser's contenteditable, so a synced note * arrives as HTML: the extension's sanitizer allow-list (NoteEditor.tsx) is * b, strong, i, em, u, s, h2, h3, blockquote, ul, ol, li, br, hr, p — attribute-less — plus * the div wrappers Chrome's contenteditable inserts for new lines. Android read * that field as plain text, which is why the phone showed raw tags. * * Parsed here rather than through HtmlCompat: the vocabulary is fifteen * attribute-less tags, host-JVM unit tests cannot touch android.text, and * Html.toHtml serialises back as `

` — outside the * allow-list, so it could not round-trip to the browser. * * Compose drops AnnotatedString spans on every keystroke, so there is no sound * WYSIWYG editing surface. Editing therefore goes through a plain-text markup * (`**bold**`, `## heading`, `> quote`, `- item`) that converts back to the same tag * vocabulary on save. The markup never leaves the device — it exists only * between load and save. */ /** Inline marks, longest markup first so `**` wins over `*` when scanning. */ private enum class Mark(val markup: String, val tag: String) { BOLD("**", "b"), UNDERLINE("__", "u"), STRIKE("~~", "s"), ITALIC("*", "i"), } private val TAG_HINT = Regex("]*>") private val WHITESPACE = Regex("\\s+") private val ORDERED_ITEM = Regex("^\\d+\\. ") /** Same test the browser's toHtml uses to decide plain text vs markup. */ internal fun isNoteHtml(raw: String): Boolean = TAG_HINT.containsMatchIn(raw) /** Stored note -> the text the editor shows and the user edits. */ internal fun noteEditorText(stored: String): String = if (isNoteHtml(stored)) htmlToMarkup(stored) else stored /** * Editor text -> stored note. A note that arrived as plain text stays plain * text: markup is only interpreted once the user opts into formatting, so a * shopping list with "- eggs" in it is not silently turned into a `