- 顶部改为 fixed 创作工具栏:左侧返回/品牌/模式,中间文章标题与保存状态, 右侧撤销/重做、保存草稿、预览、发布 - 新增工作台级撤销/重做快照栈,连续编辑合并记录;支持 Ctrl/Cmd+Z / Shift+Z, 不覆盖 RichText 输入框自身的编辑历史 - 移动端收敛工具栏布局,保留撤销/重做、预览、发布 - 先保存草稿再请求审核/直接发布;请求使用服务端更新后的文章与版本 ID - 固定工具栏以独立 chrome 层布局,正文避开顶部工具栏高度 - 补充固定工具栏、历史操作与键盘快捷键契约 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
316 lines
13 KiB
PHP
316 lines
13 KiB
PHP
<?php
|
||
/**
|
||
* Template Name: Jio - 写文章/投稿
|
||
* Template Post Type: page
|
||
*
|
||
* Front-end article submission page matching the reference newposts layout.
|
||
* 注意:page-newposts.php 是「最新文章」列表,不是投稿。
|
||
*
|
||
* @package Jio
|
||
*/
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
$ctx = include get_template_directory() . '/templates/parts/submit/context.php';
|
||
if (!is_array($ctx)) {
|
||
$ctx = [];
|
||
}
|
||
|
||
// Feature disabled → soft 404.
|
||
if (empty($ctx['enabled'])) {
|
||
global $wp_query;
|
||
$wp_query->set_404();
|
||
status_header(404);
|
||
nocache_headers();
|
||
include get_query_template('404');
|
||
return;
|
||
}
|
||
|
||
// Invalid edit id → strip.
|
||
if (!empty($_GET['edit']) && empty($ctx['edit_post'])) {
|
||
wp_safe_redirect(remove_query_arg('edit'));
|
||
exit;
|
||
}
|
||
|
||
// Jio Creator Studio:登录用户 + 构建产物存在时使用沉浸式 Gutenberg 工作台。
|
||
// 否则保留原有 TinyMCE 表单作为降级路径(游客投稿、未构建、无权限)。
|
||
$studio = class_exists('\Jio\Core\CreatorStudio') ? \Jio\Core\CreatorStudio::get_instance() : null;
|
||
$use_studio = $studio
|
||
&& $studio::is_built()
|
||
&& get_current_user_id() > 0
|
||
&& !empty($ctx['is_can_new']);
|
||
if ($use_studio) {
|
||
// 沉浸式工作台:不渲染主题头部/底部挂件与导航,避免与工作台顶栏重复。
|
||
// header.php 里的 all_top_fluid 挂件区、页脚挂件、浮动入口都由 body class
|
||
// 与下方过滤器一并屏蔽。
|
||
add_filter('jio_render_header', '__return_false');
|
||
add_filter('jio_render_footer', '__return_false');
|
||
add_filter('jio_render_fluid_widgets', '__return_false');
|
||
add_filter('show_admin_bar', '__return_false', 99);
|
||
|
||
get_header();
|
||
?>
|
||
<main id="main-content" role="main" tabindex="-1" class="jio-studio-shell">
|
||
<div id="jio-creator-studio">
|
||
<div class="jio-studio-skeleton" aria-live="polite">
|
||
<i class="fa fas fa-spinner fa-spin" aria-hidden="true"></i>
|
||
<?php esc_html_e('正在加载创作工作台…', 'jio'); ?>
|
||
</div>
|
||
</div>
|
||
<?php
|
||
// 人机验证挂载点:由发布抽屉在提交时移入并展示,脚本扫描只执行一次。
|
||
if (function_exists('jio_render_captcha') && function_exists('jio_is_captcha_enabled_for') && jio_is_captcha_enabled_for('front_submit')) :
|
||
?>
|
||
<div id="jio-creator-captcha" hidden>
|
||
<?php jio_render_captcha('front_submit'); ?>
|
||
</div>
|
||
<?php
|
||
endif;
|
||
?>
|
||
</main>
|
||
<?php
|
||
get_footer();
|
||
return;
|
||
}
|
||
|
||
// wp_editor() is rendered after get_header(); queue its runtime before wp_head.
|
||
wp_enqueue_editor();
|
||
|
||
// Assets.
|
||
$css = get_template_directory() . '/assets/css/frontend/pages/submit.css';
|
||
if (file_exists($css)) {
|
||
wp_enqueue_style(
|
||
'jio-submit',
|
||
get_template_directory_uri() . '/assets/css/frontend/pages/submit.css',
|
||
['jio-public'],
|
||
(string) filemtime($css)
|
||
);
|
||
}
|
||
$js = get_template_directory() . '/assets/js/front-submit.js';
|
||
$editor_js = get_template_directory() . '/assets/js/front-submit-editor.js';
|
||
if (file_exists($editor_js)) {
|
||
wp_enqueue_script(
|
||
'jio-front-submit-editor',
|
||
get_template_directory_uri() . '/assets/js/front-submit-editor.js',
|
||
['editor'],
|
||
(string) filemtime($editor_js),
|
||
true
|
||
);
|
||
}
|
||
$pay_download_js = get_template_directory() . '/assets/js/front-submit-pay-download.js';
|
||
if (file_exists($pay_download_js)) {
|
||
wp_enqueue_script(
|
||
'jio-front-submit-pay-download',
|
||
get_template_directory_uri() . '/assets/js/front-submit-pay-download.js',
|
||
['jio-front-submit-editor'],
|
||
(string) filemtime($pay_download_js),
|
||
true
|
||
);
|
||
}
|
||
if (file_exists($js)) {
|
||
wp_enqueue_script(
|
||
'jio-front-submit',
|
||
get_template_directory_uri() . '/assets/js/front-submit.js',
|
||
['loader_js', 'jio-front-submit-editor', 'jio-front-submit-pay-download'],
|
||
(string) filemtime($js),
|
||
true
|
||
);
|
||
$upload_image_max_bytes = \Jio\Core\FrontSubmit::upload_limit_bytes('image');
|
||
$upload_video_max_bytes = \Jio\Core\FrontSubmit::upload_limit_bytes('video');
|
||
$upload_resource_max_bytes = \Jio\Core\FrontSubmit::upload_limit_bytes('resource');
|
||
$image_upload_multiple = class_exists('\Jio\Attachment\UploadLimits')
|
||
? \Jio\Attachment\UploadLimits::image_batch_limit()
|
||
: 6;
|
||
$system_tools = class_exists('\Jio\Settings\ExtensionRuntime')
|
||
? \Jio\Settings\ExtensionRuntime::get_system_tools_settings()
|
||
: [];
|
||
$float_scenes = is_array($system_tools['mce_float_toolbar'] ?? null) ? $system_tools['mce_float_toolbar'] : ['pc_s', 'm_s'];
|
||
$float_toolbar = wp_is_mobile() ? in_array('m_s', $float_scenes, true) : in_array('pc_s', $float_scenes, true);
|
||
|
||
wp_localize_script('jio-front-submit-editor', 'jioSubmitData', [
|
||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||
'nonce' => wp_create_nonce('jio_submit'),
|
||
'restUrl' => esc_url_raw(rest_url('jio/v1')),
|
||
'restNonce' => wp_create_nonce('wp_rest'),
|
||
'postId' => (int) ($ctx['edit_id'] ?? 0),
|
||
|
||
'mediaUrl' => esc_url_raw(rest_url('jio/v1/front-submit/media')),
|
||
'media' => [
|
||
'image' => ['url' => esc_url_raw(rest_url('jio/v1/front-submit/media/image'))],
|
||
'video' => ['url' => esc_url_raw(rest_url('jio/v1/front-submit/media/video'))],
|
||
'file' => ['url' => esc_url_raw(rest_url('jio/v1/front-submit/media/file'))],
|
||
],
|
||
|
||
'splitUpload' => [
|
||
'enabled' => !empty($system_tools['split_upload_s']),
|
||
'minSize' => (int) ($system_tools['split_minimum_size'] ?? 20),
|
||
],
|
||
|
||
'imageUploadMultiple' => $image_upload_multiple,
|
||
'editor' => [
|
||
'canUploadImage' => !empty($ctx['can_upload_img']),
|
||
'canUploadVideo' => !empty($ctx['can_upload_video']),
|
||
'canEmbedVideo' => !empty($ctx['can_iframe']),
|
||
'canUploadFile' => !empty($ctx['can_upload_file']),
|
||
'canHide' => !empty($ctx['can_hide']),
|
||
'canPay' => !empty($ctx['can_pay']),
|
||
'floatToolbar' => $float_toolbar,
|
||
],
|
||
'uploads' => [
|
||
'image' => !empty($ctx['can_upload_img']) ? [
|
||
'url' => esc_url_raw(rest_url('jio/v1/front-submit/upload/image')),
|
||
'maxSize' => $upload_image_max_bytes,
|
||
] : null,
|
||
'video' => !empty($ctx['can_upload_video']) ? [
|
||
'url' => esc_url_raw(rest_url('jio/v1/front-submit/upload/video')),
|
||
'maxSize' => $upload_video_max_bytes,
|
||
] : null,
|
||
'resource' => !empty($ctx['can_upload_file']) ? [
|
||
'url' => esc_url_raw(rest_url('jio/v1/front-submit/upload/resource')),
|
||
'maxSize' => $upload_resource_max_bytes,
|
||
] : null,
|
||
],
|
||
'i18n' => [
|
||
'confirmLeave' => __('你有未保存的更改,确定要离开吗?', 'jio'),
|
||
'saving' => __('保存中...', 'jio'),
|
||
'saved' => __('草稿已保存', 'jio'),
|
||
'submitting' => __('提交中...', 'jio'),
|
||
'submitted' => __('投稿已提交', 'jio'),
|
||
'error' => __('操作失败,请重试', 'jio'),
|
||
'confirmDelete' => __('确定要删除这个草稿吗?', 'jio'),
|
||
'deleting' => __('正在删除草稿…', 'jio'),
|
||
'deleted' => __('草稿已删除', 'jio'),
|
||
'uploading' => __('正在上传…', 'jio'),
|
||
'uploaded' => __('上传成功', 'jio'),
|
||
'fileTooLarge' => __('文件大小超过限制', 'jio'),
|
||
'lastSaved' => __('最后保存:', 'jio'),
|
||
'preview' => __('预览文章', 'jio'),
|
||
'setVideoCoverTitle' => __('为视频设置封面', 'jio'),
|
||
'close' => __('关闭', 'jio'),
|
||
'insertImage' => __('插入图片', 'jio'),
|
||
'uploadImage' => __('上传图片', 'jio'),
|
||
'externalImage' => __('外链图片', 'jio'),
|
||
'insertVideo' => __('插入视频', 'jio'),
|
||
'uploadVideo' => __('上传视频', 'jio'),
|
||
'externalVideo' => __('外链视频', 'jio'),
|
||
'embedVideo' => __('嵌入视频', 'jio'),
|
||
'selectVideo' => __('选择或拖入视频', 'jio'),
|
||
'videoUploadHint' => __('上传完成后自动插入正文', 'jio'),
|
||
'externalVideoHint' => __('请填写视频文件地址:', 'jio'),
|
||
'embedVideoHint' => __('请输入嵌入地址或者直接粘贴 iframe 嵌入代码:', 'jio'),
|
||
'confirmEmbed' => __('确认嵌入', 'jio'),
|
||
'insertFile' => __('上传附件', 'jio'),
|
||
'uploadFile' => __('上传附件', 'jio'),
|
||
'externalFile' => __('输入地址', 'jio'),
|
||
'selectFile' => __('选择或拖入附件', 'jio'),
|
||
'fileUploadHint' => __('上传完成后自动插入正文', 'jio'),
|
||
'externalFileHint' => __('请填写附件地址:', 'jio'),
|
||
'selectImages' => __('选择或拖入图片', 'jio'),
|
||
'imageUploadHint' => __('可批量选择,上传完成后自动插入正文', 'jio'),
|
||
'selectedFiles' => __('已选择 %d 个文件', 'jio'),
|
||
'confirmUpload' => __('确认上传', 'jio'),
|
||
'externalImageHint' => __('每行填写一个图片地址:', 'jio'),
|
||
'confirmInsert' => __('确认插入', 'jio'),
|
||
'invalidMediaUrl' => __('请填写有效地址', 'jio'),
|
||
'invalidVideoUrl' => __('请填写有效的视频嵌入地址', 'jio'),
|
||
'invalidFile' => __('请选择有效文件', 'jio'),
|
||
'uploadUnavailable' => __('当前账号不能上传此类文件', 'jio'),
|
||
'mine' => __('我的媒体', 'jio'),
|
||
'mineImage' => __('我的图片', 'jio'),
|
||
'mineVideo' => __('我的视频', 'jio'),
|
||
'mineFile' => __('我的文件', 'jio'),
|
||
'search' => __('搜索', 'jio'),
|
||
'searchMedia' => __('搜索媒体(至少 2 个字符)', 'jio'),
|
||
'searchMin' => __('请输入至少 2 个字符进行搜索', 'jio'),
|
||
'clear' => __('清除', 'jio'),
|
||
'loadMore' => __('加载更多', 'jio'),
|
||
'loading' => __('加载中…', 'jio'),
|
||
'empty' => __('暂无媒体文件', 'jio'),
|
||
'emptyMedia' => __('暂无媒体文件', 'jio'),
|
||
'mediaError' => __('媒体加载失败', 'jio'),
|
||
'mediaUnavailable' => __('媒体库暂不可用', 'jio'),
|
||
'confirmSelected' => __('确认选择(%d)', 'jio'),
|
||
'maxItems' => __('最多选择 %d 个文件', 'jio'),
|
||
'maxMediaItems' => __('最多选择 %d 个文件', 'jio'),
|
||
'uploadingClose' => __('上传中,请稍候再关闭', 'jio'),
|
||
'mediaPickerUnavailable' => __('媒体选择器加载失败,请刷新页面后重试', 'jio'),
|
||
'download' => __('下载', 'jio'),
|
||
|
||
'quote' => __('引言', 'jio'),
|
||
'quoteGray' => __('灰色引言', 'jio'),
|
||
'quoteRed' => __('红色引言', 'jio'),
|
||
'quoteBlue' => __('蓝色引言', 'jio'),
|
||
'quoteGreen' => __('绿色引言', 'jio'),
|
||
'edit' => __('编辑', 'jio'),
|
||
'insert' => __('插入', 'jio'),
|
||
'highlightCode' => __('高亮代码', 'jio'),
|
||
'selectLanguage' => __('选择语言', 'jio'),
|
||
'codeGeneric' => __('通用高亮', 'jio'),
|
||
'codeTheme' => __('主题', 'jio'),
|
||
'codeFollowGlobal' => __('跟随全局设置', 'jio'),
|
||
'codeLabel' => __('代码:', 'jio'),
|
||
'hiddenContent' => __('隐藏内容', 'jio'),
|
||
'hiddenPlaceholder' => __('隐藏内容', 'jio'),
|
||
'replyVisible' => __('回复可见', 'jio'),
|
||
'loginVisible' => __('登录可见', 'jio'),
|
||
'vipVisible' => __('VIP 可见', 'jio'),
|
||
'payVisible' => __('付费可见', 'jio'),
|
||
'addResource' => __('添加资源', 'jio'),
|
||
'editResource' => __('编辑资源', 'jio'),
|
||
'downloadUrl' => __('下载地址', 'jio'),
|
||
'enterDownloadUrl' => __('请输入资源下载地址', 'jio'),
|
||
'pasteLinkHint' => __('支持直接粘贴网盘分享链接,并自动识别提取码', 'jio'),
|
||
'resourceNote' => __('资源备注', 'jio'),
|
||
'enterResourceNote' => __('请输入资源备注', 'jio'),
|
||
'resourceNoteHint' => __('例如提取码、解压密码等', 'jio'),
|
||
'clickCopy' => __('点击复制', 'jio'),
|
||
'copyName' => __('复制名称', 'jio'),
|
||
'copyContent' => __('复制内容', 'jio'),
|
||
'copyHint' => __('填写后,用户可点击资源备注复制内容', 'jio'),
|
||
'customButton' => __('自定义按钮', 'jio'),
|
||
'customButtonPlaceholder' => __('自定义下载按钮名称', 'jio'),
|
||
'selectButtonStyle' => __('选择按钮样式', 'jio'),
|
||
'extractCode' => __('提取码', 'jio'),
|
||
'extractCodeColon' => __('提取码:', 'jio'),
|
||
'confirm' => __('确认', 'jio'),
|
||
],
|
||
]);
|
||
}
|
||
|
||
get_header();
|
||
?>
|
||
<main id="main-content" role="main" class="container" tabindex="-1">
|
||
<form id="jio-submit-form" data-jio-submit-form data-dirty="false">
|
||
<div class="content-wrap newposts-wrap">
|
||
<div class="content-layout">
|
||
<?php
|
||
$main = get_template_directory() . '/templates/parts/submit/editor-main.php';
|
||
if (file_exists($main)) {
|
||
include $main;
|
||
}
|
||
?>
|
||
</div>
|
||
</div>
|
||
<div class="sidebar show-sidebar">
|
||
<?php \Jio\WidgetSystem\WidgetAreas::render('newposts_sidebar_top'); ?>
|
||
<?php
|
||
$terms = get_template_directory() . '/templates/parts/submit/sidebar-terms.php';
|
||
if (file_exists($terms)) {
|
||
include $terms;
|
||
}
|
||
$pay = get_template_directory() . '/templates/parts/submit/sidebar-pay.php';
|
||
if (file_exists($pay)) {
|
||
include $pay;
|
||
}
|
||
$actions = get_template_directory() . '/templates/parts/submit/sidebar-submit.php';
|
||
if (file_exists($actions)) {
|
||
include $actions;
|
||
}
|
||
?>
|
||
<?php \Jio\WidgetSystem\WidgetAreas::render('newposts_sidebar_bottom'); ?>
|
||
</div>
|
||
</form>
|
||
</main>
|
||
<?php
|
||
get_footer();
|