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:
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')
|
||||
);
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user