feat(submit): fold slide cover into multi-select image picker

Match the featured cover buttons row to the reference theme: drop the
dedicated slide button, let the icon-only image picker select multiple
images into a slide cover (single pick stays an image cover), and align
the loaded-state visibility matrix.
This commit is contained in:
kate522 committed 2026-09-06 10:30:27 +08:00
1 parent 1a0b023658
commit d0c6c3e431
5 files changed
+86 -31

No files matched your search

+1
View File
@@ -56,6 +56,7 @@
!/tests/front-submit/ !/tests/front-submit/
/tests/front-submit/* /tests/front-submit/*
!/tests/front-submit/video-cover-contract.test.cjs !/tests/front-submit/video-cover-contract.test.cjs
!/tests/front-submit/featured-buttons-contract.test.cjs
!/tests/front-submit/editor-fullscreen-contract.test.cjs !/tests/front-submit/editor-fullscreen-contract.test.cjs
!/tests/asset-ledger/ !/tests/asset-ledger/
!/tests/asset-ledger/*.php !/tests/asset-ledger/*.php
+4 -5
View File
@@ -546,9 +546,10 @@ body.aksrt-editor-media-open {
font-size: 1em; font-size: 1em;
} }
/* 纯图标按钮(设置视频封面/视频/清除):fa 字形行高与文字不同导致图标上偏, /* 纯图标按钮(图片/设置视频封面/视频/清除):fa 字形行高与文字不同导致图标上偏,
用 flex 居中修正;并去掉 legacy .but 给图标加的文字间距。btn-delete 的 用 flex 居中修正;并去掉 legacy .but 给图标加的文字间距。btn-delete 的
hidden 属性切换依赖默认 display,故用 :not([hidden]) 守卫。 */ hidden 属性切换依赖默认 display,故用 :not([hidden]) 守卫。 */
.featured-edit .btns-full .btn-image,
.featured-edit .btns-full .btn-video, .featured-edit .btns-full .btn-video,
.featured-edit .btns-full .btn-delete:not([hidden]) { .featured-edit .btns-full .btn-delete:not([hidden]) {
display: inline-flex; display: inline-flex;
@@ -566,6 +567,7 @@ body.aksrt-editor-media-open {
justify-content: center; justify-content: center;
} }
.featured-edit .btns-full .btn-image > .fa,
.featured-edit .btns-full .btn-video-pic > .fa, .featured-edit .btns-full .btn-video-pic > .fa,
.featured-edit .btns-full .btn-video > .fa, .featured-edit .btns-full .btn-video > .fa,
.featured-edit .btns-full .btn-delete > .fa { .featured-edit .btns-full .btn-delete > .fa {
@@ -573,11 +575,8 @@ body.aksrt-editor-media-open {
} }
.featured-loaded.image .btn-video, .featured-loaded.image .btn-video,
.featured-loaded.image .btn-slide,
.featured-loaded.slide .btn-video, .featured-loaded.slide .btn-video,
.featured-loaded.slide .btn-image, .featured-loaded.video .btn-image {
.featured-loaded.video .btn-image,
.featured-loaded.video .btn-slide {
display: none; display: none;
} }
+22 -18
View File
@@ -88,6 +88,7 @@
isDirty: false, isDirty: false,
isSubmitting: false, isSubmitting: false,
featured: { type: '', data: {} }, featured: { type: '', data: {} },
featuredOptions: {},
init: function () { init: function () {
this.form = document.getElementById('aksrt-submit-form'); this.form = document.getElementById('aksrt-submit-form');
@@ -387,6 +388,7 @@
if (args && args.type) { if (args && args.type) {
this.featured = { type: args.type, data: args.data || {} }; this.featured = { type: args.type, data: args.data || {} };
} }
this.featuredOptions = (args && args.options) || {};
} catch (e) {} } catch (e) {}
}, },
@@ -487,28 +489,30 @@
return; return;
} }
if (act === 'image') { if (act === 'image') {
// 与服务端 featured 校验对齐:多选(或仅有幻灯片权限时的单选)存为幻灯片封面,其余存为图片封面。
var coverOptions = self.featuredOptions || {};
var slideEnabled = !!coverOptions.slide;
var imageAllowed = !!coverOptions.image;
self.openMediaPicker({ self.openMediaPicker({
type: 'image', type: 'image',
multiple: false, multiple: slideEnabled,
allowExternal: false, maxItems: slideEnabled ? 30 : 1,
onSelect: function (item) {
if (!item) return;
self.setFeatured('image', { url: item.url || '', id: item.attachment_id || item.id || 0 });
},
});
return;
}
if (act === 'slide') {
self.openMediaPicker({
type: 'image',
multiple: true,
maxItems: 30,
allowExternal: false, allowExternal: false,
onSelect: function (items) { onSelect: function (items) {
var slides = (Array.isArray(items) ? items : []).map(function (item) { var list = Array.isArray(items) ? items : [items];
return { url: item.url || '', id: item.attachment_id || item.id || 0 }; var slides = [];
}).filter(function (item) { return item.url || item.id; }); list.forEach(function (item) {
if (slides.length) self.setFeatured('slide', slides); if (!item) return;
var url = item.url || '';
var id = item.attachment_id || item.id || 0;
if (url || id) slides.push({ url: url, id: id });
});
if (!slides.length) return;
if (slides.length > 1 || (slideEnabled && !imageAllowed)) {
self.setFeatured('slide', slides);
return;
}
self.setFeatured('image', slides[0]);
}, },
}); });
return; return;
+5 -8
View File
@@ -41,9 +41,11 @@ $can_video = !empty($ctx['can_cover_video']);
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="btns-full flex ac jc"> <div class="btns-full flex ac jc">
<?php if ($can_image) : ?> <?php if ($can_image || $can_slide) : ?>
<button type="button" class="but btn-image" data-featured-action="image"> <button type="button" class="but btn-image" data-featured-action="image"
<i class="fa fas fa-image"></i> <?php esc_html_e('图片', 'aksrt'); ?> title="<?php esc_attr_e('图片', 'aksrt'); ?>"
aria-label="<?php esc_attr_e('图片', 'aksrt'); ?>">
<i class="fa fas fa-image"></i>
</button> </button>
<?php endif; ?> <?php endif; ?>
<?php if ($can_video) : ?> <?php if ($can_video) : ?>
@@ -53,11 +55,6 @@ $can_video = !empty($ctx['can_cover_video']);
<i class="fa fas fa-image"></i> <i class="fa fas fa-image"></i>
</button> </button>
<?php endif; ?> <?php endif; ?>
<?php if ($can_slide) : ?>
<button type="button" class="but btn-slide" data-featured-action="slide">
<i class="fa fas fa-image"></i> <?php esc_html_e('幻灯片', 'aksrt'); ?>
</button>
<?php endif; ?>
<?php if ($can_video) : ?> <?php if ($can_video) : ?>
<button type="button" class="but btn-video" data-featured-action="video" <button type="button" class="but btn-video" data-featured-action="video"
title="<?php esc_attr_e('视频', 'aksrt'); ?>" title="<?php esc_attr_e('视频', 'aksrt'); ?>"
@@ -0,0 +1,54 @@
'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 shows only the icon image/video buttons and no slide button', () => {
const featured = read('templates/parts/submit/featured.php');
// Like the reference theme, the image button also serves slide covers via
// multi-select, so it renders whenever either capability is granted.
assert.match(
featured,
/if \(\$can_image \|\| \$can_slide\) : \?>\s*\n\s*<button type="button" class="but btn-image" data-featured-action="image"\s*\n\s*title="<\?php esc_attr_e\('图片', 'aksrt'\); \?>"\s*\n\s*aria-label="<\?php esc_attr_e\('图片', 'aksrt'\); \?>">\s*\n\s*<i class="fa fas fa-image"><\/i>\s*\n\s*<\/button>/
);
// The dedicated slide button is gone; slide covers come from multi-select.
assert.doesNotMatch(featured, /btn-slide|data-featured-action="slide"|幻灯片/);
});
test('image picker folds multi-select into a slide cover like the reference theme', () => {
const js = read('assets/js/front-submit.js');
assert.match(js, /featuredOptions = \(args && args\.options\) \|\| \{\};/);
assert.match(js, /var slideEnabled = !!coverOptions\.slide;/);
assert.match(js, /var imageAllowed = !!coverOptions\.image;/);
assert.match(js, /multiple: slideEnabled,/);
assert.match(js, /maxItems: slideEnabled \? 30 : 1,/);
assert.match(js, /slides\.length > 1 \|\| \(slideEnabled && !imageAllowed\)/);
assert.match(js, /self\.setFeatured\('slide', slides\)/);
assert.match(js, /self\.setFeatured\('image', slides\[0\]\)/);
// The standalone slide action died with its button.
assert.doesNotMatch(js, /act === 'slide'/);
});
test('submit.css mirrors the reference visibility matrix for the buttons row', () => {
const css = read('assets/css/frontend/pages/submit.css');
assert.match(
css,
/\.featured-edit \.btns-full \.btn-image,\s*\n\.featured-edit \.btns-full \.btn-video,\s*\n\.featured-edit \.btns-full \.btn-delete:not\(\[hidden\]\) \{\s*\n display: inline-flex;/
);
assert.match(css, /\.featured-edit \.btns-full \.btn-image > \.fa,[\s\S]*?\.featured-edit \.btns-full \.btn-delete > \.fa \{\s*\n margin-right: 0;\s*\n\}/);
assert.match(
css,
/\.featured-loaded\.image \.btn-video,\s*\n\.featured-loaded\.slide \.btn-video,\s*\n\.featured-loaded\.video \.btn-image \{\s*\n display: none;\s*\n\}/
);
assert.doesNotMatch(css, /btn-slide/);
});