社区新闻

在主题中构建基于块的动态附件模板

自 WordPress 6.4 起,前端附件(媒体)页面视图对于新的 WordPress 安装已被禁用。这一变化酝酿已久——附件页面多年来已逐渐失宠。但它们仍然可以发挥作用,尤其是在图片库或摄影网站上。

无论你喜欢还是讨厌它们,附件都是主题开发中必须面对的现实,特别是如果你要公开发布你的主题。仅仅因为新安装默认禁用它们,并不意味着没有数百万个 WordPress 网站仍然启用着它们,或者新用户不会启用它们。

我百分之百确定我是少数派,但我碰巧仍然喜欢前端附件视图。也许这是对 WordPress 早期时代的怀念。或许只是我在回忆过去为客户构建完整图片库网站的日子。

无论你自己是否使用它们,确保附件视图正常工作并符合你的主题设计都很重要。

在构建块主题时,这是一个问题,特别是如果你计划包含自定义附件模板。WordPress 目前没有允许你将动态数据(如当前附件 ID)附加到其上的媒体块。

这个问题可能会通过块绑定/连接 API以及将自定义字段连接到块的能力得到解决。只有时间能证明,未来的功能无法解决我们今天的问题。但在重构时记住它们是好的。

在本教程中,你将了解在设计附件模板时会面临哪些问题,以及我推荐的解决方法。我还将提供一些你可以探索的替代路径。

你可以通过Dynamic Attachments 仓库来学习本教程中的代码示例。

基于块的附件模板存在的问题

首先,让我们看看使用Twenty Twenty-Four主题时,默认附件页面的样子。在下面的图库中,你可以看到图片和视频附件页面:

图片附件页面的截图,显示山脉的缩略图。 视频附件页面的截图,显示一只鸟。

作为主题设计师,你可能立即想解决一些问题:

  • 图片附件显示的是小缩略图,而不是至少能填满内容区域宽度的较大尺寸。
  • 视频附件使用旧的 MediaElement.js 播放器,而不是核心视频块使用的浏览器默认播放器。
  • 作者和日期显示在标题下方,这对于附件来说通常是不需要的。这些也通常不是媒体文件本身的作者和日期。
  • 对于大多数用例来说,不需要评论表单。

这里发生了几件事。因为 Twenty Twenty-Four 主题不包含自定义的 templates/attachment.html 文件,WordPress 会自动回退到 templates/single.html 模板,该模板是专门为博客文章设计的。

有关如何为前端视图选择模板的更多信息,请查看主题手册中的模板层次结构文档。

当主题不包含附件模板时,WordPress 会自动过滤内容,在其前面添加媒体输出。但这并不总是与每个主题都搭配得很好。

在经典主题中,通过在 attachment.php 模板中编写一些自定义的 HTML 和 PHP 代码,处理起来很简单。

对于块主题,问题在于 WordPress 没有为附件模板提供块支持。使用块模板获得完美的附件页面设计有点棘手,但并非无法克服。

所以,让我们继续前进,以一种适合你主题的方式来解决这个问题。

自定义前端附件视图

要创建更好的附件模板,你需要一种方法来使用带有动态媒体数据的核心 WordPress 块。例如,对于图片附件,你可能希望获取图片文件 URL、替代文本和标题,并将它们与核心图片块关联起来。

我发现的最佳方法是在前端渲染内容时对其进行过滤。这样,你可以让用户在 UI 中控制编辑附件模板。并且你不必担心他们会破坏你漂亮的媒体功能。

本教程的其余部分将假设你正在使用 Twenty Twenty-Four 或基于它的子主题。当然,你会希望将这些技术应用到你自己的主题中,但目前我们需要一个共同的基础来工作。

这里有两条路可以走:

  • 创建一个自定义的 templates/attachment.html 模板来完全控制其外观。
  • 避免创建 templates/attachment.html 模板,只自定义媒体输出。

两者都是有效的选择,但你将在以下部分中了解到每种情况需要做什么。

启用附件页面进行测试

在继续之前,让我们确保你实际上可以在开发环境中测试附件。请记住,新的 WordPress 安装会禁用附件页面,因此如果你正在启动一个新站点进行开发,则需要打开它们。

将此代码添加到你的测试站点的自定义插件中:

add_filter( 'pre_option_wp_attachment_pages_enabled', '__return_true' );

在紧急情况下,你可以将其放入主题的 functions.php 文件中,但在将主题分发给他人之前不要忘记删除它。你不想覆盖用户的偏好设置。

创建附件模板

创建自定义附件模板的最简单方法是复制 Twenty Twenty-Four 主题的 templates/single.html 文件的内容,并将其粘贴到新的 templates/attachment.html 文件中。

唯一的要求是在模板中包含对 <!-- wp:post-content /--> 块的调用。除此之外,任何你想做的设计都是可以的。

我在我的附件模板中移除了大部分额外的块标记,只留下了页眉和页脚模板部件、几个包装组块以及对文章标题和文章内容块的调用。

欢迎借用我的 attachment.html 文件并按原样使用:

<!-- wp:template-part {"slug":"header","area":"header","tagName":"header"} /-->

<!-- wp:group {"tagName":"main","align":"full"} -->
<main class="wp-block-group alignfull">

	<!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|50"},"margin":{"bottom":"var:preset|spacing|40"}}},"layout":{"type":"constrained"}} -->
	<div class="wp-block-group" style="margin-bottom:var(--wp--preset--spacing--40);padding-top:var(--wp--preset--spacing--50)">
		<!-- wp:post-title {"level":1,"fontSize":"x-large"} /-->
	</div>
	<!-- /wp:group -->

	<!-- wp:post-content {"lock":{"move":false,"remove":true},"align":"full","layout":{"type":"constrained"}} /-->

</main>
<!-- /wp:group -->

<!-- wp:template-part {"slug":"footer","area":"footer","tagName":"footer"} /-->

现在检查以确保模板正确出现在站点编辑器中。你可以通过访问 WordPress 管理后台的 外观 > 编辑器 > 模板 > 附件页面 来做到这一点。

WordPress 站点编辑器显示附件模板。左侧是模板的描述。右侧面板是模板的预览。

在这里你根本看不到任何媒体输出或媒体块。这些必须在前端动态插入。

禁用 WordPress 的默认过滤器

如果你没有构建自定义附件模板,WordPress 会自动插入一些默认的媒体输出。它通过对内容运行 prepend_attachment 过滤器来实现这一点。

对于这种情况,将此代码添加到你的 functions.php 中:

remove_filter( 'the_content', 'prepend_attachment' );

如果你正在构建自定义附件模板,则不需要执行此步骤。但无论如何,移除过滤器不会对你的模板产生负面影响。

输出动态媒体块标记

现在我们需要一种在前端插入自定义媒体输出的方法。我在这里考虑了几个选项,但我认为最好的方法是过滤 render_block_core/post-content 钩子的输出。

从现在开始,你将在主题的 functions.php 文件中构建一个自定义过滤器函数。我们将逐步讲解该函数的每个部分,以便你了解它在做什么——也许它甚至会给你一些关于未来其他自定义过滤器的想法。

首先在你的 functions.php 文件中添加过滤器调用和函数:

add_filter( 'render_block_core/post-content', 'themeslug_render_block', 10, 3 );

function themeslug_render_block( $block_content, $block, $instance ) {
	// 自定义代码将放在这里。
}

本节的其余代码片段应放在函数内部。

每当文章内容块在前端渲染时,render_block_core/post-content 钩子就会运行。你当然不希望你的过滤器在每个此块的实例上都运行,因此你需要在运行代码之前检查是否满足以下条件:

  • 必须通过块上下文传递文章 ID。
  • 我们必须正在查看给定文章 ID 的前端附件页面(通过 is_attachment() 条件函数检查)。

如果任何条件为 false,你必须返回原始的、未更改的块内容。

现在将此代码添加到你的函数中:

if (
	empty( $instance->context['postId'] )
	|| ! is_attachment( $instance->context['postId'] )
) {
	return $block_content;
}

现在你需要为输出附件页面的媒体部分创建一个局部模板(即小的 PHP 模板文件)层次结构。

为此,你需要四个用于处理不同类型媒体的局部文件。将这些空文件添加到主题的自定义 /partials 文件夹中:

  • attachment-media-audio.php
  • attachment-media-image.php
  • attachment-media-video.php
  • attachment-media.php(回退文件)

你还不需要担心这些文件中放什么内容。我们会讲到那一步,但在构建层次结构时,了解我们正在寻找哪些文件是有帮助的。

下一段代码中最重要的部分是 wp_attachment_is() 函数。你用它来确定附件页面关联的媒体类型。

要构建你的局部模板层次结构,请将此代码添加到你的 functions.php 文件中的 themeslug_render_block() 函数内:

$partials = [];
$html     = '';

foreach ( [ 'image', 'video', 'audio' ] as $type ) {
	if ( wp_attachment_is( $type, $instance->context['postId'] ) ) {
		$partials[] = "partials/attachment-media-{$type}.php";
		break;
	}
}

$partials[] = 'partials/attachment-media.php';

我们开始接近真正神奇的部分了。但首先,有几个重要的步骤。你的函数需要:

  • 使用 locate_template() 函数定位并包含局部模板文件。你还需要将文章 ID 传递给 $args 参数,以便它在局部模板中可用。
  • 使用 PHP 输出缓冲来捕获局部模板的输出。
  • 如果没有捕获到任何内容,则返回块内容。

将此代码添加到你的函数中:

ob_start();

locate_template( $partials, true, false, [
	'post_id' => $instance->context['postId']
] );

$block_markup = ob_get_clean();

if ( ! $block_markup ) {
	return $block_content;
}

你可以通过直接文件路径包含局部模板文件,但使用 locate_template() 可以让子主题覆盖它。

到目前为止,这个函数已经包含了很多内容,但我们到了最后阶段。最后的任务是:

  • 使用 parse_blocks() 函数解析从局部模板返回的块标记。
  • 使用 render_block() 函数渲染每个块,并将其附加到 $html 变量。
  • 返回块内容,并在其前面添加媒体输出。

将此最终代码添加到你的 themeslug_render_block() 函数中:

foreach ( parse_blocks( $block_markup ) as $parsed_block ) {
	$html .= render_block( $parsed_block );
}

return $html . $block_content;

通过访问你站点上的任何附件页面并确保没有损坏来测试你的代码。

你还不会看到任何媒体输出,因为你的局部模板中没有任何块标记。所以现在让我们来做这件事。

构建动态附件局部模板

在上一节中,你向主题的 /partials 文件夹添加了四个空文件:

  • attachment-media-audio.php
  • attachment-media-image.php
  • attachment-media-video.php
  • attachment-media.php(回退文件)

你将使用这些局部模板来编写有效的块标记。你之前编写的函数将解析该标记,并在用户访问附件页面时将其注入到文章内容块中。

你从自定义 PHP 文件而不是基于 HTML 的模板部件或模式中执行此操作,是因为你需要能够通过 PHP 动态获取所需数据(如媒体 URL)。

你使用哪些块来构建局部模板完全取决于你,但下表应该给你一些可以使用的块:

文件
attachment-media-audio.php音频
attachment-media-image.php图片、封面、媒体与文本
attachment-media-video.php视频
attachment-media.php文件

获取所需块标记的最简单方法是在编辑器中插入一个你选择的空块,然后点击工具栏中的复制按钮。

文章编辑器中的 WordPress 图片块,显示了选项工具栏下拉菜单。"复制"选项被高亮显示。

这将为你提供图片块的以下代码:

<!-- wp:image -->
<figure class="wp-block-image"><img alt=""/></figure>
<!-- /wp:image -->

你需要为你想要在局部模板中使用的每个块重复此过程。

在接下来的步骤中,你会注意到我还使用了一个包装组块。这是为了更好地控制页面的布局。请随意尝试不同的东西,走你自己的路。毕竟,你构建这个是为了适应你的主题。

构建回退局部模板

对于我的回退局部模板,我决定使用核心内置的文件块。将此代码复制并粘贴到你的 partials/attachment-media.php 文件中:

<?php
// 获取动态附件数据。
$url = wp_get_attachment_url( $args['post_id'] );
?>
<!-- wp:group {"align":"full","layout":{"type":"constrained"}} -->
<div class="wp-block-group alignfull">

	<!-- wp:file {"id":<?php echo absint( $args['post_id'] ); ?>,"href":"<?php echo esc_url( $url ); ?>"} -->
	<div class="wp-block-file">
		<a href="<?php echo esc_url( $url ); ?>"><?php the_title(); ?></a>
		<a href="<?php echo esc_url( $url ) ?>" class="wp-block-file__button wp-element-button" download><?php esc_html_e( 'Download', 'x3p0-ideas' ); ?></a>
	</div>
	<!-- /wp:file -->

</div>
<!-- /wp:group -->

现在查看你站点前端的任何附件页面。你应该看到类似这样的内容: