fix(widgets): restore single-card reference UI for search and quote widgets
The 搜索框 and 一言 sidebar widgets fell into the generic renderer branch, so they rendered a .jio-panel card wrapping a second bespoke card (.jio-widget-search / .jio-widget-quote). In-article 一言 also used divergent modifier classes and an extra hover rule. - search: render through the shared jio_get_search_box() (legacy keyword, history and category pickers) inside one reference card, mirroring the reference widget's markup and title default 「搜索」 - quote: render the reference .yiyan-box card itself as the click root; the shell now emits only the reference widget-container/title wrapper - article 一言: adopt the reference class lists for header, footer and the standalone after-article box; drop the invented yiyan--* modifiers and the duplicate hover/opacity rules - client: fill .cn/.en in the reference order (first line .cn, second .en) and drop the stale window.JioYiyan bridge plus the dead qv-yiyan.php AJAX path in runtime main.js - schema: replace the ad-hoc search_style radio with the reference switches (keywords, history, input category, more_cats) and default title - assets: enqueue assets/js/yiyan.js through the quote widget's asset list Tests: widget-system suite 820/821 (the one failure is the pre-existing frontend-lifecycle theme-init stub); new search/quote card contracts pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1549d5b25d
commit
f80ca1e27d
19 files changed
+276
-182
No files matched your search
Vendored
+14
-4
@@ -183,10 +183,15 @@
|
||||
"layout_pin_m",
|
||||
"layout_bg",
|
||||
"placeholder",
|
||||
"search_style"
|
||||
"show_keywords",
|
||||
"show_history",
|
||||
"keywords_title",
|
||||
"show_input_cat",
|
||||
"in_cat",
|
||||
"more_cats"
|
||||
],
|
||||
"settings": {
|
||||
"title": "Jio · Search",
|
||||
"title": "搜索",
|
||||
"subtitle": "固定内容与交互状态视觉验收",
|
||||
"title_style": "",
|
||||
"title_highlight": "Jio",
|
||||
@@ -247,8 +252,13 @@
|
||||
"size": "cover"
|
||||
}
|
||||
},
|
||||
"placeholder": "输入关键词搜索",
|
||||
"search_style": "button"
|
||||
"placeholder": "开启精彩搜索",
|
||||
"show_keywords": true,
|
||||
"show_history": true,
|
||||
"keywords_title": "热门搜索",
|
||||
"show_input_cat": true,
|
||||
"in_cat": "101",
|
||||
"more_cats": "101,102"
|
||||
},
|
||||
"root_selector": "[data-jio-widget=\"search\"]",
|
||||
"states": [
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('搜索框与一言小工具只渲染参考主题的一张卡片', () => {
|
||||
const rendered = JSON.parse(
|
||||
execFileSync(process.env.PHP_BINARY || 'php', [path.join(__dirname, 'search-quote-render-harness.php')], {
|
||||
encoding: 'utf8',
|
||||
})
|
||||
);
|
||||
|
||||
// 参考主题这两个小工具忽略 before_widget,卡片由自身输出一次。
|
||||
assert.doesNotMatch(rendered.search, /jio-widget-shell/);
|
||||
assert.doesNotMatch(rendered.quote, /jio-widget-shell/);
|
||||
|
||||
// 搜索框:widget-container > 标题 + 单张 .widget-search 卡片,且复用共享搜索组件。
|
||||
assert.equal((rendered.search.match(/class="jio-panel/g) || []).length, 1);
|
||||
assert.match(rendered.search, /<div class="widget-container"><div><div class="box-body notop"><div class="title-theme">/);
|
||||
assert.match(rendered.search, /class="jio-panel widget-search"><div class="search-input">/);
|
||||
assert.equal((rendered.search.match(/class="search-input"/g) || []).length, 1);
|
||||
|
||||
// 一言:widget-container > 标题 + 单张 .yiyan-box 卡片(点击根元素即卡片)。
|
||||
assert.equal((rendered.quote.match(/class="yiyan-box/g) || []).length, 1);
|
||||
assert.match(rendered.quote, /<div class="widget-container"><div><div class="box-body notop"><div class="title-theme">/);
|
||||
assert.match(rendered.quote, /class="yiyan-box main-bg text-center box-body radius8 main-shadow jio-yiyan" data-jio-yiyan>/);
|
||||
assert.match(rendered.quote, /<div class="yiyan"><\/div>/);
|
||||
});
|
||||
|
||||
test('搜索框设置沿用参考主题字段,不再提供自造样式选项', () => {
|
||||
const schema = read('src/WidgetSystem/WidgetSchemas.php');
|
||||
const renderer = read('src/WidgetSystem/Renderers/GeneralRenderer.php');
|
||||
const frontend = read('src/WidgetSystem/FrontendRenderer.php');
|
||||
|
||||
for (const field of ["'id' => 'show_keywords'", "'id' => 'show_history'", "'id' => 'keywords_title'", "'id' => 'show_input_cat'", "'id' => 'in_cat'", "'id' => 'more_cats'"]) {
|
||||
assert.match(schema, new RegExp(field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
}
|
||||
assert.doesNotMatch(schema, /search_style/);
|
||||
assert.doesNotMatch(renderer, /jio-widget-search/);
|
||||
assert.match(renderer, /function_exists\('jio_get_search_box'\)/);
|
||||
|
||||
// 与 TOC 一样由内容自带卡片壳,不走通用 .jio-panel 包裹分支。
|
||||
assert.match(frontend, /\[['"]search['"], ['"]quote['"]\]/);
|
||||
assert.match(frontend, /render_reference_widget\(\$definition, \$instance, \$widget, \$content, true\)/);
|
||||
assert.match(frontend, /bool \$own_card = false/);
|
||||
});
|
||||
|
||||
test('文章内一言沿用参考主题的类名组合,不再使用自定义修饰类', () => {
|
||||
const single = read('inc/helpers/jio-single-render.php');
|
||||
const css = read('assets/css/frontend/single-post/hidden-content.css');
|
||||
|
||||
assert.equal((single.match(/'article-yiyan theme-box text-center radius8 main-shadow yiyan-box jio-yiyan'/g) || []).length, 2);
|
||||
assert.match(single, /'yiyan-box main-bg theme-box text-center box-body radius8 main-shadow jio-yiyan'/);
|
||||
assert.doesNotMatch(single, /yiyan--header|yiyan--footer|yiyan--box/);
|
||||
assert.doesNotMatch(css, /yiyan--header|yiyan--footer|yiyan--box/);
|
||||
});
|
||||
|
||||
test('遗留的 qv-yiyan.php 渲染请求已移除', () => {
|
||||
const runtime = read('assets/js/runtime/main.js');
|
||||
assert.doesNotMatch(runtime, /qv-yiyan\.php/);
|
||||
assert.doesNotMatch(runtime, /yiyan_nr\(/);
|
||||
assert.match(read('assets/js/yiyan.js'), /action=jio_yiyan/);
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Render the search and quote widgets through production code and report the
|
||||
* markup, so the contract test can assert the reference single-card shell.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Jio\WidgetSystem {
|
||||
final class AssetManager {
|
||||
public static function enqueue_for(WidgetDefinition $definition): void {}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
require __DIR__ . '/bootstrap.php';
|
||||
|
||||
use Jio\WidgetSystem\SchemaWidget;
|
||||
use Jio\WidgetSystem\WidgetDefinition;
|
||||
use Jio\WidgetSystem\WidgetRegistry;
|
||||
|
||||
if (!function_exists('jio_get_search_box')) {
|
||||
function jio_get_search_box(array $args = [], bool $echo = false) {
|
||||
return '<div class="search-input"><form class="search-form"></form></div>';
|
||||
}
|
||||
}
|
||||
if (!function_exists('jio_get_yiyan_markup')) {
|
||||
function jio_get_yiyan_markup(array $args = []): string {
|
||||
return '<div class="' . esc_attr((string) ($args['class'] ?? 'jio-yiyan')) . ' jio-yiyan" data-jio-yiyan><div class="yiyan"></div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
$cases = [];
|
||||
foreach (['search', 'quote'] as $slug) {
|
||||
$definition = WidgetRegistry::get($slug);
|
||||
$widget = new SchemaWidget($definition);
|
||||
$widget->_set(1);
|
||||
$instance = $definition->normalize(['title' => '示例标题', 'placeholder' => '开启精彩搜索']);
|
||||
ob_start();
|
||||
$widget->widget(['before_widget' => '<section class="jio-widget-shell">', 'after_widget' => '</section>'], $instance);
|
||||
$cases[$slug] = (string) ob_get_clean();
|
||||
}
|
||||
|
||||
echo json_encode($cases, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
|
||||
}
|
||||
@@ -20,15 +20,22 @@ test('article and modern widget yiyan use the reference bilingual quote source a
|
||||
assert.match(helper, /\/yiyan\/qv-yiyan\.txt/);
|
||||
assert.match(helper, /wp_ajax_nopriv_jio_yiyan/);
|
||||
assert.match(helper, /'en'\s*=>\s*\$lines\[\$i\],\s*'cn'\s*=>\s*\$lines\[\$i\s*\+\s*1\]/);
|
||||
assert.match(helper, /class="yiyan"><div class="en"><\/div><div class="cn">/);
|
||||
// 参考结构:.yiyan 初始为空,由脚本写入 .cn/.en。
|
||||
assert.match(helper, /class="yiyan"><\/div>/);
|
||||
// 参考主题:句库首行进 .cn(正文尺寸),次行进 .en(0.8em)。
|
||||
assert.match(client, /lineCn\.textContent = quote\.en/);
|
||||
assert.match(client, /lineEn\.textContent = quote\.cn/);
|
||||
assert.match(client, /REFRESH_MS\s*=\s*30000/);
|
||||
assert.match(client, /on\('auto_fun\.jio-yiyan', function \(\) \{\s*init\(document\)/);
|
||||
assert.match(client, /event\.key === 'Enter' \|\| event\.key === ' '/);
|
||||
assert.match(widgetClient, /window\.JioYiyan\.init\(figure\)/);
|
||||
assert.match(renderer, /jio_get_yiyan_markup\(\['class' => 'jio-widget-quote'\]\)/);
|
||||
// 小工具端不再单独初始化:yiyan.js 直接监听 [data-jio-yiyan]。
|
||||
assert.match(widgetClient, /scope\.querySelectorAll\('\.jio-widget-video\[data-video-url\]'\)/);
|
||||
assert.doesNotMatch(widgetClient, /JioYiyan/);
|
||||
assert.match(renderer, /yiyan-box main-bg text-center box-body radius8 main-shadow/);
|
||||
assert.match(renderer, /jio_get_yiyan_markup/);
|
||||
assert.doesNotMatch(rest, /v1\.hitokoto\.cn|function get_quote/);
|
||||
assert.match(schema, /case 'quote':\s*return \[\];/);
|
||||
assert.match(widgetStyles, /\.jio-widget-quote \.yiyan \.en/);
|
||||
assert.doesNotMatch(widgetStyles, /\.jio-widget-quote/);
|
||||
assert.ok(catalog.length > 10, 'the bundled quote file should contain a useful quote set');
|
||||
assert.equal((catalog.length - 1) % 2, 0, 'after the title, quote lines must form English/Chinese pairs');
|
||||
});
|
||||
Reference in new issue
Block a user