feat(creator-studio): switch writing editor to TinyMCE

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Opus 5 committed 2026-11-06 20:51:18 +08:00
1 parent 4868de2cda
commit 384d2b7aa0
25 files changed
+1255 -7076

No files matched your search

@@ -25,6 +25,7 @@ const pageSubmit = read('templates/pages/page-submit.php');
const saveTrait = read('src/Core/FrontSubmitSave.php');
const domainModel = read('src/Core/DomainModel.php');
const appSource = read('assets/creator-studio/src/index.tsx');
const editorToolbar = read('assets/creator-studio/src/components/editor-toolbar.tsx');
const drawer = read('assets/creator-studio/src/panels/publish-drawer.tsx');
const api = read('assets/creator-studio/src/services/api.ts');
@@ -117,13 +118,16 @@ test('studio is mounted only on the submit template with a fallback', () => {
assert.match(studio, /wp_add_inline_script/);
});
test('studio app keeps autosave, recovery, and the publish drawer wired', () => {
test('studio app uses TinyMCE while preserving autosave, recovery, and publishing', () => {
assert.match(appSource, /useAutosave/);
assert.match(appSource, /useLocalRecovery/);
assert.match(appSource, /PublishDrawer/);
assert.match(appSource, /BlockEditorProvider/);
assert.match(appSource, /registerCoreBlocks/);
assert.match(appSource, /installMediaUploadBridge/);
assert.match(appSource, /wpEditor\.initialize\(EDITOR_ID/);
assert.match(appSource, /editor\.getContent\(\)/);
assert.match(appSource, /editorRef\.current\?\.setContent/);
assert.doesNotMatch(appSource, /BlockEditorProvider|registerCoreBlocks|BlockList|registerCoreBlocks/);
assert.doesNotMatch(creatorRest, /block_content\(/);
assert.match(creatorRest, /'post_content'\s*=>\s*\$payload\['content'\]/);
assert.match(api, /'X-WP-Nonce'/);
assert.match(drawer, /canPay/);
assert.match(drawer, /canPayDownload/);
@@ -132,23 +136,32 @@ test('studio app keeps autosave, recovery, and the publish drawer wired', () =>
assert.match(drawer, /立即发布/);
});
test('frontend enqueues the WordPress format library for inline RichText tools', () => {
test('studio enqueues the WordPress editor and TinyMCE media tools', () => {
const enqueue = read('src/Core/CreatorStudio.php');
assert.match(enqueue, /\$deps\[\]\s*=\s*'wp-format-library'/);
assert.match(enqueue, /wp-format-library/);
assert.match(read('assets/creator-studio/src/index.tsx'), /<BlockTools>/);
assert.match(read('assets/creator-studio/src/styles/studio.scss'), /block-editor-rich-text__inline-format-toolbar/);
assert.match(enqueue, /wp_enqueue_editor\(\)/);
assert.match(enqueue, /\$deps\[\]\s*=\s*'editor'/);
assert.match(enqueue, /jio-creator-tinymce-tools/);
assert.match(enqueue, /tinymce_payload/);
assert.doesNotMatch(enqueue, /wp-format-library|gutenberg-blocks\.js/);
});
test('固定顶栏包含保存状态、撤销/重做、预览和发布动作', () => {
test('fixed top toolbar has TinyMCE formatting, media, and extension actions', () => {
assert.match(appSource, /className="jio-creator__bar"/);
assert.match(appSource, /history\.undo\(\)/);
assert.match(appSource, /history\.redo\(\)/);
assert.match(appSource, /undoManager\.undo\(\)/);
assert.match(appSource, /undoManager\.redo\(\)/);
assert.match(appSource, /editor\.formatter\.toggle\(value \|\| 'h2'\)/);
assert.match(editorToolbar, /value=\{formatValue\}/);
assert.match(editorToolbar, /canUndo/);
assert.match(editorToolbar, /canRedo/);
assert.match(editorToolbar, /onCommand\('Bold'\)/);
assert.match(editorToolbar, /onCommand\('Italic'\)/);
assert.match(editorToolbar, /onOpenMedia\('image'\)/);
assert.match(editorToolbar, /onOpenMedia\('video'\)/);
assert.match(editorToolbar, /onClick=\{onOpenLink\}/);
assert.match(appSource, /保存草稿/);
assert.match(appSource, /<SaveState value=\{saveState\}/);
assert.match(appSource, /draftRef\.current\.postId/);
assert.match(appSource, /useEditorHistory/);
assert.match(appSource, /event\.shiftKey \? redo\(\) : undo\(\)/);
assert.match(editorToolbar, /role="toolbar"/);
assert.match(read('assets/creator-studio/src/styles/studio.scss'), /\.jio-creator__bar\s*\{[^}]*position:\s*fixed/);
assert.match(read('assets/creator-studio/src/styles/studio.scss'), /\.jio-creator\s*\{[\s\S]*?padding-top:\s*var\(--jc-bar\)/);
});
@@ -176,26 +189,19 @@ test('沉浸式布局屏蔽主题头部/页脚与挂件区', () => {
assert.match(pageSubmit, /add_filter\('jio_render_fluid_widgets', '__return_false'\)/);
});
test('插入面板与封面条走真实区块名与服务端能力', () => {
const insertItems = read('assets/creator-studio/src/utils/insert-items.ts');
const insertPanel = read('assets/creator-studio/src/panels/insert-panel.tsx');
const cover = read('assets/creator-studio/src/components/cover-strip.tsx');
assert.match(appSource, /createBlock/);
assert.match(insertItems, /'core\/paragraph'/);
assert.match(insertItems, /'core\/heading'/);
assert.match(insertItems, /'core\/image'/);
assert.match(insertItems, /'jio\/paid-fragment'/);
// 没有能力的条目不能出现在面板里。
assert.match(insertItems, /if \(capabilities\.canUploadImage\)/);
assert.match(insertItems, /if \(capabilities\.canPay\)/);
assert.match(insertPanel, /role="dialog"/);
assert.match(insertPanel, /event\.key === 'Escape'/);
assert.match(cover, /添加封面/);
test('TinyMCE media buttons reuse Jio uploads and preserve bookmarks', () => {
assert.match(appSource, /JioSubmitMedia/);
assert.match(appSource, /editor\.selection\.getBookmark/);
assert.match(appSource, /moveToBookmark/);
assert.match(appSource, /data-edit-file-id/);
assert.match(appSource, /data-video-url/);
assert.match(read('src/Core/CreatorStudio.php'), /front-submit\/upload\/image/);
assert.match(read('assets/css/frontend/pages/submit.css'), /\.jio-editor-media\s*\{/);
assert.match(read('assets/creator-studio/src/components/cover-strip.tsx'), /添加封面/);
assert.match(appSource, /<CoverStrip/);
});
test('中文字数按字符统计而非按空格分词', () => {
assert.match(appSource, /characters_excluding_spaces/);
assert.doesNotMatch(appSource, /countWords\([^)]*'words'/);
test('Chinese character count strips markup and counts visible text', () => {
assert.match(appSource, /Array\.from\(plainText\(draft\.content\)\)\.length/);
assert.match(appSource, /container\.textContent/);
});