feat(submit): add video cover picker with playable video preview

Featured video preview now renders a real DPlayer (video-url/video-pic)
like the reference theme instead of a static image, so the cover video
plays on the submit page. Top-right bar gains the video-cover button
shown only when a video cover is loaded; backend honors pic_id and no
longer deletes the thumbnail before cover validation. Drop dead i18n
keys and data-can-* attributes.
This commit is contained in:
2026-09-05 23:41:22 +08:00
parent d84d485ec7
commit 51a591e2b9
7 changed files with 188 additions and 35 deletions
@@ -0,0 +1,90 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const root = path.resolve(__dirname, '..', '..');
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
test('featured box renders a playable video preview and a video-cover button', () => {
const featured = read('templates/parts/submit/featured.php');
assert.match(featured, /class="new-dplayer dplayer-scale-height"/);
assert.match(featured, /video-url="<\?php echo esc_url\(\(string\) \(\$data\['url'\] \?\? ''\)\); \?>"/);
assert.match(featured, /video-pic="<\?php echo esc_url\(\(string\) \(\$data\['pic'\] \?\? ''\)\); \?>"/);
assert.match(featured, /fa fas fa-play-circle/);
// The video-cover button is capability-gated like the video button.
assert.match(featured, /if \(\$can_video\) : \?>\s*\n\s*<button type="button" class="but btn-video-pic" data-featured-action="video_pic">/);
// Dead capability attributes are gone.
assert.doesNotMatch(featured, /data-can-(image|slide|video)=/);
// The static image fallback for video covers must not come back.
assert.doesNotMatch(featured, /featured-video-placeholder/);
});
test('front-submit.js re-renders the preview as a DPlayer and reuses the runtime initializer', () => {
const js = read('assets/js/front-submit.js');
assert.match(js, /new-dplayer dplayer-scale-height/);
assert.match(js, /video-url="' \+\s*escapeHtml\(d\.url \|\| ''\)/);
assert.match(js, /video-pic="' \+\s*escapeHtml\(d\.pic \|\| ''\)/);
assert.match(js, /typeof window\.auto_fun === 'function'/);
assert.match(js, /destroyFeaturedPlayer/);
assert.match(js, /data\('dplayer'\)/);
// The dedicated video-cover picker action.
assert.match(js, /act === 'video_pic'/);
assert.match(js, /i18n\.setVideoCoverTitle/);
assert.match(js, /pic_id: cover \? \(cover\.attachment_id \|\| cover\.id \|\| 0\) : 0/);
});
test('submit.css toggles the video-cover button like the reference theme', () => {
const css = read('assets/css/frontend/pages/submit.css');
assert.match(css, /\.featured-edit \.btns-full \.btn-video-pic \{\s*\n display: none;\s*\n\}/);
assert.match(css, /\.featured-loaded\.video \.btns-full \.btn-video-pic \{\s*\n display: inline-flex;\s*\n\}/);
assert.doesNotMatch(css, /featured-video-placeholder/);
});
test('the backend accepts pic_id and never wipes the thumbnail before validation', () => {
const featured = read('src/Core/FrontSubmitFeatured.php');
const videoBranch = featured.slice(featured.indexOf("case 'video':"), featured.indexOf("case 'slide':"));
assert.match(videoBranch, /absint\(\$payload\['pic_id'\] \?\? 0\)/);
assert.match(videoBranch, /!\$this->can_use_front_submit_image\(\$pic_id, \$user_id\)/);
assert.match(videoBranch, /wp_get_attachment_image_url\(\$pic_id, 'full'\)/);
assert.match(videoBranch, /set_post_thumbnail\(\$post_id, \$pic_id\)/);
// delete_post_thumbnail only runs when no valid cover picture remains.
const deletePos = videoBranch.indexOf('delete_post_thumbnail($post_id)');
const picGuard = videoBranch.indexOf("if ($pic !== '') {");
assert.ok(picGuard !== -1 && deletePos > picGuard, 'delete_post_thumbnail must be guarded by the pic check');
assert.doesNotMatch(videoBranch, /upload_submit_image/);
});
test('i18n exposes the video-cover strings and dead keys are removed', () => {
const page = read('templates/pages/page-submit.php');
const editorJs = read('assets/js/front-submit-editor.js');
const submitJs = read('assets/js/front-submit.js');
assert.match(page, /'setVideoCover' => __\('设置视频首图封面', 'aksrt'\)/);
assert.match(page, /'setVideoCoverTitle' => __\('为视频设置首图封面', 'aksrt'\)/);
assert.doesNotMatch(page, /'(pickImage|pickVideo|pickVideoCover|pickVideoResource|videoUrl)'/);
// data-video-name / mediaUrl stay: pinned by tinymce.css and the
// front-submit-attachment contract respectively.
assert.match(editorJs, /data-video-name/);
assert.match(editorJs, /cfg\.mediaUrl/);
assert.doesNotMatch(submitJs, /i18n\.pickVideoCover/);
});
test('the unused aksrt_update_post ajax endpoint is removed', () => {
const frontSubmit = read('src/Core/FrontSubmit.php');
assert.doesNotMatch(frontSubmit, /aksrt_update_post/);
assert.doesNotMatch(frontSubmit, /ajax_update_post/);
});