feat(creator-studio): 图片宽度四档与作者行

- 通过 editor.BlockEdit 过滤器给 core/image 增加「半屏 / 中等 / 通栏 / 全宽」
  四档宽度,选中图片时出现在区块工具条;宽度写入 className 并序列化到
  post_content,前台样式即可生效,无需自定义区块或额外元数据
- 前台样式:半屏靠左 50%,中等 72%,通栏跟随正文行宽,全宽突破行宽但受容器
  内边距约束;移动端半屏与全宽回退为整行,避免横向滚动
- 新增作者行(头像 + 昵称 + 身份说明),数据由 CreatorRest::author_payload()
  从服务端给出,前端不自行拼装;无昵称时整行不渲染,头像缺失时回退首字占位
- 作者行放在摘要之后、正文之前

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Opus 5 committed 2026-11-06 16:05:19 +08:00
1 parent 79909f449e
commit f5e9420266
11 files changed
+680 -39

No files matched your search

@@ -0,0 +1,40 @@
/**
* 作者行:标题下方的头像 + 昵称(公众号式)。
*
* 数据来自服务端 bootstrap,前端只负责展示;没有作者信息时整行不渲染,
* 避免出现空头像占位。
*
* @package Jio
*/
import { __ } from '@wordpress/i18n';
type Author = {
name?: string;
avatar?: string;
meta?: string;
};
export function AuthorRow({ author }: { author: Author }) {
const name = typeof author?.name === 'string' ? author.name.trim() : '';
if (name === '') {
return null;
}
const avatar = typeof author?.avatar === 'string' ? author.avatar : '';
const meta = typeof author?.meta === 'string' ? author.meta : '';
return (
<div className="jio-creator__author">
{avatar !== '' ? (
<img className="jio-creator__author-avatar" src={avatar} alt="" width={24} height={24} />
) : (
<span className="jio-creator__author-avatar is-placeholder" aria-hidden="true">
{name.slice(0, 1)}
</span>
)}
<span className="jio-creator__author-name">{name}</span>
{meta !== '' && <span className="jio-creator__author-meta">{meta}</span>}
<span className="jio-creator__author-hint">{__('原创内容', 'jio')}</span>
</div>
);
}
@@ -0,0 +1,73 @@
/**
* 图片宽度预设(公众号式四档)。
*
* 通过 block filter 给 core/image 追加「半屏 / 中等 / 通栏 / 全宽」四档宽度,
* 选中图片时出现在区块工具条里。宽度存进图片区块的 className,序列化到
* post_content 后由前台样式生效,不需要自定义区块或额外元数据。
*
* @package Jio
*/
import { addFilter } from '@wordpress/hooks';
import { createHigherOrderComponent } from '@wordpress/compose';
import { BlockControls } from '@wordpress/block-editor';
import { ToolbarGroup } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
const WIDTH_CLASSES = [
{ key: 'half', label: __('半屏', 'jio'), className: 'jio-img-w--half' },
{ key: 'medium', label: __('中等', 'jio'), className: 'jio-img-w--medium' },
{ key: 'wide', label: __('通栏', 'jio'), className: 'jio-img-w--wide' },
{ key: 'full', label: __('全宽', 'jio'), className: 'jio-img-w--full' },
];
function currentPreset(className: string): string {
const found = WIDTH_CLASSES.find((w) => (className || '').split(/\s+/).includes(w.className));
return found ? found.key : 'wide';
}
function withImageWidthControls(BlockEdit: any) {
return (props: any) => {
if (props.name !== 'core/image') {
return <BlockEdit {...props} />;
}
const className = String(props.attributes?.className || '');
const active = currentPreset(className);
const setPreset = (preset: (typeof WIDTH_CLASSES)[number]) => {
const rest = className
.split(/\s+/)
.filter((c) => c && !WIDTH_CLASSES.some((w) => w.className === c));
props.setAttributes({ className: [...rest, preset.className].join(' ') });
};
return (
<>
<BlockEdit {...props} />
<BlockControls group="inline">
<ToolbarGroup
controls={WIDTH_CLASSES.map((preset) => ({
title: preset.label,
// 用文字而不是图标,四档宽度靠标签更易分辨。
children: preset.label,
isActive: active === preset.key,
onClick: () => setPreset(preset),
}))}
/>
</BlockControls>
</>
);
};
}
export function installImageWidthPresets(): void {
if ((window as any).__jioImageWidthInstalled) {
return;
}
(window as any).__jioImageWidthInstalled = true;
addFilter(
'editor.BlockEdit',
'jio/creator-studio/image-width',
createHigherOrderComponent(withImageWidthControls, 'withJioImageWidth')
);
}
+5
View File
@@ -19,8 +19,10 @@ import { __ } from '@wordpress/i18n';
import { apiPost } from './services/api';
import { installApiFetchNonce } from './services/api-fetch';
import { installMediaUploadBridge } from './services/media';
import { installImageWidthPresets } from './extensions/image-width';
import { PublishDrawer } from './panels/publish-drawer';
import { CoverStrip } from './components/cover-strip';
import { AuthorRow } from './components/author-row';
import { InsertPanel, type InsertItem } from './panels/insert-panel';
import { buildInsertGroups } from './utils/insert-items';
import { SaveState, type SaveStateValue } from './components/save-state';
@@ -420,6 +422,8 @@ function Studio(): JSX.Element {
/>
</label>
<AuthorRow author={bootstrap.author || {}} />
<div className="jio-creator__editor">
<BlockEditorProvider
value={blocks}
@@ -497,6 +501,7 @@ function mount() {
}
installApiFetchNonce(nonce);
installMediaUploadBridge(bootstrap, nonce);
installImageWidthPresets();
target.classList.add('is-ready');
createRoot(target).render(<Studio />);
}
@@ -456,6 +456,102 @@ $warn: #b4573f;
}
}
/* ---------- 图片宽度四档 ---------- */
.jio-creator__editor,
.jio-page-submit .entry-content {
.wp-block-image {
margin-left: auto;
margin-right: auto;
img {
display: block;
width: 100%;
height: auto;
border-radius: 6px;
}
}
/* 半屏:围绕正文左侧,右侧留给说明文字。 */
.wp-block-image.jio-img-w--half {
width: 50%;
margin-left: 0;
margin-right: auto;
@media (max-width: 767px) {
width: 100%;
margin-left: auto;
}
}
.wp-block-image.jio-img-w--medium {
width: 72%;
}
.wp-block-image.jio-img-w--wide {
width: 100%;
}
/* 全宽:突破正文行宽,但受容器 padding 约束避免横向滚动。 */
.wp-block-image.jio-img-w--full {
width: calc(100% + clamp(24px, 6vw, 64px));
max-width: none;
margin-left: calc(clamp(24px, 6vw, 64px) / -2);
@media (max-width: 767px) {
width: 100%;
margin-left: 0;
}
}
}
/* ---------- 作者行 ---------- */
.jio-creator__author {
display: flex;
align-items: center;
gap: 8px;
margin: 2px 0 20px;
color: #8c9a94;
font-size: 12px;
}
.jio-creator__author-avatar {
width: 24px;
height: 24px;
flex: 0 0 auto;
border-radius: 50%;
object-fit: cover;
}
.jio-creator__author-avatar.is-placeholder {
display: inline-flex;
align-items: center;
justify-content: center;
background: #e3ece7;
color: #4a5f57;
font-size: 11px;
font-weight: 600;
}
.jio-creator__author-name {
color: #4a5f57;
font-weight: 500;
}
.jio-creator__author-meta {
padding: 1px 6px;
border-radius: 4px;
background: #eef5f2;
color: #3c7d6c;
font-size: 11px;
}
.jio-creator__author-hint {
margin-left: auto;
color: #b0bcb7;
}
/* ---------- 常驻插入按钮 ---------- */
.jio-creator__insert {