Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,387 @@
package zed.rainxch.core.domain.util

object EmojiShortcodes {
// Subset of github/gemoji that covers the long tail of READMEs.
// ~250 entries — picked from gemoji download stats + common usage in
// README/CHANGELOG headers. Not exhaustive on purpose; missing
// shortcodes survive as literal text rather than ballooning the
// bundle with a 1500-entry table we mostly don't need.
private val TABLE: Map<String, String> =
mapOf(
// Frequently used in README hero / status sections
"rocket" to "🚀",
"sparkles" to "✨",
"tada" to "🎉",
"fire" to "🔥",
"star" to "⭐",
"star2" to "🌟",
"boom" to "💥",
"zap" to "⚡",
"muscle" to "💪",
"raised_hands" to "🙌",
"tophat" to "🎩",
"crown" to "👑",
"trophy" to "🏆",
"medal" to "🎖️",
"100" to "💯",
"ok_hand" to "👌",
"pray" to "🙏",
"thumbsup" to "👍",
"+1" to "👍",
"thumbsdown" to "👎",
"-1" to "👎",
"clap" to "👏",
"wave" to "👋",
"point_right" to "👉",
"point_left" to "👈",
"point_up" to "☝️",
"point_down" to "👇",
// Status / signals
"warning" to "⚠️",
"white_check_mark" to "✅",
"heavy_check_mark" to "✔️",
"ballot_box_with_check" to "☑️",
"x" to "❌",
"negative_squared_cross_mark" to "❎",
"heavy_multiplication_x" to "✖️",
"no_entry" to "⛔",
"no_entry_sign" to "🚫",
"exclamation" to "❗",
"heavy_exclamation_mark" to "❗",
"bangbang" to "‼️",
"interrobang" to "⁉️",
"question" to "❓",
"grey_question" to "❔",
"grey_exclamation" to "❕",
"information_source" to "ℹ️",
// Hearts
"heart" to "❤️",
"yellow_heart" to "💛",
"green_heart" to "💚",
"blue_heart" to "💙",
"purple_heart" to "💜",
"black_heart" to "🖤",
"white_heart" to "🤍",
"orange_heart" to "🧡",
"brown_heart" to "🤎",
"broken_heart" to "💔",
"heart_eyes" to "😍",
"hearts" to "♥️",
// Faces
"smile" to "😄",
"smiley" to "😃",
"grin" to "😁",
"laughing" to "😆",
"joy" to "😂",
"wink" to "😉",
"blush" to "😊",
"innocent" to "😇",
"sunglasses" to "😎",
"thinking" to "🤔",
"raised_eyebrow" to "🤨",
"hugs" to "🤗",
"kissing_heart" to "😘",
"smirk" to "😏",
"rolling_eyes" to "🙄",
"neutral_face" to "😐",
"expressionless" to "😑",
"no_mouth" to "😶",
"face_with_monocle" to "🧐",
"nerd_face" to "🤓",
"exploding_head" to "🤯",
"cold_face" to "🥶",
"hot_face" to "🥵",
"partying_face" to "🥳",
"sob" to "😭",
"cry" to "😢",
"weary" to "😩",
"scream" to "😱",
"angry" to "😠",
"rage" to "😡",
"skull" to "💀",
"skull_and_crossbones" to "☠️",
"ghost" to "👻",
"alien" to "👽",
"robot" to "🤖",
"poop" to "💩",
"hankey" to "💩",
"shit" to "💩",
// Dev / engineer
"computer" to "💻",
"iphone" to "📱",
"watch" to "⌚",
"keyboard" to "⌨️",
"mouse_three_button" to "🖱️",
"printer" to "🖨️",
"minidisc" to "💽",
"floppy_disk" to "💾",
"cd" to "💿",
"dvd" to "📀",
"package" to "📦",
"outbox_tray" to "📤",
"inbox_tray" to "📥",
"envelope" to "✉️",
"email" to "📧",
"mailbox" to "📫",
"satellite" to "📡",
"tv" to "📺",
"camera" to "📷",
"camera_flash" to "📸",
"video_camera" to "📹",
"movie_camera" to "🎥",
"film_projector" to "📽️",
"telephone" to "☎️",
"phone" to "☎️",
"calling" to "📲",
"vibration_mode" to "📳",
"loud_sound" to "🔊",
"sound" to "🔉",
"speaker" to "🔈",
"mute" to "🔇",
"mega" to "📣",
"loudspeaker" to "📢",
// Tools / build
"hammer" to "🔨",
"wrench" to "🔧",
"screwdriver" to "🪛",
"nut_and_bolt" to "🔩",
"hammer_and_wrench" to "🛠️",
"toolbox" to "🧰",
"gear" to "⚙️",
"factory" to "🏭",
"construction" to "🚧",
"test_tube" to "🧪",
"petri_dish" to "🧫",
"dna" to "🧬",
"microscope" to "🔬",
"telescope" to "🔭",
"satellite_antenna" to "📡",
"magnet" to "🧲",
"balance_scale" to "⚖️",
"scales" to "⚖️",
// Symbols
"bug" to "🐛",
"ant" to "🐜",
"bulb" to "💡",
"lock" to "🔒",
"unlock" to "🔓",
"key" to "🔑",
"old_key" to "🗝️",
"shield" to "🛡️",
"scroll" to "📜",
"page_facing_up" to "📄",
"page_with_curl" to "📃",
"bookmark" to "🔖",
"bookmark_tabs" to "📑",
"books" to "📚",
"book" to "📖",
"notebook" to "📓",
"ledger" to "📒",
"memo" to "📝",
"pencil" to "📝",
"pencil2" to "✏️",
"black_nib" to "✒️",
"lower_left_fountain_pen" to "🖋️",
"lower_left_ballpoint_pen" to "🖊️",
"lower_left_paintbrush" to "🖌️",
"art" to "🎨",
"fireworks" to "🎆",
"balloon" to "🎈",
"gift" to "🎁",
"ribbon" to "🎀",
"confetti_ball" to "🎊",
"label" to "🏷️",
"round_pushpin" to "📍",
"pushpin" to "📌",
"paperclip" to "📎",
"link" to "🔗",
"chains" to "⛓️",
"triangular_flag_on_post" to "🚩",
"checkered_flag" to "🏁",
"rainbow" to "🌈",
"umbrella" to "☔",
"snowflake" to "❄️",
"snowman" to "⛄",
"sun_with_face" to "🌞",
"full_moon_with_face" to "🌝",
"earth_americas" to "🌎",
"earth_africa" to "🌍",
"earth_asia" to "🌏",
"globe_with_meridians" to "🌐",
// Animals frequently in READMEs
"octocat" to "🐙",
"cat" to "🐱",
"dog" to "🐶",
"rabbit" to "🐰",
"tiger" to "🐯",
"bear" to "🐻",
"panda_face" to "🐼",
"wolf" to "🐺",
"fox_face" to "🦊",
"unicorn" to "🦄",
"dragon" to "🐉",
"snake" to "🐍",
"whale" to "🐳",
"fish" to "🐟",
"penguin" to "🐧",
"owl" to "🦉",
"bird" to "🐦",
"chicken" to "🐔",
"duck" to "🦆",
"frog" to "🐸",
"monkey_face" to "🐵",
"monkey" to "🐒",
"see_no_evil" to "🙈",
"hear_no_evil" to "🙉",
"speak_no_evil" to "🙊",
// Food / coffee
"coffee" to "☕",
"tea" to "🍵",
"beer" to "🍺",
"beers" to "🍻",
"cocktail" to "🍸",
"wine_glass" to "🍷",
"champagne" to "🍾",
"tropical_drink" to "🍹",
"cake" to "🍰",
"birthday" to "🎂",
"cookie" to "🍪",
"doughnut" to "🍩",
"candy" to "🍬",
"lollipop" to "🍭",
"chocolate_bar" to "🍫",
"pizza" to "🍕",
"hamburger" to "🍔",
"fries" to "🍟",
"hot_dog" to "🌭",
"taco" to "🌮",
"burrito" to "🌯",
"sushi" to "🍣",
"ramen" to "🍜",
"rice" to "🍚",
"spaghetti" to "🍝",
"bread" to "🍞",
"cheese" to "🧀",
"egg" to "🥚",
"bacon" to "🥓",
"salad" to "🥗",
"apple" to "🍎",
"green_apple" to "🍏",
"banana" to "🍌",
"lemon" to "🍋",
"grapes" to "🍇",
"watermelon" to "🍉",
"strawberry" to "🍓",
"cherries" to "🍒",
"peach" to "🍑",
"pineapple" to "🍍",
"coconut" to "🥥",
"avocado" to "🥑",
"kiwi_fruit" to "🥝",
"carrot" to "🥕",
"corn" to "🌽",
"tomato" to "🍅",
"potato" to "🥔",
"hot_pepper" to "🌶️",
"mushroom" to "🍄",
"shamrock" to "☘️",
"leaves" to "🍃",
"fallen_leaf" to "🍂",
"maple_leaf" to "🍁",
"herb" to "🌿",
"evergreen_tree" to "🌲",
"deciduous_tree" to "🌳",
"palm_tree" to "🌴",
"cactus" to "🌵",
"tulip" to "🌷",
"sunflower" to "🌻",
"rose" to "🌹",
"wilted_flower" to "🥀",
"bouquet" to "💐",
"cherry_blossom" to "🌸",
// Travel / weather
"airplane" to "✈️",
"rocket_ship" to "🚀",
"car" to "🚗",
"taxi" to "🚕",
"bus" to "🚌",
"train" to "🚂",
"bike" to "🚲",
"house" to "🏠",
"house_with_garden" to "🏡",
"office" to "🏢",
"school" to "🏫",
"hospital" to "🏥",
"bank" to "🏦",
"atm" to "🏧",
"hotel" to "🏨",
"convenience_store" to "🏪",
"department_store" to "🏬",
"japanese_castle" to "🏯",
"european_castle" to "🏰",
"mountain" to "⛰️",
"volcano" to "🌋",
"sunny" to "☀️",
"cloud" to "☁️",
"partly_sunny" to "⛅",
"umbrella_with_rain_drops" to "☔",
"thunder_cloud_and_rain" to "⛈️",
"lightning" to "🌩️",
"tornado" to "🌪️",
)

// [a-z0-9_+\-] is a superset of github shortcode chars. The
// bookend `:` markers must be tight against the token (no spaces),
// and the shortcode itself must not embed `:` (so URL like
// `https://example.com:8080` doesn't grab `8080` as a code).
private val PATTERN = Regex(""":([a-z0-9_+\-]{1,40}):""")

fun render(input: String): String {
if (input.isEmpty()) return input
if (!input.contains(':')) return input
return splitOutCodeRegions(input).joinToString("") { (chunk, isCode) ->
if (isCode) chunk else replaceInText(chunk)
}
}

private fun replaceInText(text: String): String =
PATTERN.replace(text) { match ->
val key = match.groupValues[1]
TABLE[key] ?: match.value
}

// Split the markdown body into alternating (text, code) regions
// anchored on triple-backtick fences. Single-backtick inline code
// is left in the text regions on purpose — losing rare emoji
// collisions in inline code is fine; properly tokenising inline
// spans isn't worth the parser complexity.
private data class Chunk(val content: String, val isCode: Boolean)

private fun splitOutCodeRegions(input: String): List<Chunk> {
val fence = "```"
if (!input.contains(fence)) return listOf(Chunk(input, isCode = false))
val out = mutableListOf<Chunk>()
var i = 0
var inCode = false
while (i < input.length) {
val next = input.indexOf(fence, startIndex = i)
if (next < 0) {
out += Chunk(input.substring(i), isCode = inCode)
break
}
// Take everything up to and including the fence into the
// current region — the fence itself is part of the code
// block when we're transitioning out, so always include
// 3 chars in the "code" side.
if (inCode) {
out += Chunk(input.substring(i, next + fence.length), isCode = true)
} else {
out += Chunk(input.substring(i, next), isCode = false)
out += Chunk(fence, isCode = true)
}
i = next + fence.length
inCode = !inCode
Comment on lines +361 to +383
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fence parsing toggles on any ``` occurrence, not actual markdown fence lines.

Current logic treats every triple-backtick substring as a fence delimiter. That can mis-split regions when backticks appear in normal text/content, causing incorrect emoji replacement scope.

Proposed direction (line-based fence detection)
-    private fun splitOutCodeRegions(input: String): List<Chunk> {
-        val fence = "```"
-        if (!input.contains(fence)) return listOf(Chunk(input, isCode = false))
-        val out = mutableListOf<Chunk>()
-        var i = 0
-        var inCode = false
-        while (i < input.length) {
-            val next = input.indexOf(fence, startIndex = i)
-            if (next < 0) {
-                out += Chunk(input.substring(i), isCode = inCode)
-                break
-            }
-            if (inCode) {
-                out += Chunk(input.substring(i, next + fence.length), isCode = true)
-            } else {
-                out += Chunk(input.substring(i, next), isCode = false)
-                out += Chunk(fence, isCode = true)
-            }
-            i = next + fence.length
-            inCode = !inCode
-        }
-        return out
-    }
+    private fun splitOutCodeRegions(input: String): List<Chunk> {
+        val lines = input.split('\n')
+        if (lines.none { it.matches(Regex("""^\s{0,3}```.*$""")) }) {
+            return listOf(Chunk(input, isCode = false))
+        }
+        val out = mutableListOf<Chunk>()
+        val buffer = StringBuilder()
+        var inCode = false
+
+        fun flush() {
+            if (buffer.isNotEmpty()) {
+                out += Chunk(buffer.toString(), isCode = inCode)
+                buffer.clear()
+            }
+        }
+
+        lines.forEachIndexed { idx, line ->
+            val isFenceLine = line.matches(Regex("""^\s{0,3}```.*$"""))
+            if (isFenceLine) {
+                flush()
+                out += Chunk(line + if (idx != lines.lastIndex) "\n" else "", isCode = true)
+                inCode = !inCode
+            } else {
+                buffer.append(line)
+                if (idx != lines.lastIndex) buffer.append('\n')
+            }
+        }
+        flush()
+        return out
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val fence = "```"
if (!input.contains(fence)) return listOf(Chunk(input, isCode = false))
val out = mutableListOf<Chunk>()
var i = 0
var inCode = false
while (i < input.length) {
val next = input.indexOf(fence, startIndex = i)
if (next < 0) {
out += Chunk(input.substring(i), isCode = inCode)
break
}
// Take everything up to and including the fence into the
// current region — the fence itself is part of the code
// block when we're transitioning out, so always include
// 3 chars in the "code" side.
if (inCode) {
out += Chunk(input.substring(i, next + fence.length), isCode = true)
} else {
out += Chunk(input.substring(i, next), isCode = false)
out += Chunk(fence, isCode = true)
}
i = next + fence.length
inCode = !inCode
private fun splitOutCodeRegions(input: String): List<Chunk> {
val lines = input.split('\n')
if (lines.none { it.matches(Regex("""^\s{0,3}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/util/EmojiShortcodes.kt`
around lines 361 - 383, The current split logic treats every triple-backtick
substring as a fence, misclassifying inline backticks; replace the
substring-based parsing with a line-oriented detector in splitOutCodeRegions so
fences are only recognized when they occur on their own (up to 3 leading spaces)
using a regex like ^\s{0,3}```.*$; iterate lines, accumulate non-fence lines
into a buffer and flush into Chunk(buffer, isCode=current) when encountering a
fence line, add the fence line itself as a Chunk(isCode=true) (preserve trailing
newlines between lines), toggle inCode on fence lines, and return the collected
List<Chunk> so emoji replacement only ignores true markdown code blocks.

}
return out
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"bullets": [
"Manually linking apps — sorted by installer source (F-Droid / Obtainium first, Play Store and system updates last) with a chip showing each app's source.",
"Manual link now suggests matching GitHub repos automatically — pick an app, get ranked candidates, tap to link. Manual URL entry still available.",
"GitHub-style alert callouts in README and release notes — Note, Tip, Important, Warning, Caution now render as tinted cards with icons instead of literal text."
"GitHub-style alert callouts in README and release notes — Note, Tip, Important, Warning, Caution now render as tinted cards with icons instead of literal text.",
"Emoji shortcodes in README and release notes — :rocket: now renders as 🚀, :tada: as 🎉, and ~250 others. Common dev/status icons covered out of the box."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Count the actual number of emoji shortcodes in the implementation

# Search for the shortcode map definition in EmojiShortcodes
ast-grep --pattern 'mapOf(
  $$$
)'  | rg -c ':\w+:' || echo "Pattern not found, trying alternative search"

# Alternative: search for shortcode entries in the file
rg -o '"\w+"\s+to\s+".' core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/util/EmojiShortcodes.kt | wc -l

Repository: OpenHub-Store/GitHub-Store

Length of output: 78


Update emoji shortcode count to reflect actual implementation.

The JSON file states "~250 others" but verification reveals the implementation contains approximately 330 shortcodes total, not ~250. Update the text to accurately reflect this count.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/presentation/src/commonMain/composeResources/files/whatsnew/18.json` at
line 32, The whatsnew JSON entry string "Emoji shortcodes in README and release
notes — :rocket: now renders as 🚀, :tada: as 🎉, and ~250 others. Common
dev/status icons covered out of the box." has an inaccurate shortcode count;
update that text to reflect the implemented ~330 shortcodes (e.g., replace "~250
others" with "~330 others" or "around 330 others") so the entry accurately
matches the implementation.

]
}
]
Expand Down
Loading