需求(工单 #25):写文章时自动生成别名(翻译)、自动生成标签、 自动选择分类、自动生成摘要,问主题能不能做、是否必须用插件。 结论:主题自己就能做,不依赖任何插件。 新增 inc/theme-writing.php(四个独立引擎 + 设置读写 + 密钥处理): - 触发点:transition_post_status,且要求 new_status === publish && old_status !== publish ⇒ 只在「第一次点发布」时动手,自动保存不触发,已发布的文章一概不碰。 - 幂等锁 _yang_writing_done:同一篇只做一次,避免反复覆写。 四道「不覆盖」守卫(用户逐条确认的决策): - 摘要:只在 post_excerpt 为空时才写。 - 分类:只在文章还挂默认分类(未分类)时才动。 - 别名:只在「空」或「不是纯 ASCII」时才生成。判据不是"空"而是 "不是纯 ASCII" —— 中文标题下 WP 生成的是 %e4%bf%a1... 这种编码串, 它非空但不可用。已有英文别名的 966 篇一概不动(别名=URL,改了 就是成批 404)。 - 标签:wp_set_post_tags(..., true) 追加,绝不删除手工标签。 修一个真实产品 bug:3.2.56 及以前 apply() 里写的是 list($suggest, $_) = yang_writing_make_tags(...),$new 被整个丢掉; 接着又拿 $suggest 去 get_term_by 二次分类 —— 而 $suggest 里每个词 都来自词典、必然全部命中 ⇒「新建」分支永远进不去 ⇒ 这条功能在真实 发布流程里一次都没生效过。改为 list($exist_hits, $new_cands), 两段分别复核后合并。 标签两级策略(新博客也能用):先匹配已有标签池,匹配不到才新建, 每篇新增数量受 w_tag_new_limit 约束(默认 3,设 0 = 永不新建)。 中文分词(服务器无 scws/jieba):先按虚词与标点切短语,再在短语 内部取 2~6 字 n-gram。不切句直接取窗口会产出「星等换算与赤」 这类跨词碎片,且字面上"出现 ≥2 次"能通过所有频次闸门 —— 这是 n-gram 的固有缺陷,只能靠切分规避,调阈值无效。 虚词表统一由 yang_writing_seps() 提供(闸门④ 复用同一函数)。 翻译:默认 MyMemory(免注册),可选百度/有道。任何失败都返回空串 跳过该项,绝不阻塞发布。 密钥:存独立选项 yang_writing_secrets,不进 kratos;password 类型 + std 空串;页面只显示末 4 位;留空提交 = 不修改。 设置页:新增「写作增强」独立 heading 标签页,四个开关默认全关 (升级不改变现有行为)。framework 里 'requires' 这个键根本没被读取, 条件显示改用 options-custom.js 显式逻辑;info 类型不生成 .section 包裹层,补一条 .section-info 规则才能藏住服务商申请说明。 验证(发布前): - 写作端到端 71 条、后台渲染 21 条、密钥路径 9 条,合计 101 条全过 - 反证 22 组(W1~W16 端到端 / A1~A6 后台)× R1~R14 发布包注入, 全部让失败数从 0 增加 - 测试自身不污染:每节差分回滚 + 总闸门,标签库稳定 703 / 967 篇 - 发布走 theme-update/,主题目录仍是 3.2.56,未被覆盖
231 lines
7.9 KiB
JavaScript
231 lines
7.9 KiB
JavaScript
/**
|
||
* Custom scripts needed for the colorpicker, image button selectors,
|
||
* and navigation tabs.
|
||
*/
|
||
|
||
jQuery(document).ready(function ($) {
|
||
|
||
$('input[id^="s_"]').click(function () {
|
||
jQuery('#section-' + this.id + '_url').fadeToggle(400)
|
||
jQuery('#section-' + this.id + '_links').fadeToggle(400)
|
||
})
|
||
|
||
for (i = 0; i < $('input[id^="s_"]' + ':checked').length; i++) {
|
||
let id = $('input[id^="s_"]' + ':checked')[i].id
|
||
$('#section-' + id + '_url').show()
|
||
$('#section-' + id + '_links').show()
|
||
}
|
||
|
||
jQuery('#g_cos').click(function () {
|
||
jQuery('#section-g_cos_bucketname').fadeToggle(400)
|
||
jQuery('#section-g_cos_url').fadeToggle(400)
|
||
jQuery('#section-g_cos_accesskey').fadeToggle(400)
|
||
jQuery('#section-g_cos_secretkey').fadeToggle(400)
|
||
})
|
||
|
||
if (jQuery('#g_cos:checked').val() !== undefined) {
|
||
jQuery('#section-g_cos_bucketname').show()
|
||
jQuery('#section-g_cos_url').show()
|
||
jQuery('#section-g_cos_accesskey').show()
|
||
jQuery('#section-g_cos_secretkey').show()
|
||
}
|
||
|
||
jQuery('#g_cc_switch').click(function () {
|
||
jQuery('#section-g_cc').fadeToggle(400)
|
||
})
|
||
|
||
if (jQuery('#g_cc_switch:checked').val() !== undefined) {
|
||
jQuery('#section-g_cc').show()
|
||
}
|
||
|
||
jQuery('#g_donate').click(function () {
|
||
jQuery('#section-g_donate_alipay').fadeToggle(400)
|
||
jQuery('#section-g_donate_wechat').fadeToggle(400)
|
||
})
|
||
|
||
if (jQuery('#g_donate:checked').val() !== undefined) {
|
||
jQuery('#section-g_donate_alipay').show()
|
||
jQuery('#section-g_donate_wechat').show()
|
||
}
|
||
|
||
jQuery('#m_smtp').click(function () {
|
||
jQuery('#section-m_host').fadeToggle(400)
|
||
jQuery('#section-m_sec').fadeToggle(400)
|
||
jQuery('#section-m_port').fadeToggle(400)
|
||
jQuery('#section-m_username').fadeToggle(400)
|
||
jQuery('#section-m_passwd').fadeToggle(400)
|
||
jQuery('#section-m_sendmail').fadeToggle(400)
|
||
})
|
||
|
||
if (jQuery('#m_smtp:checked').val() !== undefined) {
|
||
jQuery('#section-m_host').show()
|
||
jQuery('#section-m_sec').show()
|
||
jQuery('#section-m_port').show()
|
||
jQuery('#section-m_username').show()
|
||
jQuery('#section-m_passwd').show()
|
||
jQuery('#section-m_sendmail').show()
|
||
}
|
||
|
||
jQuery('#g_thumbnail').click(function () {
|
||
jQuery('#section-g_postthumbnail').fadeToggle(400)
|
||
})
|
||
|
||
if (jQuery('#g_thumbnail:checked').val() !== undefined) {
|
||
jQuery('#section-g_postthumbnail').show()
|
||
}
|
||
|
||
jQuery('#top_select').change(function () {
|
||
if (jQuery("#top_select").val() == 'color') {
|
||
jQuery('#section-top_color').fadeIn(400)
|
||
jQuery('#section-top_img').fadeOut(400)
|
||
jQuery('#section-top_title').fadeOut(400)
|
||
jQuery('#section-top_describe').fadeOut(400)
|
||
} else {
|
||
jQuery('#section-top_color').fadeOut(400)
|
||
jQuery('#section-top_img').fadeIn(400)
|
||
jQuery('#section-top_title').fadeIn(400)
|
||
jQuery('#section-top_describe').fadeIn(400)
|
||
}
|
||
})
|
||
|
||
if (jQuery('#top_select').val() == 'color') {
|
||
jQuery('#section-top_color').show()
|
||
jQuery('#section-top_img').hide()
|
||
jQuery('#section-top_title').hide()
|
||
jQuery('#section-top_describe').hide()
|
||
} else {
|
||
jQuery('#section-top_color').hide()
|
||
jQuery('#section-top_img').show()
|
||
jQuery('#section-top_title').show()
|
||
jQuery('#section-top_describe').show()
|
||
}
|
||
|
||
// 自动目录:字段显隐跟着两个开关走。
|
||
//
|
||
// 「在正文中显示」勾了 → 「正文中的对齐」「自动插入位置」才有意义
|
||
// 「浮动显示」勾了 → 「浮动方向」才有意义
|
||
//
|
||
// 为什么必须藏起来:两个开关任一关闭时,它下面那几个子选项完全无效。
|
||
// 若还留着,用户会以为「选了文章结尾却没插进正文」是个 bug。
|
||
//
|
||
// 用 class 切换而不是 fadeIn/fadeOut:.section 的 display 是 flow-root
|
||
// (3.2.28 靠它建独立 BFC,避免被左侧浮动标签列的 clear:both 拉长)。
|
||
// jQuery 的 show()/hide() 会写行内 display,把那套布局打回 block,
|
||
// 等于悄悄回退 3.2.28 的修复。
|
||
//
|
||
// ⚠️ 绑定事件用 delegation('change' 挂 document、选择器过滤):
|
||
// 这两个是 checkbox,框架的 checkbox 控件可能在外面再包一层 label/inline,
|
||
// 直接绑在 input 上偶尔会因为 DOM 顺序变了而漏绑。delegation 不受影响。
|
||
function yq_toggle_toc_fields () {
|
||
var inline = jQuery('#toc_inline').is(':checked')
|
||
var float = jQuery('#toc_float').is(':checked')
|
||
|
||
jQuery('#section-toc_inline_align').toggleClass('yq-cond-hide', !inline)
|
||
jQuery('#section-toc_anchor').toggleClass('yq-cond-hide', !inline)
|
||
jQuery('#section-toc_float_side').toggleClass('yq-cond-hide', !float)
|
||
}
|
||
|
||
jQuery(document).on('change', '#toc_inline, #toc_float', yq_toggle_toc_fields)
|
||
yq_toggle_toc_fields()
|
||
|
||
// 写作增强:翻译服务商的四个凭据 + 说明,只在「自动生成英文别名」勾选时才有意义。
|
||
//
|
||
// 为什么用 requires 之外的方式:`requires` 这个键在框架里**根本没被读取**
|
||
// (grep 整个 options-framework 目录零命中,条件显示全靠本文件里这些
|
||
// 硬编码的 jQuery)。所以主题里其它 19 处 'requires' 其实都是死代码,
|
||
// 这里不再往那个坑里加,直接写显式逻辑。
|
||
//
|
||
// 为什么连说明文字一起藏:用户没勾别名生成时,那段「去百度申请 APPID」
|
||
// 只会是噪声。
|
||
//
|
||
// ⚠️ 用 delegation + toggleClass('yq-cond-hide'),不直接绑 input.hide():
|
||
// 原因与上面的自动目录一致(.section 的 display 是 flow-root,
|
||
// jQuery 会把行内 display 写成 block,回退 3.2.28 的布局修复)。
|
||
// info 那块没有 .section 类,靠 CSS 里补的 .section-info 规则命中。
|
||
function yq_toggle_writing_slug_fields () {
|
||
var on = jQuery('#w_on_slug').is(':checked')
|
||
jQuery('#section-w_provider').toggleClass('yq-cond-hide', !on)
|
||
jQuery('#section-w_baidu_appid').toggleClass('yq-cond-hide', !on)
|
||
jQuery('#section-w_baidu_key').toggleClass('yq-cond-hide', !on)
|
||
jQuery('#section-w_youdao_appid').toggleClass('yq-cond-hide', !on)
|
||
jQuery('#section-w_youdao_key').toggleClass('yq-cond-hide', !on)
|
||
jQuery('#section-w_timeout').toggleClass('yq-cond-hide', !on)
|
||
jQuery('#w_provider_help').toggleClass('yq-cond-hide', !on)
|
||
}
|
||
|
||
jQuery(document).on('change', '#w_on_slug', yq_toggle_writing_slug_fields)
|
||
yq_toggle_writing_slug_fields()
|
||
|
||
// 写作增强:「每篇最多新增几个标签」只在「自动生成标签」勾选时才有意义。
|
||
function yq_toggle_writing_tag_fields () {
|
||
var on = jQuery('#w_on_tag').is(':checked')
|
||
jQuery('#section-w_tag_new_limit').toggleClass('yq-cond-hide', !on)
|
||
}
|
||
|
||
jQuery(document).on('change', '#w_on_tag', yq_toggle_writing_tag_fields)
|
||
yq_toggle_writing_tag_fields()
|
||
|
||
// Loads the color pickers
|
||
$('.of-color').wpColorPicker()
|
||
|
||
// Image Options
|
||
$('.of-radio-img-img').click(function () {
|
||
$(this).parent().parent().find('.of-radio-img-img').removeClass('of-radio-img-selected')
|
||
$(this).addClass('of-radio-img-selected')
|
||
})
|
||
|
||
$('.of-radio-img-label').hide()
|
||
$('.of-radio-img-img').show()
|
||
$('.of-radio-img-radio').hide()
|
||
|
||
// Loads tabbed sections if they exist
|
||
if ($('.nav-tab-wrapper').length > 0) {
|
||
options_framework_tabs()
|
||
}
|
||
|
||
function options_framework_tabs () {
|
||
|
||
var $group = $('.group'),
|
||
$navtabs = $('.nav-tab-wrapper a'),
|
||
active_tab = ''
|
||
|
||
// Hides all the .group sections to start
|
||
$group.hide()
|
||
|
||
// Find if a selected tab is saved in localStorage
|
||
if (typeof (localStorage) != 'undefined') {
|
||
active_tab = localStorage.getItem('active_tab')
|
||
}
|
||
|
||
// If active tab is saved and exists, load it's .group
|
||
if (active_tab != '' && $(active_tab).length) {
|
||
$(active_tab).fadeIn()
|
||
$(active_tab + '-tab').addClass('nav-tab-active')
|
||
} else {
|
||
$('.group:first').fadeIn()
|
||
$('.nav-tab-wrapper a:first').addClass('nav-tab-active')
|
||
}
|
||
|
||
// Bind tabs clicks
|
||
$navtabs.click(function (e) {
|
||
|
||
e.preventDefault()
|
||
|
||
// Remove active class from all tabs
|
||
$navtabs.removeClass('nav-tab-active')
|
||
|
||
$(this).addClass('nav-tab-active').blur()
|
||
|
||
if (typeof (localStorage) != 'undefined') {
|
||
localStorage.setItem('active_tab', $(this).attr('href'))
|
||
}
|
||
|
||
var selected = $(this).attr('href')
|
||
|
||
$group.hide()
|
||
$(selected).fadeIn()
|
||
|
||
})
|
||
}
|
||
|
||
}) |