From 410b2a061cdf7a1cee76cc57e6ea836d650653b1 Mon Sep 17 00:00:00 2001 From: Josh Doman Date: Sun, 17 Aug 2025 19:41:40 -0400 Subject: [PATCH 1/9] fix(parser): Protect inline code from formatting while allowing spans Fixes #2. Protects inline code from being formatted while still allowing bold/italic/link spans across code. Uses Unicode Private Use Area placeholders to temporarily replace code blocks during formatting, then restores them afterward. Co-authored-by: Josh Doman --- src/parser.js | 13 ++++ test/overtype.test.js | 160 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) diff --git a/src/parser.js b/src/parser.js index f53c694..f4a43ae 100644 --- a/src/parser.js +++ b/src/parser.js @@ -155,9 +155,22 @@ export class MarkdownParser { let html = text; // Order matters: parse code first to avoid conflicts html = this.parseInlineCode(html); + // Use placeholders to protect inline code while preserving formatting spans + const codeBlocks = new Map(); + html = html.replace(/(.*?<\/code>)/g, (match) => { + // Prevent conflicts with private use area Unicode + const placeholder = `\uE000${codeBlocks.size}\uE001`; + codeBlocks.set(placeholder, match); + return placeholder; + }); + // Process other inline elements on text with placeholders html = this.parseLinks(html); html = this.parseBold(html); html = this.parseItalic(html); + // Restore code blocks + codeBlocks.forEach((codeBlock, placeholder) => { + html = html.replace(placeholder, codeBlock); + }); return html; } diff --git a/test/overtype.test.js b/test/overtype.test.js index 581fcc2..9c56dd3 100644 --- a/test/overtype.test.js +++ b/test/overtype.test.js @@ -198,6 +198,166 @@ This is **bold** and *italic*. assert(actual.includes('class="raw-line"'), 'Raw line display', 'Should show raw line for active line'); })(); +// Test: Inline code with underscores and stars +(() => { + const tests = [ + { + input: '`OP_CAT_DOG`', + expected: '
`OP_CAT_DOG`
', + description: 'Should not italicize underscores inside code' + }, + { + input: '`OP_CAT` and *dog*', + expected: '
`OP_CAT` and *dog*
', + description: 'Should italicize outside code but not inside' + }, + { + input: '`function_name_here` _should work_', + expected: '
`function_name_here` _should work_
', + description: 'Should handle mixed code and italic with underscores' + }, + { + input: '`__init__` method', + expected: '
`__init__` method
', + description: 'Should not bold double underscores inside code' + }, + { + input: 'Text `with_code` and **bold**', + expected: '
Text `with_code` and **bold**
', + description: 'Should handle code with underscores and separate bold' + }, + { + input: '`*asterisk*` and _underscore_', + expected: '
`*asterisk*` and _underscore_
', + description: 'Should not italicize asterisks inside code' + } + ]; + + tests.forEach(test => { + const actual = MarkdownParser.parseLine(test.input); + assert(htmlEqual(actual, test.expected), `Inline code protection: ${test.input}`, `${test.description}. Expected: ${test.expected}, Got: ${actual}`); + }); +})(); + +// Test: Formatting that spans across code blocks +(() => { + const tests = [ + { + input: '*cat `test` dog*', + expected: '
*cat `test` dog*
', + description: 'Should italicize text that spans across code blocks' + }, + { + input: '**bold `code_here` more bold**', + expected: '
**bold `code_here` more bold**
', + description: 'Should bold text that spans across code blocks' + }, + { + input: '_italic `with_underscores` still italic_', + expected: '
_italic `with_underscores` still italic_
', + description: 'Should handle italic with underscores spanning code' + }, + { + input: '__bold `code` and `more_code` bold__', + expected: '
__bold `code` and `more_code` bold__
', + description: 'Should bold text spanning multiple code blocks' + } + ]; + + tests.forEach(test => { + const actual = MarkdownParser.parseLine(test.input); + assert(htmlEqual(actual, test.expected), `Spanning code: ${test.input}`, `${test.description}. Expected: ${test.expected}, Got: ${actual}`); + }); +})(); + +// Test: Multiple inline code blocks with external formatting +(() => { + const tests = [ + { + input: '`first_code` and `second_code` with *italic*', + expected: '
`first_code` and `second_code` with *italic*
', + description: 'Should handle multiple code blocks with external formatting' + }, + { + input: '*Before `__code__` between `_more_code_` after*', + expected: '
*Before `__code__` between `_more_code_` after*
', + description: 'Should handle italic spanning multiple protected code blocks' + }, + { + input: '**Text `code1` middle `code2` end**', + expected: '
**Text `code1` middle `code2` end**
', + description: 'Should bold across multiple code blocks' + } + ]; + + tests.forEach(test => { + const actual = MarkdownParser.parseLine(test.input); + assert(htmlEqual(actual, test.expected), `Multiple code + format: ${test.input}`, `${test.description}. Expected: ${test.expected}, Got: ${actual}`); + }); +})(); + +// Test: Complex nested scenarios +(() => { + const tests = [ + { + input: 'Normal `code_block` and **bold `with_code` bold** text', + expected: '
Normal `code_block` and **bold `with_code` bold** text
', + description: 'Should handle mixed standalone and spanning formatting' + }, + { + input: '*italic* `code_here` **bold `spanning_code` bold**', + expected: '
*italic* `code_here` **bold `spanning_code` bold**
', + description: 'Should handle multiple different formatting types with code' + }, + { + input: '[Link `with_code` text](url) and `regular_code`', + expected: '
[Link `with_code` text](url) and `regular_code`
', + description: 'Should handle links spanning code blocks' + } + ]; + + tests.forEach(test => { + const actual = MarkdownParser.parseLine(test.input); + assert(htmlEqual(actual, test.expected), `Complex nested code: ${test.input}`, `${test.description}. Expected: ${test.expected}, Got: ${actual}`); + }); +})(); + +// Test: Edge cases that should NOT be formatted +(() => { + const tests = [ + { + input: '`**not_bold**`', + expected: '
`**not_bold**`
', + description: 'Should not process bold markers inside code' + }, + { + input: '`__also_not_bold__`', + expected: '
`__also_not_bold__`
', + description: 'Should not process underscore bold markers inside code' + }, + { + input: '`*not_italic*`', + expected: '
`*not_italic*`
', + description: 'Should not process asterisk italic markers inside code' + }, + { + input: '`_not_italic_`', + expected: '
`_not_italic_`
', + description: 'Should not process underscore italic markers inside code' + }, + { + input: '`[not_a_link](url)`', + expected: '
`[not_a_link](url)`
', + description: 'Should not process link markers inside code' + } + ]; + + tests.forEach(test => { + const actual = MarkdownParser.parseLine(test.input); + assert(htmlEqual(actual, test.expected), `Code protection edge cases: ${test.input}`, `${test.description}. Expected: ${test.expected}, Got: ${actual}`); + }); +})(); + // ===== Integration Tests ===== console.log('\nπŸ”§ Integration Tests\n'); From 73763a5ea1033c0f64542919c724a71c56e83859 Mon Sep 17 00:00:00 2001 From: David Miranda Date: Sun, 17 Aug 2025 19:42:54 -0400 Subject: [PATCH 2/9] docs: Add contributor credit and improve code comments - Added Josh Doman to Contributors section for PR #6 - Enhanced comment explaining Unicode Private Use Area choice - Added gh-report.md analyzing all issues and PRs - Added twitter-thread.md from successful launch --- README.md | 5 ++ gh-report.md | 180 ++++++++++++++++++++++++++++++++++++++++++++++ src/parser.js | 8 ++- twitter-thread.md | 104 +++++++++++++++++++++++++++ 4 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 gh-report.md create mode 100644 twitter-thread.md diff --git a/README.md b/README.md index dbd0688..af573c7 100644 --- a/README.md +++ b/README.md @@ -431,6 +431,11 @@ OverType uses a unique invisible textarea overlay approach: - Textarea content drives everything - One-way data flow: textarea β†’ parser β†’ preview +## Contributors + +Special thanks to: +- [Josh Doman](https://github.com/joshdoman) - Fixed inline code formatting preservation ([#6](https://github.com/panphora/overtype/pull/6)) + ## License MIT diff --git a/gh-report.md b/gh-report.md new file mode 100644 index 0000000..d6e4c40 --- /dev/null +++ b/gh-report.md @@ -0,0 +1,180 @@ +# GitHub Issues & PRs Report for OverType + +Generated: 2025-08-17 + +## Summary +- **Open PRs**: 1 +- **Open Issues**: 5 +- **Closed PRs**: 0 +- **Closed Issues**: 0 + +The project just launched (142 HN points in 5 hours!) and is already receiving valuable community contributions and feedback. + +--- + +## Pull Requests + +### PR #6: fix(parser): Protect inline code from formatting while allowing spans +- **Author**: Josh Doman (@joshdoman) +- **Created**: 2025-08-17 (Today!) +- **Status**: OPEN, MERGEABLE (Clean) +- **Files Changed**: 2 (src/parser.js +13 lines, test/overtype.test.js +160 lines) + +**What it fixes:** +- Solves Issue #2 where inline code with underscores/asterisks was incorrectly formatted +- Examples fixed: + - `__init__` was being bolded β†’ now displays correctly + - `OP_CAT` followed by italics was breaking β†’ now works + - `*asterisk*` in code was being italicized β†’ now protected + +**How it works:** +- Replaces code blocks with Unicode private use area placeholders (`\uE000{index}\uE001`) +- Applies formatting (bold/italic/links) to the text +- Restores code blocks afterward +- Still allows formatting that spans across code (e.g., `*text `code` text*` correctly italicizes everything) + +**My Recommendation:** βœ… **MERGE THIS** +- High-quality PR with comprehensive test coverage (160 lines of tests!) +- Fixes a real bug reported by a user +- Elegant solution using placeholder technique +- Author clearly understands the codebase +- Tests cover edge cases including nested formatting and spanning scenarios + +--- + +## Issues (Priority Order) + +### πŸ”΄ HIGH PRIORITY + +#### Issue #3: Hitting tab inside the editor breaks the cursor +- **Author**: mwerezak +- **Impact**: Critical UX bug - cursor misalignment after tab + +**Problem**: Tab key moves focus away from editor, and returning breaks cursor alignment + +**Recommended Fix:** +```javascript +textarea.addEventListener('keydown', (e) => { + if (e.key === 'Tab') { + e.preventDefault(); + // Insert 2 spaces at cursor position + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + const value = textarea.value; + textarea.value = value.substring(0, start) + ' ' + value.substring(end); + textarea.selectionStart = textarea.selectionEnd = start + 2; + // Trigger input event to update preview + textarea.dispatchEvent(new Event('input')); + } +}); +``` + +**Priority**: Fix immediately - this breaks core editing functionality + +--- + +#### Issue #1: `code` tag does not inherit font size +- **Author**: Sascha IßbrΓΌcker (@sissbruecker) +- **Impact**: Alignment breaks when user CSS sets different font-size for code + +**Problem**: External CSS like `code { font-size: 85%; }` breaks the perfect alignment + +**Recommended Fix:** +```css +/* In overtype styles, force code to match text size */ +.overtype-container code { + font-size: inherit !important; + line-height: inherit !important; +} +``` + +**Priority**: High - this is a common CSS pattern that will affect many integrations + +--- + +### 🟑 MEDIUM PRIORITY + +#### Issue #2: Incorrectly renders inline code containing an underscore +- **Author**: Josh Doman (@joshdoman) +- **Status**: **FIXED BY PR #6** βœ… + +**Action**: Merge PR #6 to close this issue + +--- + +#### Issue #4: Unable to open/navigate to links +- **Author**: David Fiala (@davidfiala) +- **Impact**: Feature request - links aren't clickable + +**Analysis**: This is tricky because the textarea captures all clicks. Options: +1. Make links clickable with modifier key (Cmd/Ctrl+Click) +2. Add a preview-only mode toggle +3. Show link preview on hover with "click to open" hint + +**Recommended Approach:** +```javascript +// Add to preview element +preview.addEventListener('click', (e) => { + if (e.target.tagName === 'A' && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + window.open(e.target.href, '_blank'); + } +}); +``` + +**Priority**: Medium - nice to have but not critical for editing + +--- + +### 🟒 LOW PRIORITY + +#### Issue #5: Image support +- **Author**: David Fiala (@davidfiala) +- **Impact**: Feature request - images don't display + +**Analysis**: Images would break the alignment concept since they have variable height. This might be fundamentally incompatible with the transparent textarea approach. + +**Options:** +1. Document that images are unsupported by design +2. Show image URLs as styled links +3. Add image preview on hover (won't work on mobile) + +**Recommended Approach:** +- Add to README/docs: "Images are not supported as they would break the character-perfect alignment that makes OverType work" +- Consider showing image alt text in brackets: `[Image: alt text]` + +**Priority**: Low - document as intentional limitation + +--- + +## Recommended Action Plan + +### Immediate (Today): +1. **Merge PR #6** - It's ready and fixes a reported bug +2. **Fix Issue #3** (Tab key) - Critical UX bug +3. **Fix Issue #1** (Code font-size) - Common integration issue + +### This Week: +4. Add Cmd/Ctrl+Click for links (Issue #4) +5. Update documentation about image limitations (Issue #5) + +### Future Considerations: +- Add CONTRIBUTING.md to guide future contributors (given the high-quality PR received) +- Consider adding GitHub Actions for automated testing +- Add a "Known Limitations" section to README + +--- + +## Community Engagement Notes + +The project is getting excellent engagement: +- High-quality PR within hours of launch +- Issues are well-written with clear examples +- Users are trying to integrate OverType into real projects +- The transparent textarea concept is resonating with developers + +Keep this momentum by: +- Responding quickly to issues +- Thanking contributors +- Being transparent about design limitations +- Maintaining the "simple and minimal" philosophy \ No newline at end of file diff --git a/src/parser.js b/src/parser.js index f4a43ae..0ab14f2 100644 --- a/src/parser.js +++ b/src/parser.js @@ -156,9 +156,15 @@ export class MarkdownParser { // Order matters: parse code first to avoid conflicts html = this.parseInlineCode(html); // Use placeholders to protect inline code while preserving formatting spans + // We use Unicode Private Use Area (U+E000-U+F8FF) as placeholders because: + // 1. These characters are reserved for application-specific use + // 2. They'll never appear in user text + // 3. They maintain single-character width (important for alignment) + // 4. They're invisible if accidentally rendered + // This allows formatting like *text `code` text* to span across code blocks + // while preventing formatting inside code like `__init__` from being bolded const codeBlocks = new Map(); html = html.replace(/(.*?<\/code>)/g, (match) => { - // Prevent conflicts with private use area Unicode const placeholder = `\uE000${codeBlocks.size}\uE001`; codeBlocks.set(placeholder, match); return placeholder; diff --git a/twitter-thread.md b/twitter-thread.md new file mode 100644 index 0000000..b606231 --- /dev/null +++ b/twitter-thread.md @@ -0,0 +1,104 @@ +# Twitter Thread: OverType Launch + +I got so frustrated with WYSIWYG editors that I built my own. + +The twist? It's just a textarea. + +OverType: A transparent