Repository navigation
feat: WYSIWYG editors via WordPress TinyMCE with server-side markdown conversion - #46
Merged
Merged
Conversation
…_enqueue_editor The New-topic body field and comment composer become real WYSIWYG editors on top of WordPress's bundled TinyMCE (wp.editor). The hub pipeline stays markdown-only, so the editor's limited HTML is converted server-side before anything reaches the hub — a later UI task builds the editor itself. - Page::enqueue() now calls wp_enqueue_editor() on the Community page so wp.editor/TinyMCE is available to the JS app. - New EpicWP\Roundtable\Html\HtmlToMarkdown: converts a limited, allow-listed HTML tag set (p, br, strong/b, em/i, ul/ol/li with indented nesting, a[href] restricted to http/https/mailto, code, pre, h3, h4, blockquote) to markdown. Every other tag is unwrapped (text content kept); script/style are dropped entirely, including their content. Pure PHP (DOMDocument, no Composer dependency), never fatals — malformed input falls back to stripped plain text, and input with no HTML markers passes through byte-for-byte unchanged. - TopicController and CommentsController::handleCreate now run the body through HtmlToMarkdown::convert() before calling the HubClient. Validation still checks the ORIGINAL string's non-emptiness (unchanged semantics); the hub keeps enforcing size limits on the converted result. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…(wp.editor) UI half of the WYSIWYG scope, on top of the PHP plumbing (wp_enqueue_editor(), HtmlToMarkdown server-side conversion). Both composers now submit the editor's raw HTML instead of hand-typed markdown; the proxies convert it server-side. - New assets/src/wysiwyg-editor.jsx: a shared WysiwygEditor wraps `wp.editor` (classic TinyMCE) to match the plain-textarea value/onInput contract used elsewhere. Uncontrolled after mount like any native rich-text widget — `value` seeds the initial content only, every edit reports back via onInput(html), and a changing `resetNonce` clears the editor in place (for the comment composer, after a successful post) without unmounting it. Falls back to a plain controlled textarea when window.wp.editor is unavailable. Also exports `htmlHasText()` (strips tags/ to decide whether an editor is meaningfully empty — an "empty" TinyMCE doc is usually `<p></p>`, not `''`) and `uniqueEditorId()` (so the New-topic dialog and a comment composer can coexist without id collisions). - publish-dialog.jsx (direct mode only — the chat-drafted review flow is untouched): body field is now a WysiwygEditor with `formatselect bold italic bullist numlist link code` and block_formats limited to Paragraph/Heading/Subheading, ~220px tall. Submit gating now checks htmlHasText(summary) instead of a raw truthiness check. - comment-editor.jsx: replaced the hand-rolled Write/Preview tabs + B/I/code/ link/list/quote/fence toolbar with a WysiwygEditor restricted to `bold italic bullist numlist link` — no headings/format select. The moderation-notice/disabled-submit behavior for pending topics is unchanged (submitDisabled still only gates the submit button; the editor still renders). Dropped the Cmd/Ctrl+Enter submit shortcut and the "Markdown is supported" hint along with the markdown toolbar they described — not requested, and the shortcut would need separate iframe-level keydown plumbing TinyMCE doesn't give for free. - styles.css: removed the now-orphaned hand-rolled-toolbar CSS (rt-ed-tools/rt-ed-tool/rt-ed-sep/rt-ed-grow/rt-ed-md/rt-ed-preview); kept rt-ed-top/rt-ed-tabs/rt-ed-tab/rt-ed-preview-empty (still used by publish-dialog's unchanged review-mode markdown editor). Added a defensive, unscoped z-index rule for TinyMCE's floating panels and the "Insert/edit link" dialog (rendered as direct children of <body>, outside #roundtable-app) so they aren't hidden behind our modal overlay (z-index:100000) — applied proactively since this couldn't be verified in a live browser from this environment; covers both the TinyMCE 4 (mce-*) skin WordPress bundles today and 5/6 (tox-*) skins. - api.js: corrected postComment's now-stale "markdown" doc comment. Tests: node's test runner has no TinyMCE, so only what's statically testable — htmlHasText/uniqueEditorId (pure functions), WysiwygEditor's fallback vs. seed-textarea branch selection, and CommentEditor's submit-disabled logic on empty/real content. Updated topic-detail.test.mjs's assertions for the now-removed Write/Preview/toolbar markup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…xt runs Critical from branch review: ordinary WYSIWYG prose is always wrapped in <p>, so it always took the DOM path — but literal text like "- I tried updating..." or "# 1 priority" or "1. First try..." or "Run `npm install` first." passed straight through renderText() unescaped, so the downstream markdown renderer reinterpreted the user's literal characters as a bullet list, a heading, an ordered list, and inline code, respectively. Turndown-style fix: every literal text run is now escaped so markdown-significant characters render as what was typed, not as syntax — leading-of-line constructs (#, -, +, >, digit. or digit)) only at the very start of a text node, and inline constructs (backtick, *, _, [, ], \) wherever they appear. Text inside code/pre is exempt (already protected by its own backticks/fence) via a new ancestor check. Coherent contract: this is now applied on every exit path of convert() — the DOM render, the malformed-input plain-text fallback, and the no-markup-to-parse fast path — documented in the class docblock. The fast path used to return truly byte-for-byte-unchanged text; it now escapes too, since plain text with no `<`/`&` can still contain the same markdown-significant characters. Tests: one per repro (leading dash/heading/ordered-list/backtick), plus leading +/>, inline */_/[]/backslash, a non-leading dash left alone, the fast-path/DOM-path consistency case, code/pre content NOT escaped, the malformed-HTML fallback also escaping, and a regression check that toolbar-produced bold/list/link markdown still converts normally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Topic body and comment composer become real WYSIWYG editors using WordPress's bundled TinyMCE (wp.editor API, no new JS dependency). Topics: Paragraph/H3/H4 + bold, italic, lists, link, code. Comments: bold, italic, lists, link (no headings). The hub pipeline stays markdown: a whitelist-based HtmlToMarkdown converter (with Turndown-style escaping of markdown-significant characters in literal text) runs server-side in the proxies and doubles as sanitization — disallowed tags, attributes and URL schemes are stripped.
Reviewed (branch review + scoped re-review, XSS paths verified clean) and browser-verified end-to-end: formatted submissions arrive as clean markdown; the wp-link dialog stacks above the SDK modal.
Tests: PHP 220/220, JS 154/154, phpcs + phpstan clean; bundle rebuilt and committed.
🤖 Generated with Claude Code