feat: add HTML to HSML conversion mode - #2
Conversation
📝 WalkthroughWalkthroughAdds a conversion-mode toggle (compile ↔ convert), new HtmlEditor and HsmlOutput components, refactors editor state to support dual pipelines with prefixed hash persistence, updates App and Sidebar to render per-mode, adjusts editors’ Tab/Shift-Tab behavior, and expands e2e tests to cover conversion flow and indentation. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
e2e/playground.spec.ts (1)
4-5: Consider renaming selectors for clarity across both modes.The selectors
HSML_EDITORandHTML_OUTPUTare accurate for compile mode but become semantically confusing in convert mode whereHSML_EDITORselects the HTML editor andHTML_OUTPUTselects the HSML output. Consider using mode-agnostic names.Proposed rename for clarity
-const HSML_EDITOR = ':not(.editor-readonly) > .cm-editor .cm-content'; -const HTML_OUTPUT = '.editor-readonly .cm-editor .cm-content'; +const EDITABLE_EDITOR = ':not(.editor-readonly) > .cm-editor .cm-content'; +const READONLY_OUTPUT = '.editor-readonly .cm-editor .cm-content';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/playground.spec.ts` around lines 4 - 5, The selector constants HSML_EDITOR and HTML_OUTPUT are semantically confusing across modes; rename them to mode-agnostic identifiers (e.g., EDITOR_SELECTOR and OUTPUT_SELECTOR or INPUT_SELECTOR and OUTPUT_SELECTOR) and update all usages in e2e/playground.spec.ts (references to HSML_EDITOR and HTML_OUTPUT) so tests read the editor vs output independent of compile/convert mode; ensure the values remain the same (':not(.editor-readonly) > .cm-editor .cm-content' and '.editor-readonly .cm-editor .cm-content') and update any comments or variable docs accordingly.src/composables/useEditorState.ts (1)
134-149: Clear pending timers when switching modes to prevent stale callbacks.When switching modes, the pending debounced timer from the previous mode could still fire. Although the mode guard checks inside the timeout callbacks provide protection, clearing the timer explicitly is cleaner and prevents unnecessary timer execution.
Proposed fix
// Trigger on mode switch — carry over the output as the new input watch(conversionMode, (mode) => { + clearTimeout(compileTimer); + clearTimeout(convertTimer); if (mode === 'compile') { if (hsmlOutput.value) { hsmlSource.value = hsmlOutput.value; } compileSource(); writeToHash('compile', hsmlSource.value); } else { if (htmlOutput.value) { htmlInput.value = htmlOutput.value; } convertSource(); writeToHash('convert', htmlInput.value); } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/composables/useEditorState.ts` around lines 134 - 149, The watch handler for conversionMode should clear any pending debounced timers from the previous mode before invoking the new mode's functions to avoid stale callbacks; update the watch(conversionMode, ...) block to cancel the relevant timers (e.g., clearTimeout(compileTimerId) and/or clearTimeout(convertTimerId)) or call provided cancel methods from the debounced wrappers, then proceed with the existing logic (copy hsmlOutput→hsmlSource or htmlOutput→htmlInput, call compileSource() or convertSource(), and writeToHash()). Ensure the timer identifiers or cancel functions are the same ones used inside compileSource/convertSource so you actually stop any scheduled callbacks before switching modes.src/components/HtmlEditor.vue (1)
27-35: Tab handler only supports indentation, not un-indentation.The custom Tab handler inserts spaces but doesn't handle Shift+Tab for removing indentation. This is a minor UX gap but acceptable for a playground.
Optional: Add Shift+Tab support
const tabSize = keymap.of([ { key: 'Tab', run: (view) => { view.dispatch(view.state.replaceSelection(' ')); return true; }, }, + { + key: 'Shift-Tab', + run: (view) => { + const { from } = view.state.selection.main; + const line = view.state.doc.lineAt(from); + const lineText = line.text; + if (lineText.startsWith(' ')) { + view.dispatch({ + changes: { from: line.from, to: line.from + 2, insert: '' }, + }); + } + return true; + }, + }, ]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/HtmlEditor.vue` around lines 27 - 35, The Tab keymap (tabSize) only inserts two spaces and doesn't handle un-indentation for Shift+Tab; update the keymap.of array to add a second binding for 'Shift-Tab' that inspects the current selection/lines in the editor (using the view and view.state), and for each selected line removes the leading two spaces (or a leading tab) where present by dispatching an appropriate transaction via view.dispatch—modify the existing tab handler logic name/tabSize and add a complementary 'Shift-Tab' run handler that returns true after performing the un-indent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tsconfig.e2e.json`:
- Around line 1-14: Add "composite": true to the tsconfig's compilerOptions so
this config can be used as a project reference; update the tsconfig.e2e.json's
compilerOptions object to include composite: true (alongside
target/module/moduleResolution/etc.) to satisfy the root tsconfig's project
reference and enable TypeScript incremental/build info handling.
---
Nitpick comments:
In `@e2e/playground.spec.ts`:
- Around line 4-5: The selector constants HSML_EDITOR and HTML_OUTPUT are
semantically confusing across modes; rename them to mode-agnostic identifiers
(e.g., EDITOR_SELECTOR and OUTPUT_SELECTOR or INPUT_SELECTOR and
OUTPUT_SELECTOR) and update all usages in e2e/playground.spec.ts (references to
HSML_EDITOR and HTML_OUTPUT) so tests read the editor vs output independent of
compile/convert mode; ensure the values remain the same (':not(.editor-readonly)
> .cm-editor .cm-content' and '.editor-readonly .cm-editor .cm-content') and
update any comments or variable docs accordingly.
In `@src/components/HtmlEditor.vue`:
- Around line 27-35: The Tab keymap (tabSize) only inserts two spaces and
doesn't handle un-indentation for Shift+Tab; update the keymap.of array to add a
second binding for 'Shift-Tab' that inspects the current selection/lines in the
editor (using the view and view.state), and for each selected line removes the
leading two spaces (or a leading tab) where present by dispatching an
appropriate transaction via view.dispatch—modify the existing tab handler logic
name/tabSize and add a complementary 'Shift-Tab' run handler that returns true
after performing the un-indent.
In `@src/composables/useEditorState.ts`:
- Around line 134-149: The watch handler for conversionMode should clear any
pending debounced timers from the previous mode before invoking the new mode's
functions to avoid stale callbacks; update the watch(conversionMode, ...) block
to cancel the relevant timers (e.g., clearTimeout(compileTimerId) and/or
clearTimeout(convertTimerId)) or call provided cancel methods from the debounced
wrappers, then proceed with the existing logic (copy hsmlOutput→hsmlSource or
htmlOutput→htmlInput, call compileSource() or convertSource(), and
writeToHash()). Ensure the timer identifiers or cancel functions are the same
ones used inside compileSource/convertSource so you actually stop any scheduled
callbacks before switching modes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f79e1dc8-58e5-4eae-89eb-a3a2f2e20b3c
📒 Files selected for processing (8)
e2e/playground.spec.tssrc/App.vuesrc/components/HsmlOutput.vuesrc/components/HtmlEditor.vuesrc/components/SidebarPanel.vuesrc/composables/useEditorState.tstsconfig.e2e.jsontsconfig.json
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/components/HtmlEditor.vue (1)
27-47: Extract shared indent keymap to avoid drift between editors.Line 27-Line 47 duplicates the same keymap logic used in
src/components/HsmlEditor.vue. A shared helper will keep behavior aligned and reduce maintenance overhead.♻️ Suggested refactor
--- a/src/components/HtmlEditor.vue +++ b/src/components/HtmlEditor.vue @@ import { onBeforeUnmount, onMounted, ref, watch } from 'vue'; import { useEditorState } from '../composables/useEditorState'; import { useTheme } from '../composables/useTheme'; +import { twoSpaceIndentKeymap } from '../codemirror/twoSpaceIndentKeymap'; @@ - const indentKeymap = keymap.of([ - { - key: 'Tab', - run: (view) => { - view.dispatch(view.state.replaceSelection(' ')); - return true; - }, - }, - { - key: 'Shift-Tab', - run: (view) => { - const { state } = view; - const line = state.doc.lineAt(state.selection.main.head); - if (line.text.startsWith(' ')) { - view.dispatch({ changes: { from: line.from, to: line.from + 2 } }); - return true; - } - return false; - }, - }, - ]); @@ - indentKeymap, + twoSpaceIndentKeymap,// src/codemirror/twoSpaceIndentKeymap.ts import { keymap } from '@codemirror/view'; export const twoSpaceIndentKeymap = keymap.of([ { key: 'Tab', run: (view) => { view.dispatch(view.state.replaceSelection(' ')); return true; }, }, { key: 'Shift-Tab', run: (view) => { const line = view.state.doc.lineAt(view.state.selection.main.head); if (!line.text.startsWith(' ')) return false; view.dispatch({ changes: { from: line.from, to: line.from + 2 } }); return true; }, }, ]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/HtmlEditor.vue` around lines 27 - 47, The indentKeymap block in HtmlEditor.vue duplicates the same Tab/Shift-Tab logic used in HsmlEditor.vue; extract that shared keymap into a single exported helper (e.g., twoSpaceIndentKeymap) and import it into both components, replacing the local indentKeymap definition; ensure the helper exports the keymap.of([...]) with the same run handlers (using view.state.replaceSelection and view.state.doc.lineAt/view.dispatch) and update HtmlEditor.vue and HsmlEditor.vue to import and use twoSpaceIndentKeymap instead of their inline definitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/composables/useEditorState.ts`:
- Around line 139-147: The mode-handoff currently only assigns when outputs are
truthy, which loses valid empty-string outputs; update the handoff logic around
hsmlOutput -> hsmlSource and htmlOutput -> htmlInput (and then call
compileSource/writeToHash as before) to assign when the output is present even
if empty — e.g., check for null/undefined rather than truthiness
(hsmlOutput.value !== undefined && hsmlOutput.value !== null) or simply always
assign the .value so empty strings are carried through.
---
Nitpick comments:
In `@src/components/HtmlEditor.vue`:
- Around line 27-47: The indentKeymap block in HtmlEditor.vue duplicates the
same Tab/Shift-Tab logic used in HsmlEditor.vue; extract that shared keymap into
a single exported helper (e.g., twoSpaceIndentKeymap) and import it into both
components, replacing the local indentKeymap definition; ensure the helper
exports the keymap.of([...]) with the same run handlers (using
view.state.replaceSelection and view.state.doc.lineAt/view.dispatch) and update
HtmlEditor.vue and HsmlEditor.vue to import and use twoSpaceIndentKeymap instead
of their inline definitions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 81888843-2eef-45f6-86bc-25b9548b058c
📒 Files selected for processing (5)
e2e/playground.spec.tssrc/components/HsmlEditor.vuesrc/components/HtmlEditor.vuesrc/composables/useEditorState.tstsconfig.e2e.json
✅ Files skipped from review due to trivial changes (1)
- tsconfig.e2e.json
🚧 Files skipped from review as they are similar to previous changes (1)
- e2e/playground.spec.ts
Summary by CodeRabbit
New Features
Usability
Tests