|
| 1 | +# Add a `format` prop to the TipTap editor to support HTML output (not only Markdown) |
| 2 | + |
| 3 | +> Issue draft — Technical Task Issue template. Type: **Task**. Labels: `DEV: frontend`. |
| 4 | +
|
| 5 | +## Overview |
| 6 | + |
| 7 | +The shared TipTap editor (`shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue`) currently reads and writes **Markdown** only. Add a `format` prop — either `'markdown'` or `'html'`, defaulting to `'markdown'` — so a consumer can choose to drive the editor with HTML instead. When `format` is `'html'`, the editor's `value` prop is treated as HTML and the `update` event emits HTML; when it is `'markdown'` (the default), behavior is unchanged. |
| 8 | + |
| 9 | +This is needed for the **new QTI editor**, where the data must be XML rather than Markdown: its rich-text fields are stored as XML strings produced by `editor.getHTML()`, so the editor has to be able to consume and emit HTML. |
| 10 | + |
| 11 | +**Complexity:** Low |
| 12 | +**Target branch:** `unstable` |
| 13 | + |
| 14 | +### Context |
| 15 | + |
| 16 | +There is no way for a consumer to get HTML out from the TipTap editor, even though TipTap can produce it directly via `editor.getHTML()`. The QTI editor (and potentially other consumers) needs HTML/XML, so it requires the editor to (a) accept its `value` as HTML and (b) emit HTML on `update`. |
| 17 | + |
| 18 | +**The `format` prop is a transitional measure.** In the future, once the QTI editor has replaced the current (Markdown-based) assessment editing, we expect to **remove the `format` prop entirely** and have the TipTap editor always consume and emit HTML. The prop exists only so Markdown and HTML consumers can coexist during that transition. |
| 19 | + |
| 20 | +Relevant spots in `TipTapEditor.vue`: |
| 21 | +- `getMarkdownContent()` — the output helper. |
| 22 | +- `watch(() => props.value, ...)` — the input/sync-from-parent path (uses `preprocessMarkdown`). |
| 23 | +- the editor-state `watch(() => editor.value?.state, ...)` — the sync-to-parent path (emits Markdown). |
| 24 | +- the `props` block — where `value`, `mode`, etc. are declared. |
| 25 | + |
| 26 | +### The Change |
| 27 | + |
| 28 | +- Add a `format` prop: a string defaulting to `'markdown'`, accepting only `'markdown'` or `'html'` (with a prop validator). |
| 29 | +- Make the **output** path format-aware: in `'markdown'` it returns `storage.markdown.getMarkdown()` (current behavior); in `'html'` it returns `editor.getHTML()`. |
| 30 | +- Make the **input** path format-aware: in `'markdown'` it preprocesses with `preprocessMarkdown` then sets content (current behavior); in `'html'` it sets `value` as HTML content directly (no Markdown preprocessing). |
| 31 | +- The sync-to-parent comparison/emit and the sync-from-parent comparison should use the same format-aware content helper, so the loop-prevention logic keeps working in both formats. |
| 32 | +- `'markdown'` remains the default, so all current consumers are unaffected (backward compatible). |
| 33 | + |
| 34 | +### Out of Scope |
| 35 | + |
| 36 | +- Changing any current consumer to use `format="html"` (this task only adds the capability; the QTI editor will opt in separately). |
| 37 | +- Toolbar/feature differences between formats (the same editing features apply; only the serialization in/out changes). |
| 38 | +- Markdown-vs-HTML sanitization policy changes beyond what the editor already does. |
| 39 | + |
| 40 | +### Acceptance Criteria |
| 41 | + |
| 42 | +#### General |
| 43 | +- [ ] `TipTapEditor.vue` declares a `format` prop (String) defaulting to `'markdown'`, validated to accept only `'markdown'` or `'html'`. |
| 44 | +- [ ] With `format="markdown"` (and when the prop is omitted), input and output behavior are unchanged from today (Markdown in, Markdown out) — existing consumers are unaffected. |
| 45 | +- [ ] With `format="html"`, the `value` prop is treated as HTML and set as editor content **without** Markdown preprocessing. |
| 46 | +- [ ] With `format="html"`, the `update` event emits the editor's HTML (`editor.getHTML()`). |
| 47 | +- [ ] The sync-from-parent and sync-to-parent paths both use the format-aware content helper, so external updates and the loop-prevention flag work correctly in both formats. |
| 48 | +- [ ] Content round-trips in each format: setting `value` and reading back `update` is consistent within `'markdown'` and within `'html'`. |
| 49 | + |
| 50 | +#### Accessibility and i18n |
| 51 | +- [ ] No user-facing strings are added; if any are, they come from the editor's existing strings module. |
| 52 | + |
| 53 | +#### Testing |
| 54 | +- [ ] Unit tests cover `format="markdown"` (default) emitting Markdown and `format="html"` emitting HTML. |
| 55 | +- [ ] A test confirms the default (no `format` prop) preserves current Markdown behavior. |
| 56 | +- [ ] Existing lint and test suites pass. |
| 57 | + |
| 58 | +### References |
| 59 | + |
| 60 | +- `shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue` — `getMarkdownContent()`, the `props.value` watch (input via `preprocessMarkdown`), the editor-state watch (output via `getMarkdown()`), and the `props` block. |
| 61 | +- `editor.getHTML()` (TipTap) — the HTML serialization counterpart to the Markdown storage's `getMarkdown()`. |
| 62 | + |
| 63 | +## AI usage |
| 64 | + |
| 65 | +I used Claude (Claude Code) to draft this issue. I specified the change (a `markdown`/`html` `format` prop defaulting to `markdown`, making input parsing and output emission format-aware); Claude read the current `TipTapEditor.vue` to ground the description and acceptance criteria, and I reviewed every section. |
0 commit comments