-
-
Notifications
You must be signed in to change notification settings - Fork 65
feat(rgb): enhance decodeLegacy with HTML tags, raw hex & format modi… #244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: birdflop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,16 +1,19 @@ | ||||||||||||||||||||||||||||||||||||||
| function hexToHSL(hex: string) { | ||||||||||||||||||||||||||||||||||||||
| const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); | ||||||||||||||||||||||||||||||||||||||
| if (!result) return { h: 100, s: 100, l: 100 }; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const r = parseInt(result[1], 16) / 255; | ||||||||||||||||||||||||||||||||||||||
| const g = parseInt(result[2], 16) / 255; | ||||||||||||||||||||||||||||||||||||||
| const b = parseInt(result[3], 16) / 255; | ||||||||||||||||||||||||||||||||||||||
| const max = Math.max(r, g, b), | ||||||||||||||||||||||||||||||||||||||
| min = Math.min(r, g, b); | ||||||||||||||||||||||||||||||||||||||
| const max = Math.max(r, g, b); | ||||||||||||||||||||||||||||||||||||||
| const min = Math.min(r, g, b); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| let h = 0; | ||||||||||||||||||||||||||||||||||||||
| let s, | ||||||||||||||||||||||||||||||||||||||
| l = (max + min) / 2; | ||||||||||||||||||||||||||||||||||||||
| let s = 0; | ||||||||||||||||||||||||||||||||||||||
| let l = (max + min) / 2; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (max === min) { | ||||||||||||||||||||||||||||||||||||||
| h = s = 0; // achromatic | ||||||||||||||||||||||||||||||||||||||
| h = s = 0; | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| const d = max - min; | ||||||||||||||||||||||||||||||||||||||
| s = l > 0.5 ? d / (2 - max - min) : d / (max + min); | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -36,43 +39,39 @@ function hexToHSL(hex: string) { | |||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export function getSignificantPoints(gradient: string[], threshold: number) { | ||||||||||||||||||||||||||||||||||||||
| // Convert all colors to HSL | ||||||||||||||||||||||||||||||||||||||
| const hslColors = gradient.map(hexToHSL); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Calculate differences between consecutive colors | ||||||||||||||||||||||||||||||||||||||
| const differences = []; | ||||||||||||||||||||||||||||||||||||||
| for (let i = 1; i < hslColors.length; i++) { | ||||||||||||||||||||||||||||||||||||||
| const hDiff = Math.abs(hslColors[i].h - hslColors[i - 1].h); | ||||||||||||||||||||||||||||||||||||||
| const sDiff = Math.abs(hslColors[i].s - hslColors[i - 1].s); | ||||||||||||||||||||||||||||||||||||||
| const lDiff = Math.abs(hslColors[i].l - hslColors[i - 1].l); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Weight hue, saturation, and lightness changes | ||||||||||||||||||||||||||||||||||||||
| differences.push({ | ||||||||||||||||||||||||||||||||||||||
| index: i, | ||||||||||||||||||||||||||||||||||||||
| change: hDiff * 2 + sDiff + lDiff, // Hue changes weighted more heavily | ||||||||||||||||||||||||||||||||||||||
| change: hDiff * 2 + sDiff + lDiff, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Identify significant points based on notable changes | ||||||||||||||||||||||||||||||||||||||
| const significantPoints = [gradient[0]]; // Always include the first color | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Iterate over differences to capture significant transitions | ||||||||||||||||||||||||||||||||||||||
| for (let i = 1; i < differences.length; i++) { | ||||||||||||||||||||||||||||||||||||||
| if (differences[i - 1].change > threshold) { | ||||||||||||||||||||||||||||||||||||||
| // Dynamic threshold based on gradient characteristics | ||||||||||||||||||||||||||||||||||||||
| significantPoints.push(gradient[differences[i - 1].index]); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| significantPoints.push(gradient[gradient.length - 1]); // Always include the last color | ||||||||||||||||||||||||||||||||||||||
| significantPoints.push(gradient[gradient.length - 1]); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return significantPoints; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export function decodeLegacy(rgbtext: string) { | ||||||||||||||||||||||||||||||||||||||
| const legacyCodeRegex = | ||||||||||||||||||||||||||||||||||||||
| /(?:(?:[&§]|\\u00a7)x(?:(?:[&§]|\\u00a7)[0-9A-Fa-f]){6}|&#[0-9A-Fa-f]{6}|(?:[&§]|\\u00a7)[l-orL-ORkK])/g; | ||||||||||||||||||||||||||||||||||||||
| const matches = [...rgbtext.matchAll(legacyCodeRegex)]; | ||||||||||||||||||||||||||||||||||||||
| if (!rgbtext || !rgbtext.trim()) return null; | ||||||||||||||||||||||||||||||||||||||
| const codeRegex = | ||||||||||||||||||||||||||||||||||||||
| /(?:(?:[&§]|\\u00a7)x(?:(?:[&§]|\\u00a7)[0-9A-Fa-f]){6}|[&#§]\b[0-9A-Fa-f]{6}\b|&#[0-9A-Fa-f]{6}|<span[^>]*style=["']([^"']*)["'][^>]*>|<\/span>|(?:[&§]|\\u00a7)[l-orL-ORkK])/gi; | ||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two real decode failures come from this regex:
Suggested change
This adds a Verified with a battery of manual test cases ( Generated by Claude Code |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const matches = [...rgbtext.matchAll(codeRegex)]; | ||||||||||||||||||||||||||||||||||||||
| if (matches.length === 0) return null; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const colors: Array<{ hex: string; pos: number }> = []; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -98,38 +97,49 @@ export function decodeLegacy(rgbtext: string) { | |||||||||||||||||||||||||||||||||||||
| const match = matches[i]; | ||||||||||||||||||||||||||||||||||||||
| const codeStr = match[0]; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const lastChar = codeStr.charAt(codeStr.length - 1).toLowerCase(); | ||||||||||||||||||||||||||||||||||||||
| if (codeStr.length === 2 || codeStr.startsWith('\\u00a7')) { | ||||||||||||||||||||||||||||||||||||||
| if (lastChar === 'r') { | ||||||||||||||||||||||||||||||||||||||
| currentColor = '#ffffff'; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.bold = false; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.italic = false; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.underline = false; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.strikethrough = false; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.obfuscate = false; | ||||||||||||||||||||||||||||||||||||||
| } else if (lastChar === 'l') { | ||||||||||||||||||||||||||||||||||||||
| currentFmts.bold = true; | ||||||||||||||||||||||||||||||||||||||
| } else if (lastChar === 'o') { | ||||||||||||||||||||||||||||||||||||||
| currentFmts.italic = true; | ||||||||||||||||||||||||||||||||||||||
| } else if (lastChar === 'n') { | ||||||||||||||||||||||||||||||||||||||
| if (codeStr.toLowerCase().startsWith('<span')) { | ||||||||||||||||||||||||||||||||||||||
| const styleAttr = match[1] || ''; | ||||||||||||||||||||||||||||||||||||||
| const colorMatch = styleAttr.match(/color:\s*(#[0-9a-fA-F]{6})/i); | ||||||||||||||||||||||||||||||||||||||
| if (colorMatch) currentColor = colorMatch[1]; | ||||||||||||||||||||||||||||||||||||||
| if (/font-weight:\s*bold/i.test(styleAttr)) currentFmts.bold = true; | ||||||||||||||||||||||||||||||||||||||
| if (/font-style:\s*italic/i.test(styleAttr)) currentFmts.italic = true; | ||||||||||||||||||||||||||||||||||||||
| if (/text-decoration:[^;]*underline/i.test(styleAttr)) { | ||||||||||||||||||||||||||||||||||||||
| currentFmts.underline = true; | ||||||||||||||||||||||||||||||||||||||
| } else if (lastChar === 'm') { | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| if (/text-decoration:[^;]*line-through/i.test(styleAttr)) { | ||||||||||||||||||||||||||||||||||||||
| currentFmts.strikethrough = true; | ||||||||||||||||||||||||||||||||||||||
| } else if (lastChar === 'k') { | ||||||||||||||||||||||||||||||||||||||
| currentFmts.obfuscate = true; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } else if (codeStr.toLowerCase() === '</span>') { | ||||||||||||||||||||||||||||||||||||||
| // closing tag | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+104
to
114
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files | rg '(^|/)Decode\.ts$|rgb|HTML|Parser' || true
echo
echo "Decode outline:"
if [ -f src/util/rgb/Decode.ts ]; then
ast-grep outline src/util/rgb/Decode.ts --view compact || true
echo
echo "Relevant Decode.ts 1-180:"
sed -n '1,180p' src/util/rgb/Decode.ts | cat -n
fi
echo
echo "Search for Decode/rich/span usage:"
rg -n "Decode|\\$<r|r>|currentFmts|font-weight|font-style|text-decoration|span" . --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' | head -200Repository: birdflop/web Length of output: 27745 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "MiniMessageDecode relevant sections:"
sed -n '1,380p' src/util/rgb/MiniMessageDecode.ts | cat -n
echo
echo "Decode tests:"
if [ -f src/util/rgb/__tests__ 2>/dev/null ]; then
fd . src/util/rgb/__tests__ -t f -x sh -c 'echo "--- $1"; cat -n "$1"' sh {}
fi
fd './Decode.*\.test\.(ts|tsx|js)$|rgbUtils.test.ts' src packages --exec sh -c 'echo "--- $1"; cat -n "$1"' sh {}
echo
echo "HTML/span examples in rgb package docs/source:"
rg -n "<span[^>]+style=|font-(weight|style)|text-decoration|text:<color>|<color|<span" packages/src packages packages/rgbirdflop --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' | head -250Repository: birdflop/web Length of output: 19310 🌐 Web query:
💡 Result: The specific output format Citations:
🌐 Web query:
💡 Result: The term MiniTranslator is commonly associated with a Java utility used to convert Minecraft legacy formatting codes (like &n for underline and &m for strikethrough) into MiniMessage tags [1][2][3]. While there is no single authoritative library named "MiniTranslator" that contains a specific "Decode.ts" file implementing text-decoration logic, technical searches reveal a relevant implementation in the context of converting macOS Notes data to Markdown [4][5]. Specifically, in the Citations:
🌐 Web query:
💡 Result: I couldn’t find Citations: 🌐 Web query:
💡 Result: The text pattern provided appears to be a composite of CSS properties (font-weight, text-decoration) and a programmatic object reference (currentFmts.bold) often used in text-processing or editor-related software. 1. CSS Properties: The segments font-weight: bold and text-decoration: underline are standard CSS rules used to style text [1][2]. In rich text editors like CKEditor or Lexical, these CSS properties are frequently used to identify or apply formatting styles during document parsing or transformation [3][4][5]. 2. currentFmts.bold: This is not a standard web API. Based on common coding patterns in text-formatting libraries, it is likely a property or object within an editor's internal state management (such as TinyMCE's formatter engine [6][7], a custom extension [4], or a formatting utility). It represents a flag or boolean configuration indicating that the currently selected text or node should be, or is currently, formatted as bold [4][6]. In summary, the string reflects an internal software operation where a system checks if a specific text range has bold formatting (currentFmts.bold) and applies corresponding CSS styles (font-weight: bold; text-decoration: underline;) when rendering or exporting that content [3][4]. Citations:
🌐 Web query:
💡 Result: In the context of the Adventure library's MiniMessage implementation, which is the industry standard for parsing MiniMessage, the terms parseMiniMessage and decodeMiniMessage are not part of the official public API [1][2][3]. The Adventure MiniMessage API uses the following primary methods to process strings [1][2][4]: deserialize(String input): This is the standard method used to convert a MiniMessage string into a Component object [1][2][3]. serialize(Component component): This is used to convert a Component object back into a MiniMessage string [1][2]. If you have encountered parseMiniMessage or decodeMiniMessage in your codebase, they are likely either: 1. Custom wrapper utility methods defined within your own project or a third-party plugin/library (such as AdventureUtils) to simplify common tasks [5][6]. 2. Legacy method names from older or alternative MiniMessage implementations [7]. For example, some third-party utilities define a parseMiniMessage method that might combine deserialization with other logic, such as legacy color code conversion or applying custom TagResolvers [5][6]. You should check the source code or the documentation of the specific utility class where these methods are defined to understand their exact behavior [5][6]. Citations:
Reset span formatting flags on each style change.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| if (codeStr.startsWith('&#')) { | ||||||||||||||||||||||||||||||||||||||
| const lastChar = codeStr.charAt(codeStr.length - 1).toLowerCase(); | ||||||||||||||||||||||||||||||||||||||
| if (codeStr.length === 2 || codeStr.startsWith('\\u00a7')) { | ||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This condition conflates two unrelated matches. Any regex match starting with the literal
Suggested change
This precisely identifies only the standalone formatting-code alternative (regardless of prefix), so escaped-unicode legacy hex sequences correctly fall through to the hex-extraction branch and keep their color. Confirmed via manual testing: Generated by Claude Code |
||||||||||||||||||||||||||||||||||||||
| if (lastChar === 'r') { | ||||||||||||||||||||||||||||||||||||||
| currentColor = '#ffffff'; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.bold = false; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.italic = false; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.underline = false; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.strikethrough = false; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.obfuscate = false; | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+115
to
+123
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
The condition Detect formatting/reset codes by their actual shape instead of length/prefix heuristics, so a 🐛 Proposed fix const lastChar = codeStr.charAt(codeStr.length - 1).toLowerCase();
- if (codeStr.length === 2 || codeStr.startsWith('\\u00a7')) {
+ if (/^(?:[&§]|\\u00a7)[l-orL-ORkK]$/i.test(codeStr)) {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| } else if (lastChar === 'l') { | ||||||||||||||||||||||||||||||||||||||
| currentFmts.bold = true; | ||||||||||||||||||||||||||||||||||||||
| } else if (lastChar === 'o') { | ||||||||||||||||||||||||||||||||||||||
| currentFmts.italic = true; | ||||||||||||||||||||||||||||||||||||||
| } else if (lastChar === 'n') { | ||||||||||||||||||||||||||||||||||||||
| currentFmts.underline = true; | ||||||||||||||||||||||||||||||||||||||
| } else if (lastChar === 'm') { | ||||||||||||||||||||||||||||||||||||||
| currentFmts.strikethrough = true; | ||||||||||||||||||||||||||||||||||||||
| } else if (lastChar === 'k') { | ||||||||||||||||||||||||||||||||||||||
| currentFmts.obfuscate = true; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } else if (codeStr.startsWith('&#') || codeStr.startsWith('§#')) { | ||||||||||||||||||||||||||||||||||||||
| currentColor = '#' + codeStr.slice(2); | ||||||||||||||||||||||||||||||||||||||
| } else if (codeStr.startsWith('#')) { | ||||||||||||||||||||||||||||||||||||||
| currentColor = codeStr; | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| const hexDigits = codeStr.replace(/(?:[&§]|\\u00a7|x)/g, ''); | ||||||||||||||||||||||||||||||||||||||
| const hexDigits = codeStr.replace(/(?:[&§]|\\u00a7|x)/gi, ''); | ||||||||||||||||||||||||||||||||||||||
| currentColor = '#' + hexDigits; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| currentFmts.bold = false; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.italic = false; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.underline = false; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.strikethrough = false; | ||||||||||||||||||||||||||||||||||||||
| currentFmts.obfuscate = false; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const startIdx = match.index + codeStr.length; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.