-
Notifications
You must be signed in to change notification settings - Fork 1
Handle markdown when not recognized #52
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
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 |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Project notes | ||
|
|
||
| - Buy milk | ||
| - Call dentist | ||
| - Review PR |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,110 @@ | ||||||||||||||||||||||||||||||
| import React from 'react' | ||||||||||||||||||||||||||||||
| import RichTextEditor from '../../../ui/web/components/RichTextEditor' | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| function mountEditor(content = '') { | ||||||||||||||||||||||||||||||
| const spy = cy.spy().as('onChange') | ||||||||||||||||||||||||||||||
| cy.mount( | ||||||||||||||||||||||||||||||
| <RichTextEditor | ||||||||||||||||||||||||||||||
| initialContent={content} | ||||||||||||||||||||||||||||||
| onContentChange={spy} | ||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| describe('RichTextEditor — Apply as Markdown button', () => { | ||||||||||||||||||||||||||||||
| describe('button state', () => { | ||||||||||||||||||||||||||||||
| it('renders the MD button in the toolbar', () => { | ||||||||||||||||||||||||||||||
| mountEditor('') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="apply-markdown-button"]').should('be.visible') | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| it('is disabled when no text is selected', () => { | ||||||||||||||||||||||||||||||
| mountEditor('<p>Some text</p>') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="apply-markdown-button"]').should('be.disabled') | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| it('becomes enabled after selecting text', () => { | ||||||||||||||||||||||||||||||
| mountEditor('<p>Some text</p>') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').click() | ||||||||||||||||||||||||||||||
| cy.get('.ProseMirror').type('{selectall}') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="apply-markdown-button"]').should('not.be.disabled') | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| it('becomes disabled again after deselecting (pressing Escape)', () => { | ||||||||||||||||||||||||||||||
| mountEditor('<p>Some text</p>') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').click() | ||||||||||||||||||||||||||||||
| cy.get('.ProseMirror').type('{selectall}') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="apply-markdown-button"]').should('not.be.disabled') | ||||||||||||||||||||||||||||||
| cy.get('.ProseMirror').type('{end}') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="apply-markdown-button"]').should('be.disabled') | ||||||||||||||||||||||||||||||
|
Comment on lines
+33
to
+39
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. Align test title with the actual key used. The test name says “pressing Escape,” but Line 38 sends ✏️ Proposed fix- it('becomes disabled again after deselecting (pressing Escape)', () => {
+ it('becomes disabled again after deselecting (pressing End)', () => {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| describe('markdown rendering', () => { | ||||||||||||||||||||||||||||||
| it('converts selected low-score markdown text to rendered list', () => { | ||||||||||||||||||||||||||||||
| // This text scores below the auto-detection threshold (bullet list only = 2pts < 3) | ||||||||||||||||||||||||||||||
| // so without forcedType it would be detected as plain text | ||||||||||||||||||||||||||||||
| mountEditor('<p>Project notes\n\n- Buy milk\n- Call dentist\n- Review PR</p>') | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').click() | ||||||||||||||||||||||||||||||
| cy.get('.ProseMirror').type('{selectall}') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="apply-markdown-button"]').click() | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').find('ul').should('exist') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').find('li').should('have.length.gte', 3) | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').should('contain.text', 'Buy milk') | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| it('converts selected heading markdown to h1 element', () => { | ||||||||||||||||||||||||||||||
| mountEditor('<p># Main Title\n\nContent below</p>') | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').click() | ||||||||||||||||||||||||||||||
| cy.get('.ProseMirror').type('{selectall}') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="apply-markdown-button"]').click() | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').find('h1').should('exist').and('contain.text', 'Main Title') | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| it('strips XSS tags from forced markdown input', () => { | ||||||||||||||||||||||||||||||
| mountEditor('<p># Title\n\n<script>alert(1)</script>\n\nParagraph</p>') | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').click() | ||||||||||||||||||||||||||||||
| cy.get('.ProseMirror').type('{selectall}') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="apply-markdown-button"]').click() | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').should('not.contain.html', '<script>') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').find('h1').should('exist') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').should('contain.text', 'Paragraph') | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| it('calls onContentChange after applying markdown', () => { | ||||||||||||||||||||||||||||||
| mountEditor('<p>- Item one\n- Item two</p>') | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').click() | ||||||||||||||||||||||||||||||
| cy.get('.ProseMirror').type('{selectall}') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="apply-markdown-button"]').click() | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| cy.get('@onChange').should('have.been.called') | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| it('text without markdown syntax remains as paragraph without crashing', () => { | ||||||||||||||||||||||||||||||
| mountEditor('<p>Just plain text with no markdown</p>') | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').click() | ||||||||||||||||||||||||||||||
| cy.get('.ProseMirror').type('{selectall}') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="apply-markdown-button"]').click() | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // No crash, text still present | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').should('contain.text', 'Just plain text with no markdown') | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| describe('early returns — no crash', () => { | ||||||||||||||||||||||||||||||
| it('clicking disabled button (no selection) does not modify content', () => { | ||||||||||||||||||||||||||||||
| mountEditor('<p>Unchanged</p>') | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="apply-markdown-button"]').click({ force: true }) | ||||||||||||||||||||||||||||||
| cy.get('[data-cy="editor-content"]').should('contain.text', 'Unchanged') | ||||||||||||||||||||||||||||||
| cy.get('@onChange').should('not.have.been.called') | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n "onSelectionChange" --type ts --type tsx -A 5Repository: koreyba/EverFreeNote
Length of output: 91
🏁 Script executed:
rg -n "onSelectionChange" -A 5Repository: koreyba/EverFreeNote
Length of output: 6949
🏁 Script executed:
sed -n '140,160p' ui/web/components/RichTextEditorWebView.tsxRepository: koreyba/EverFreeNote
Length of output: 565
🏁 Script executed:
sed -n '250,280p' app/editor-webview/page.tsxRepository: koreyba/EverFreeNote
Length of output: 773
Cache the previous selection state to avoid high-frequency postMessage calls
handleSelectionChangeposts aSELECTION_CHANGEmessage on every invocation. However, TipTap'sonSelectionUpdateevent fires on every cursor movement or selection change, andonSelectionChange?.(from !== to)is called for each event. This results in postMessage calls even when the boolean value hasn't changed (e.g., moving the cursor while maintaining a selection will trigger many messages with identical payloads).Store the previous
hasSelectionvalue in a ref and only post when the boolean state actually transitions to avoid unnecessary native layer communication:Suggested pattern
🤖 Prompt for AI Agents