原因
因为自己普通话不标准,希望可以制作适合自己的带拼音的文档,可以把平常看到的内容、说过的话作为练习普通话的材料。
在市面上,带拼音的材料、书籍并不多,而且有可能是一些比较生僻的内容。所以希望可以自己制作这样的材料。之前也尝试过:【word + word百宝箱插件 】的方式来做材料。但个人原因,不是很喜欢用 word 文档的方式,更喜欢 Markdown 的方式。
平常自己用的最多的就是 Typora,但没有找到现成的处理工具,所以,就自己开发了一个。
这个工具就是一个 html 文件,所以,复制我提供的完整代码,保存到 html 文件中,双击使用浏览器打开即可使用全部功能。
效果图
处理前的文本排版样式:
处理后的样式:
可以选中后批量突出显示:
提供现成的 CSS 样式,且可以一键复制:
提供自动渲染脚本:
提供导出 PDF 时需要的 CSS 样式代码,一键复制过去即可使用,还提供了两个在线转换的工具,不需要记忆网址,直接点击即可在新的标签页中打开:
堆叠式通知:
在 Typora 中渲染出来的效果【效果与主题字体有关】:
在 PDF 中的效果:
这就是测试过程中用的 Typora 的默认 github 主题的拷贝,所以,字体比较丑,重要的是,可以突出显示拼音和汉字。
原理
除了 html 外,我就只知道 word 可以做到 汉字顶部带拼音的效果。而 word 可以做到,也是因为它是 xml 结构的。
Tip:word 文档的后缀 .docx ,其中 doc 就是 document 的意思,而其中的字母 x ,其实就是 xml 的意思。你可以随便创建一个后缀为 .docx 的 word 文档,然后修改后缀为 zip ,然后用解压软件解压,就可以看到许多 xml 文件。如果这里面有带拼音的内容,就可以看出,它的结构与html中的ruby标签类似。
而在 Typora 中查看 Markdown 文件,它就是被渲染成 html 的,所以,可以将汉字与拼音用 ruby 标签来包裹,达到我们想要的上下结构的显示效果。
如果人为手动的修改,那工作量会大得吓人。所以才写了这个工具来批量将普通的文本转为用ruby标签包裹的文本。
使用流程
先复制我提供的代码到 html 文件中,然后用浏览器打开。你就可以再网页的底部找到两个链接,鼠标左键单击就可以在新的标签页中打开。
然后,复制你需要添加拼音的文本到这两个网站中的其中一个即可,根据网页上的按钮和提示,自己操作。
注意:需要自己检查多音字,两个网站都可以切换多音字的读音。先把拼音修改正确后,再复制到这个小工具中,进行转换。
当把转换后的内容复制到 Typora 中后,它是不会像这个工具的 【Ruby标签结果】窗口那样,渲染出你突出显示的内容的。也就是不会显示为红色加粗的样式。
所以,接下来就是复制这个工具中提供的 CSS 样式,到你现在 Typora 正在使用的主题中。保存重启 Typora ,查看之前粘贴的 带拼音的内容。
这个时候,你会发现,依然没有效果。。。。
其实这是 Typora 这个软件本身的问题,它渲染后的 html 结构太乱了,你自己在开发者工具中查看就知道我为什么说它乱了。。。
正由于它的结构乱,所以,CSS选择器无法选择想要修改的内容,这个时候就需要 JS 代码来操作。
此时,就需要复制这个网页工具上的 :渲染脚本,到 window.html 文件中。
修改 window.html 的方法,我之前的一篇文章中,有写到过:【非主题的方式】Typora 标题自动编号功能的实现——全网首发!-CSDN博客
操作完后,重启 Typora ,就可以看到那些你突出显示的字和拼音,被用红色加粗的样式 凸显出来了。样式,可以自己在代码中修改。
这段脚本是自动渲染的,也提供了一个快捷键:F7 来一键处理并渲染样式。
顺便提一下,如果你不喜欢这种自动渲染的方式,也可以复制这段代码,让AI来修改,让它:去掉其中的观察代码,改为使用快捷键调用的方式,并让它保留通知的效果,即可。
其实,如果只是做成,汉字黑色,拼音统一红色的上下结构。一点都不难。
但要突出那些需要重点记忆的字或者词,真的麻烦。可以说,这个工具 接近 1000 行代码中,有一半以上的代码都是在做这个功能。
如果你还想将这个文档导出为 PDF 格式的话,那么就还需要接着做。
复制这个网页工具中,关于 PDF 的那部分代码到 你 Typora 正在使用的主题中。重启后,再导出,才可以让 需要突出显示的 字 或 词的样式被保留在 PDF 文件中。
王婆卖瓜自卖自夸
这个网页工具主要特点如下:
- 支持两种输入格式:支持 【“中zhōng”】 和 【“中(zhōng)”】 两种常见的拼音标注格式
- 即时转换:一键转换,结果立刻可见
- 本地使用:数据安全,不需要网络,无需等待
- 高亮标记重点词:可以通过右键点击让特定汉字及其拼音以红色显示,方便标记重点词汇
- 批量高亮:可以选中多个汉字,右键点击一次性批量高亮
- 一键复制:复制结果后可直接粘贴到支持HTML的编辑器中使用
- 适配Typora:提供了专用CSS样式、自动渲染脚本,完美适配Typora编辑器,及其导出时需要的 PDF 的样式
- 友好的提示系统:带进度条的堆叠式通知,操作反馈清晰明了
- 美观的界面:简洁大方的设计,操作体验一流
。。。。
扩展:增加一键给标题编号的功能
我平常用 Typora 写 Markdown 的时候,会需要它自动排序的功能。
很明显这个功能,Typora 也没有。所以,也就自己写了一个。
方法参考之前提到的那篇文章:【非主题的方式】Typora 标题自动编号功能的实现——全网首发!-CSDN博客
如果你喜欢用命令行工具来排序 Markdown 的标题,那么可以试试这个工具,只有 17KB 的大小:Markdown标题序号处理工具——用 C 语言实现-CSDN博客
如果你喜欢本地网页工具的方式来处理标题编号的问题,那么可以看看:用 HTML 网页来管理 Markdown 标题序号-CSDN博客
完整源代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>拼音排版转Ruby标签工具</title>
<style>
:root {
--primary-color: #3498db;
--primary-hover: #2980b9;
--success-color: #2ecc71;
--success-hover: #27ae60;
--danger-color: #e74c3c;
--danger-hover: #c0392b;
--text-color: #333;
--light-gray: #f5f5f5;
--border-color: #ddd;
--shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
body {
font-family: "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: var(--light-gray);
color: var(--text-color);
line-height: 1.6;
position: relative;
min-height: 100vh;
padding-bottom: 120px;
/* 为底部链接预留更多空间 */
}
h1,
h2,
h3 {
color: #2c3e50;
margin-top: 0;
}
h1 {
text-align: center;
margin-bottom: 30px;
font-size: 2.2em;
font-weight: 600;
background: linear-gradient(135deg, #3498db, #2c3e50);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
}
.container {
display: flex;
flex-direction: column;
gap: 25px;
}
.card {
background-color: white;
border-radius: 8px;
padding: 20px;
box-shadow: var(--shadow);
}
textarea {
width: 96%;
height: 180px;
padding: 15px;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 16px;
line-height: 1.6;
resize: none;
transition: border-color 0.3s, box-shadow 0.3s;
}
textarea:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}
.btns {
display: flex;
justify-content: space-between;
gap: 10px;
margin-top: 15px;
}
button {
flex: 1;
background-color: var(--primary-color);
color: white;
border: none;
padding: 12px 20px;
font-size: 16px;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
font-weight: 500;
}
button:hover {
background-color: var(--primary-hover);
transform: translateY(-1px);
}
button:active {
transform: translateY(0);
}
.btn-clear {
background-color: #95a5a6;
}
.btn-clear:hover {
background-color: #7f8c8d;
}
.output-container {
position: relative;
}
.output {
width: 94%;
min-height: 180px;
padding: 20px;
background-color: white;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 18px;
line-height: 2.2;
white-space: pre-wrap;
word-wrap: break-word;
overflow-y: auto;
max-height: 300px;
}
.copy-btn {
position: absolute;
top: 10px;
right: 10px;
background-color: var(--success-color);
padding: 8px 15px;
font-size: 14px;
border-radius: 4px;
}
.copy-btn:hover {
background-color: var(--success-hover);
}
/* 提取公共样式,创建一个新的类 */
.typora-section {
margin-top: 30px;
margin-bottom: 30px;
}
.typora-section h3 {
margin-bottom: 15px;
}
.typora-section p {
margin-bottom: 15px;
color: #555;
}
/* 将特定的差异样式单独应用 */
.typora-pdf-code {
margin-bottom: 60px;
}
pre {
background-color: #f8f9fa;
padding: 15px;
border-radius: 6px;
overflow: auto;
position: relative;
line-height: 1.5;
max-height: 200px;
border: 1px solid var(--border-color);
}
#copyThemeBtn,
#copyPDFBtn,
#copyRenderScript {
margin-bottom: 15px;
background-color: #9b59b6;
}
#copyThemeBtn:hover,
#copyPDFBtn:hover,
#copyRenderScript:hover {
background-color: #8e44ad;
}
/* Ruby标签样式 */
ruby {
ruby-position: over;
margin-right: 0.5em;
}
rt {
font-size: 0.65em;
color: var(--text-color);
text-align: center;
padding-bottom: 3px;
letter-spacing: normal;
font-weight: 500;
}
/* 高亮样式 */
ruby.highlighted rb,
ruby.highlighted rt,
.highlighted rb,
.highlighted rt,
.highlighted {
color: var(--danger-color) !important;
font-weight: bold !important;
}
rb {
letter-spacing: 0.5em;
position: relative;
left: 0.25em;
}
/* 提示信息样式 */
.toast-container {
position: fixed;
top: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 10px;
z-index: 1000;
max-height: 90vh;
overflow-y: auto;
padding-right: 5px;
/* 避免滚动条贴边 */
}
.toast {
background-color: #2ecc71;
color: white;
padding: 12px 20px 20px;
/* 增加底部padding为进度条留出空间 */
border-radius: 4px;
box-shadow: var(--shadow);
opacity: 0;
transition: opacity 0.3s, transform 0.3s;
transform: translateY(-10px);
position: relative;
min-width: 200px;
}
.toast.show {
opacity: 1;
transform: translateY(0);
}
.toast.error {
background-color: var(--danger-color);
}
.toast.info {
background-color: var(--primary-color);
}
/* 进度条样式 */
.toast-progress {
position: absolute;
bottom: 0;
left: 0;
height: 4px;
background-color: rgba(255, 255, 255, 0.7);
width: 100%;
border-radius: 0 0 4px 4px;
transform-origin: left;
will-change: transform;
backface-visibility: hidden;
}
/* 右键菜单 */
.context-menu {
display: none;
position: fixed;
background-color: rgb(67, 236, 138);
border-radius: 4px;
box-shadow: var(--shadow);
z-index: 100;
min-width: 150px;
}
.context-menu-item {
padding: 10px 15px;
cursor: pointer;
transition: background-color 0.2s;
}
/* .context-menu-item:hover {
background-color: var(--light-gray);
} */
/* 返回顶部按钮 */
.back-to-top {
position: fixed;
bottom: 30px;
right: 30px;
width: 50px;
height: 50px;
background-color: var(--primary-color);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
box-shadow: var(--shadow);
opacity: 0;
visibility: hidden;
z-index: 999;
}
.back-to-top.show {
opacity: 1;
visibility: visible;
}
.back-to-top:hover {
background-color: var(--primary-hover);
transform: translateY(-3px);
}
/* 底部链接区域 */
.footer-links {
margin-top: 40px;
padding: 20px;
background-color: white;
border-radius: 8px;
box-shadow: var(--shadow);
text-align: center;
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
}
.footer-links h3 {
margin-top: 0;
margin-bottom: 15px;
color: #2c3e50;
}
.link-container {
display: flex;
justify-content: center;
gap: 20px;
flex-wrap: wrap;
}
.external-link {
display: inline-flex;
align-items: center;
padding: 10px 15px;
background-color: #f8f9fa;
border-radius: 8px;
color: var(--primary-color);
text-decoration: none;
transition: all 0.3s;
font-weight: 500;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.external-link:hover {
background-color: var(--primary-color);
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.external-link svg {
margin-right: 8px;
}
</style>
</head>
<body>
<h1>拼音排版转Ruby标签工具</h1>
<div class="container">
<div class="card">
<h3>输入文本:</h3>
<textarea id="inputText" placeholder="请粘贴拼音标注文本,例如:中zhōng 文wén 汉hàn 字zì 或 中(zhōng) 文(wén) 汉(hàn) 字(zì)">中zhōng 文wén 汉hàn 字zì 转zhuǎn 拼pīn 音yīn 工gōng 具jù
将jiāng 汉hàn 字zì 转zhuǎn 换huàn 为wèi 中zhōng 文wén 拼pīn 音yīn ,支zhī 持chí 显xiǎn 示shì 声shēng 调diào ,常cháng 见jiàn 多duō 音yīn 字zì 智zhì 能néng 转zhuǎn 换huàn
多duō 音yīn 字zì 示shì 例lì :
三sān 百bǎi 六liù 十shí 行háng ,我wǒ 最zuì 行xíng</textarea>
<div class="btns">
<button id="clearBtn" class="btn-clear">清空</button>
<button id="convertBtn">转换</button>
</div>
</div>
<div class="card output-container">
<h3>Ruby标签结果:</h3>
<div id="output" class="output"></div>
<button id="copyBtn" class="copy-btn">复制结果</button>
</div>
</div>
<div class="card typora-section typora-theme-code">
<h3>Typora 主题设置</h3>
<p>想要在 Typora 中得到与预览窗口中一样的效果,请将以下 CSS 代码添加到你的 Typora 主题文件中:</p>
<button id="copyThemeBtn">复制主题代码</button>
<pre id="preThemeCode"><code>/* 添加到Typora的theme.css文件 */
ruby {
ruby-position: over;
margin-right: 0.5em;
line-height: 2.2; /* 增加行高 */
}
rt {
font-size: 0.65em;
color: #333; /* 默认与文本颜色相同 */
padding-bottom: 3px;
font-weight: 500;
}
rb {
letter-spacing: 0.5em;
position: relative;
left: 0.25em;
}
/* Typora中高亮样式 - 通过JS注入实现 */
.typora-highlighted ruby,
.typora-highlighted rt,
.typora-highlighted rb {
color: #e74c3c !important;
font-weight: bold !important;
}
/* 调整段落间距 */
p {
line-height: 1.8;
margin-bottom: 1em;
}</code></pre>
</div>
<div class="card typora-section typora-render-code">
<h3>Typora 自动渲染脚本</h3>
<p>想要在 Typora 中得到与预览窗口中一样的效果【个别字突出显示】,请将以下代码添加到 Typora 的 window.html 文件中:</p>
<button id="copyRenderScript">复制渲染脚本</button>
<!-- 想要让 Script 标签中的代码不被执行,那么就需要将 Script 标签左右两边的大于小于用特殊字符代替。 -->
<pre id="preRenderCode"><code><script>
// 全局高亮计数器
let totalHighlightCount = 0;
const showNotification = (message, isSuccess = true) => {
let toastContainer = document.getElementById('custom-toast-container');
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.id = 'custom-toast-container';
toastContainer.style = `
position: fixed;
top: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 10px;
z-index: 9999;
max-height: 90vh;
overflow-y: auto;
padding-right: 5px;
`;
document.body.appendChild(toastContainer);
}
const toast = document.createElement('div');
toast.style = `
background: ${isSuccess ? '#4CAF50' : '#F44336'};
color: white;
padding: 12px 20px 20px;
border-radius: 4px;
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
position: relative;
min-width: 200px;
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.3s, transform 0.3s;
`;
toast.textContent = message;
const progressBar = document.createElement('div');
progressBar.style = `
position: absolute;
bottom: 0;
left: 0;
height: 4px;
background: rgba(255,255,255,0.7);
width: 100%;
border-radius: 0 0 4px 4px;
transform-origin: left;
`;
toast.appendChild(progressBar);
toastContainer.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '1';
toast.style.transform = 'translateY(0)';
progressBar.style.transition = 'transform 3s linear';
progressBar.style.transform = 'scaleX(0)';
}, 10);
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => {
if (toast.parentNode === toastContainer) {
toastContainer.removeChild(toast);
}
}, 300);
}, 3000);
};
let observer;
function initObserver() {
const config = {
childList: true,
subtree: true,
characterData: true,
attributes: true,
attributeFilter: ['class']
};
observer = new MutationObserver(function(mutations) {
const needsUpdate = mutations.some(mutation => {
return mutation.type === 'characterData' ||
(mutation.type === 'childList' && mutation.addedNodes.length > 0) ||
(mutation.type === 'attributes' && mutation.attributeName === 'class');
});
if (needsUpdate) {
debouncedProcessRubyHighlights();
}
});
observer.observe(document.body, config);
showNotification('拼音高亮观察器已启动', true);
}
let debounceTimer;
function debouncedProcessRubyHighlights() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
processRubyHighlights(false); // 自动触发时不显示通知
}, 300);
}
function processRubyHighlights(showNotice = true) {
const rawInlines = document.querySelectorAll('.md-raw-inline');
let currentHighlightCount = 0;
rawInlines.forEach(rawInline => {
const content = rawInline.textContent || '';
const rubyContainer = rawInline.previousElementSibling;
if (!rubyContainer || !rubyContainer.classList.contains('md-ruby-container')) {
return;
}
if (content.includes('class="highlighted"')) {
rubyContainer.classList.add('typora-highlighted');
currentHighlightCount++;
} else {
rubyContainer.classList.remove('typora-highlighted');
}
});
// 更新全局高亮计数器
totalHighlightCount = currentHighlightCount;
// 只有明确要求显示通知时才显示
if (showNotice) {
showNotification(`已处理拼音高亮\n当前高亮: ${totalHighlightCount}处`, true);
}
}
document.addEventListener('DOMContentLoaded', function() {
initObserver();
processRubyHighlights(false); // 初始化时不显示通知
document.addEventListener('keydown', function(e) {
if (e.key === 'F7' || e.keyCode === 118) {
processRubyHighlights(true); // 手动刷新时显示通知
e.preventDefault();
}
});
});
window.addEventListener('beforeunload', function() {
if (observer) {
observer.disconnect();
}
});
document.addEventListener('paste', function() {
setTimeout(() => {
debouncedProcessRubyHighlights();
showNotification('检测到粘贴操作,正在处理拼音高亮...', true);
}, 100);
});
</script></code></pre>
</div>
<div class="card typora-section typora-pdf-code">
<h3>Typora 导出 PDF 样式</h3>
<p>想要在导出的 PDF 文件中得到与预览窗口中一样的效果,请将以下 CSS 代码添加到你的 Typora 主题文件中:</p>
<button id="copyPDFBtn">复制PDF所需样式</button>
<pre id="copyPDFCode"><code>/* 添加到你的主题 CSS 文件末尾 */
@media print, (prefers-color-scheme: print) {
ruby.highlighted,
ruby.highlighted rt {
color: #ff0000 !important;
font-weight: bold !important;
}
}</code></pre>
</div>
<!-- 底部链接 -->
<div class="footer-links">
<h3>相关工具网站</h3>
<div class="link-container">
<a href="https://www.lddgo.net/convert/pinyin" class="external-link" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
汉字转拼音 - LDDGO
</a>
<a href="https://www.qqxiuzi.cn/zh/pinyin/" class="external-link" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
汉字拼音转换 - 千千秀字
</a>
</div>
</div>
<!-- 提示信息容器 -->
<div class="toast-container" id="toastContainer"></div>
<!-- 右键菜单 -->
<div class="context-menu" id="contextMenu">
<div class="context-menu-item" id="highlightMenuItem">高亮/取消高亮</div>
</div>
<!-- 返回顶部按钮 -->
<div class="back-to-top" id="backToTop">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</div>
<script>
// DOM元素
const convertBtn = document.getElementById('convertBtn');
const inputText = document.getElementById('inputText');
const output = document.getElementById('output');
const clearBtn = document.getElementById('clearBtn');
const copyBtn = document.getElementById('copyBtn');
const copyThemeBtn = document.getElementById('copyThemeBtn');
const preThemeCode = document.getElementById('preThemeCode');
const toastContainer = document.getElementById('toastContainer');
const contextMenu = document.getElementById('contextMenu');
const highlightMenuItem = document.getElementById('highlightMenuItem');
const backToTop = document.getElementById('backToTop');
const copyRenderScript = document.getElementById('copyRenderScript');
const preRenderCode = document.getElementById('preRenderCode');
const copyPDFBtn = document.getElementById('copyPDFBtn');
const copyPDFCode = document.getElementById('copyPDFCode');
// 转换按钮点击事件
convertBtn.addEventListener('click', function () {
if (!inputText.value.trim()) {
showToast('内容为空,请先输入文本!', 'error');
return;
}
const rubyText = convertToRuby(inputText.value);
output.innerHTML = rubyText;
showToast('转换完成!', 'success');
// 添加右键菜单事件
setupRubyHighlighting();
});
// 清空按钮点击事件
clearBtn.addEventListener('click', function () {
if (!inputText.value.trim()) {
showToast('内容已为空!', 'info');
return;
}
inputText.value = '';
output.innerHTML = '';
showToast('已清空!', 'info');
});
// 复制结果按钮点击事件
copyBtn.addEventListener('click', async function () {
const content = output.innerHTML.trim();
if (!content) {
showToast('内容为空,无法复制!', 'error');
return;
}
try {
await navigator.clipboard.writeText(content);
showToast('结果已复制!', 'success');
} catch (err) {
showToast('复制失败!', 'error');
}
});
// 复制主题代码按钮点击事件
copyThemeBtn.addEventListener('click', async function () {
try {
await copyToClipboardWithDelay(preThemeCode.textContent);
showToast('主题代码已复制!', 'success');
} catch (err) {
showToast('复制失败!', 'error');
}
});
// 复制渲染脚本按钮点击事件
copyRenderScript.addEventListener('click', async function() {
try {
await copyToClipboardWithDelay(preRenderCode.textContent);
showToast('渲染脚本代码已复制!', 'success');
} catch (err) {
showToast('复制失败!', 'error');
}
});
// 复制 PDF 样式按钮点击事件
copyPDFBtn.addEventListener('click', async function() {
try {
await copyToClipboardWithDelay(copyPDFCode.textContent);
showToast('PDF 样式代码已复制!', 'success');
} catch (err) {
showToast('复制失败!', 'error');
}
});
// 添加延迟的复制函数,确保进度条有足够的时间显示
async function copyToClipboardWithDelay(text) {
return new Promise((resolve, reject) => {
setTimeout(async () => {
try {
await navigator.clipboard.writeText(text);
resolve();
} catch (err) {
reject(err);
}
}, 50); // 添加小延迟,确保进度条动画可见
});
}
// 返回顶部按钮
window.addEventListener('scroll', function () {
if (window.pageYOffset > 300) {
backToTop.classList.add('show');
} else {
backToTop.classList.remove('show');
}
});
backToTop.addEventListener('click', function () {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
// 显示带进度条的堆叠式提示信息
function showToast(message, type = 'success') {
// 创建新的提示元素
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
// 创建进度条
const progressBar = document.createElement('div');
progressBar.className = 'toast-progress';
toast.appendChild(progressBar);
// 将提示元素添加到容器
toastContainer.appendChild(toast);
// 使用setTimeout以确保DOM更新并应用过渡效果
setTimeout(() => {
toast.classList.add('show');
// 强制重排以确保动画正确触发
void toast.offsetWidth;
// 设置进度条动画
progressBar.style.transition = 'transform 3s linear';
progressBar.style.transform = 'scaleX(0)';
}, 10);
// 设置自动消失
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => {
if (toast.parentNode === toastContainer) {
toastContainer.removeChild(toast);
}
}, 300);
}, 3000);
}
// 转换函数:将拼音标注文本转换为Ruby标签格式
function convertToRuby(text) {
// 处理汉字和拼音的组合(中zhōng 文wén)
let rubyText = text.replace(
/([\u4e00-\u9fa5])([a-zA-Zāáǎàēéěèīíǐìōóǒòūúǔùǖǘǚǜü]+)/g,
'<ruby><rb>$1</rb><rt>$2</rt></ruby>'
);
// 处理带括号的拼音格式(中(zhōng) 文(wén))
rubyText = rubyText.replace(
/([\u4e00-\u9fa5])\(([a-zA-Zāáǎàēéěèīíǐìōóǒòūúǔùǖǘǚǜü]+)\)/g,
'<ruby><rb>$1</rb><rt>$2</rt></ruby>'
);
// 处理标点符号后的空格
rubyText = rubyText.replace(/([,。、;:?!])\s+/g, '$1');
// 保留换行符
rubyText = rubyText.replace(/\n/g, '<br>');
return rubyText;
}
// 设置Ruby标签的高亮功能
function setupRubyHighlighting() {
// 获取所有ruby元素
const rubyElements = output.querySelectorAll('ruby');
// 添加右键事件监听
rubyElements.forEach(ruby => {
ruby.addEventListener('contextmenu', handleRubyContextMenu);
});
// 禁用默认右键菜单
output.addEventListener('contextmenu', function (e) {
e.preventDefault();
});
}
// 处理Ruby标签的右键菜单
function handleRubyContextMenu(e) {
e.preventDefault();
e.stopPropagation();
// 根据选择的内容,处理单个或多个Ruby元素
let selectedRubyElements = [];
// 检查是否有选择的文本
const selection = window.getSelection();
if (selection.toString().trim() !== '') {
// 有选择的文本,处理选中范围内的所有Ruby元素
const range = selection.getRangeAt(0);
const container = range.commonAncestorContainer;
// 如果容器是output元素或其子元素
if (output.contains(container)) {
// 获取选中范围内的所有Ruby元素
const rubyElements = output.querySelectorAll('ruby');
rubyElements.forEach(ruby => {
if (range.intersectsNode(ruby)) {
selectedRubyElements.push(ruby);
}
});
}
} else {
// 没有选择文本,只处理当前右键点击的Ruby元素
let currentRuby = e.target;
// 确保获取到的是ruby元素
while (currentRuby && currentRuby.tagName !== 'RUBY') {
if (currentRuby === output) break;
currentRuby = currentRuby.parentElement;
}
if (currentRuby && currentRuby.tagName === 'RUBY') {
selectedRubyElements.push(currentRuby);
}
}
// 如果有选中的Ruby元素
if (selectedRubyElements.length > 0) {
// 显示右键菜单
contextMenu.style.display = 'block';
contextMenu.style.left = `${e.clientX}px`;
contextMenu.style.top = `${e.clientY}px`;
// 确保菜单不超出视口边界
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const menuRect = contextMenu.getBoundingClientRect();
if (e.clientX + menuRect.width > viewportWidth) {
contextMenu.style.left = `${viewportWidth - menuRect.width - 5}px`;
}
if (e.clientY + menuRect.height > viewportHeight) {
contextMenu.style.top = `${viewportHeight - menuRect.height - 5}px`;
}
// 设置高亮菜单项的点击事件
highlightMenuItem.onclick = function (e) {
e.stopPropagation();
// 切换所有选中Ruby元素的高亮状态
selectedRubyElements.forEach(ruby => {
ruby.classList.toggle('highlighted');
});
// 隐藏右键菜单
contextMenu.style.display = 'none';
};
}
}
// 点击页面其他区域时隐藏右键菜单
document.addEventListener('click', function () {
contextMenu.style.display = 'none';
});
// 窗口大小变化时隐藏右键菜单
window.addEventListener('resize', function () {
contextMenu.style.display = 'none';
});
// 全局拦截右键事件(确保菜单外的右键行为不受影响)
document.addEventListener('contextmenu', function (e) {
// 仅当点击的是右键菜单本身时阻止默认行为
if (e.target.closest('.context-menu')) {
e.preventDefault();
e.stopPropagation();
return false;
}
});
</script>
</body>
</html>