代码高亮整套自研:10 浅 + 10 深配色、语言标签、评论区贴代码 (#11)
不再依赖第三方高亮插件,全部由主题自己实现。
主题配色(20 套)
- 浅色 10:github / vs / atom-one-light / idea / xcode /
stackoverflow-light / intellij-light / googlecode / magula / color-brewer
- 深色 10:github-dark / atom-one-dark / monokai / github-dark-dimmed /
a11y-dark / stackoverflow-dark / night-owl / srcery /
panda-syntax-dark / tomorrow-night-blue
- 深色卡片带 data-kind="dark",顶栏跟着换深色底
- 只加载当前选中的一套(20 套全加载会让后出现的规则压前面的,
切换配色容易"看起来没变")
代码语言标签(用户要求:防止复制错语言)
- 作者标了 language-xxx → 实线边框、正体
- 自动识别猜出来的 → 虚线边框、斜体
- 认不出来 → 显示 "—",仍保持虚线斜体(不冒充作者标注)
行号
- 默认关;行号槽在 <pre> 外面,复制天然不带行号
- 改掉原主题的 word-break:break-all(任意字符断行,代码像乱码)
- 归零 hljs 主题自带的 pre code.hljs{padding:1em},
否则代码被压低 1em、与行号槽错位 15.5px
复制
- 默认开;安全上下文判断 + execCommand 退路(HTTP 下 clipboard 是 undefined)
- 取 textContent 而非 innerHTML
- 默认 opacity:0、hover 显示、触屏覆盖回 1
(旧写法只在 @media(hover:none) 里写 1,等于空操作)
自动识别
- 做成选项,默认开
- 只对没标语言的块生效;站内实测长块准确率 22%,不能覆盖作者标注
评论区贴代码
- 评论工具条加"代码"按钮 + 29 种语言面板
- kses 放行 pre 并给 pre/code 放开 class
(WP 默认 pre 整条剥掉、code 属性全清,此前根本贴不了代码)
- 放行 pre 之后 wpautop/wptexturize/make_clickable 自动让路,
代码里的连续空行、引号、URL 都不会被改写
- comments.js 用占位符保护代码块,避免 walk() 的 /\n{3,}/ 压缩
其它
- 资源版本号改为读主题头,不再写死(写死会导致以后升级后
浏览器一直用旧缓存,改了 CSS/JS 完全不生效)
- 删除 footer.php 里旧的 Prism 兜底(两套高亮器同页会互相打架)
验收
- 用线上 284 个真实代码块跑产品代码:284/284 全部变卡片,内容丢字 0、双转义 0
- 47 个 text 标记全部映射到 plaintext(hljs 无 text 语言,不映射会静默不上色)
- 80 个裸 <pre>(无 code 子元素)全部覆盖
- 核心纯函数 65/65;核心反向 10/10 变红
- 浏览器端到端 32/32;行号几何逐行对齐偏差 0.00px,几何反向 4/4 变红
- 端到端反向 7/7 变红;语言标签反向 2/2 变红
- 全量 PHP 语法 95 文件 0 错误
发布:走 theme-update 通道(zip + readme + update.json),
未覆盖服务器主题目录;线上主题目录仍是 3.2.41,待后台升级。
This commit is contained in:
+115
-2
@@ -196,7 +196,7 @@
|
||||
* 这样「选中一段字 → 点 B」的选区在 click 到来时还在。
|
||||
* ⚠️ 面板内部的元素(色板、表情链接)也要挡:3.2.25 本地探针实测,
|
||||
* 点色板时焦点被抢走,foreColor 落在塌陷光标上,颜色功能整个失效。 */
|
||||
$$('.comment-tool-btn, .comment-color-swatch, .comment-color-panel, .comment-smile-panel, .comment-smile-panel a', form)
|
||||
$$('.comment-tool-btn, .comment-color-swatch, .comment-color-panel, .comment-smile-panel, .comment-smile-panel a, .comment-code-panel, .comment-code-lang', form)
|
||||
.forEach(function (b) {
|
||||
b.addEventListener('mousedown', function (ev) { ev.preventDefault() })
|
||||
})
|
||||
@@ -284,6 +284,76 @@
|
||||
})
|
||||
}
|
||||
|
||||
/* ── 3.2.42 代码块面板 ──────────────────────────────────
|
||||
* 与颜色/表情面板同款开合逻辑(点按钮开合、点外面关掉)。
|
||||
* 选定语言后会在光标处插入:
|
||||
* <pre><code class="language-xxx">这里换成你的代码</code></pre>
|
||||
* 为什么先插占位文字而不是空块:空的 <pre> 在浏览器里高度接近 0,
|
||||
* 光标定位困难、看起来像"没插进去",访客会反复点。
|
||||
*
|
||||
* 🔴 为什么语言必须让访客自己选:
|
||||
* 自动识别在站内实测准确率只有 22%(18 个长块对 4 个),
|
||||
* 让它替访客决定语言等于制造错误标签。
|
||||
* 🔴 插入后必须把光标留在代码块里,否则访客紧接着打字会打到块外面。
|
||||
*/
|
||||
var codeBtn = $('#yang-code-btn')
|
||||
var codePanel = $('#yang-code-panel')
|
||||
if (codeBtn && codePanel) {
|
||||
codeBtn.addEventListener('click', function (ev) {
|
||||
ev.stopPropagation()
|
||||
var open = !codePanel.hidden
|
||||
codePanel.hidden = open
|
||||
codeBtn.setAttribute('aria-expanded', open ? 'false' : 'true')
|
||||
})
|
||||
document.addEventListener('click', function (ev) {
|
||||
if (codePanel.hidden) return
|
||||
if (codePanel.contains(ev.target) || codeBtn.contains(ev.target)) return
|
||||
codePanel.hidden = true
|
||||
codeBtn.setAttribute('aria-expanded', 'false')
|
||||
})
|
||||
codePanel.addEventListener('click', function (ev) {
|
||||
var b = ev.target.closest ? ev.target.closest('.comment-code-lang') : null
|
||||
if (!b) return
|
||||
ev.preventDefault()
|
||||
var lang = b.getAttribute('data-lang') || 'text'
|
||||
insertCodeBlock(lang)
|
||||
codePanel.hidden = true
|
||||
codeBtn.setAttribute('aria-expanded', 'false')
|
||||
})
|
||||
}
|
||||
|
||||
/* 在光标处插入一个代码块,并把占位文字选中,方便直接覆盖 */
|
||||
function insertCodeBlock (lang) {
|
||||
if (!editor) return
|
||||
editor.focus()
|
||||
var cls = lang && lang !== 'text' ? ' class="language-' + lang + '"' : ''
|
||||
var html = '<pre><code' + cls + '></code></pre>'
|
||||
// 先插空块,再把占位文字塞进 code 里并选中
|
||||
try {
|
||||
document.execCommand('insertHTML', false, html)
|
||||
} catch (ex) {
|
||||
// 老浏览器兜底:直接插纯文本标记
|
||||
document.execCommand('insertText', false,
|
||||
'\n<pre><code' + cls + '>\n\n</code></pre>\n')
|
||||
return
|
||||
}
|
||||
// 找到刚插入的最后那个 code,放占位文字并全选
|
||||
var codes = $$('pre:last-of-type code', editor)
|
||||
var target = codes.length ? codes[codes.length - 1] : null
|
||||
if (!target) {
|
||||
var all = $$('pre code', editor)
|
||||
target = all.length ? all[all.length - 1] : null
|
||||
}
|
||||
if (target) {
|
||||
target.textContent = '在这里粘贴或输入代码'
|
||||
var range = document.createRange()
|
||||
range.selectNodeContents(target)
|
||||
var sel = window.getSelection()
|
||||
sel.removeAllRanges()
|
||||
sel.addRange(range)
|
||||
}
|
||||
}
|
||||
|
||||
/* 编辑区被清空后常残留 <br>/<div><br></div>,压成真空,
|
||||
* 否则 :empty 占位符不显示。 */
|
||||
editor.addEventListener('input', function () {
|
||||
@@ -316,6 +386,40 @@
|
||||
document.createTextNode(' ' + (im.getAttribute('data-code') || '') + ' '),
|
||||
im)
|
||||
})
|
||||
|
||||
/* ── 3.2.42 代码块:必须在 walk() 之前单独序列化 ──────────
|
||||
* 🔴 不能交给 walk() 递归处理,原因是 walk 会给所有文本做
|
||||
* escHtml()(把 & 变 &、< 变 <)。代码块里的内容
|
||||
* 出发时是**已转义过的显示文本**(浏览器 textContent 已把
|
||||
* < 还原成 <),需要重新转义一次才对;但 walk 还可能
|
||||
* 把代码里的连续空行压掉(收尾的 /\n{3,}/ 替换),
|
||||
* 而代码里连续空行是完全正常的。
|
||||
* ⇒ 做法:先把每个 <pre> 换成唯一占位标记,
|
||||
* walk 结束后再把标记替换回序列化好的代码块。
|
||||
* 占位符用 \u0000 包裹,escHtml 不会改动它。
|
||||
*/
|
||||
var codeBlocks = []
|
||||
var pres = $$('pre', clone)
|
||||
for (var pi = 0; pi < pres.length; pi++) {
|
||||
var pre = pres[pi]
|
||||
var codeEl = pre.querySelector('code')
|
||||
var srcEl = codeEl || pre
|
||||
var cls = (codeEl ? codeEl.className : '') + ' ' + (pre.className || '')
|
||||
var mm = /(?:language|lang)-([a-z0-9+#.-]+)/i.exec(cls)
|
||||
var lang = mm ? mm[1].toLowerCase() : ''
|
||||
// textContent 是浏览器还原后的纯文本,重新转义成安全形态再提交
|
||||
var raw = srcEl.textContent || ''
|
||||
var safe = raw.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
var block = '<pre><code'
|
||||
+ (lang ? ' class="language-' + lang + '"' : '')
|
||||
+ '>' + safe + '</code></pre>'
|
||||
var ph = '\u0000CB' + codeBlocks.length + '\u0000'
|
||||
codeBlocks.push(block)
|
||||
pre.parentNode.replaceChild(document.createTextNode(ph), pre)
|
||||
}
|
||||
|
||||
function walk (n) {
|
||||
var out = ''
|
||||
for (var i = 0; i < n.childNodes.length; i++) {
|
||||
@@ -341,7 +445,16 @@
|
||||
}
|
||||
return out
|
||||
}
|
||||
return walk(clone).replace(/\n{3,}/g, '\n\n').trim()
|
||||
|
||||
var body = walk(clone)
|
||||
// 空行压缩只作用于代码块之外 —— 先把已存的块换回来,避免误伤
|
||||
body = body.replace(/\n{3,}/g, '\n\n')
|
||||
// 🔴 占位符还原:必须在压缩空行**之后**做。
|
||||
// 如果先还原,代码里的连续空行会被上面的 /\n{3,}/ 一起压掉。
|
||||
for (var ci = 0; ci < codeBlocks.length; ci++) {
|
||||
body = body.split('\u0000CB' + ci + '\u0000').join(codeBlocks[ci])
|
||||
}
|
||||
return body.trim()
|
||||
}
|
||||
|
||||
/* ── 提交 ──────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user