Open jinghu-moon opened 1 year ago
在网上找了找。东拼西凑勉强实现了这个功能。以下是我的代码。
var codeblocks = document.getElementsByClassName('md-fences')
for (var i = 0; i < codeblocks.length; i++) {
//显示 复制代码 按钮
currentCode = codeblocks[i]
currentCode.style = "position: relative;"
var copy = document.createElement('div')
copy.style = "position: absolute;right: 4px;\
top: 4px;background-color: white;padding: 2px 8px;\
margin: 8px;border-radius: 4px;cursor: pointer;\
z-index: 9999;\
box-shadow: 0 2px 4px rgba(0,0,0,0.05), 0 2px 4px rgba(0,0,0,0.05);"
copy.innerHTML = "复制"
currentCode.append(copy)
//让所有 "复制"按钮 全部隐藏
copy.style.visibility = "hidden"
console.log(copy)
}
for (var i = 0; i < codeblocks.length; i++) {
!function (i) {
//鼠标移到代码块,就显示按钮
codeblocks[i].onmouseover = function () {
codeblocks[i].childNodes[1].style.visibility = "visible"
}
//执行 复制代码 功能
function copyArticle(event) {
const range = document.createRange();
//范围是 code,不包括刚才创建的div
range.selectNode(codeblocks[i].childNodes[0]);
const selection = window.getSelection();
if (selection.rangeCount > 0) selection.removeAllRanges();
selection.addRange(range);
document.execCommand('copy');
codeblocks[i].childNodes[1].innerHTML = "复制成功"
setTimeout(function () {
codeblocks[i].childNodes[1].innerHTML = "复制"
}, 1000);
//清除选择区
if (selection.rangeCount > 0) selection.removeAllRanges(); 0
}
codeblocks[i].childNodes[1].addEventListener('click', copyArticle, false);
}(i);
!function (i) {
//鼠标从代码块移开 则不显示复制代码按钮
codeblocks[i].onmouseout = function () {
codeblocks[i].childNodes[1].style.visibility = "hidden"
}
}(i);
}
我按照你实现自定义鼠标样式的方式,引入了 js 文件。却没有用。必要在控制台执行一遍才行。老哥知道怎么解决吗?我才开始学习 js。
我不太会 JS。不过我在网络上找到一个创建复制按钮相关功能的文章。或许会对你目前的情况有一定的用处。
@jinghu-moon 请问这个怎么去除左侧行号部分?
@jinghu-moon 请问这个怎么去除左侧行号部分?
window.html
,
src="./appsrc/window/frame.js"
。src="./app/window/frame.js"
。appsrc
目录, Typora 为新版本,无则旧版本。<script src="./code-block-enhanced.js" defer="defer"></script>
,保存文件。code-block-enhanced.js
文件放入 /resources 文件夹。重启 Typora 即可。此时代码块具有折叠、显示编程语言、备注(代码块语言输入框中空格输入,例如 Java 这是一段测试代码
)、刷新、复制代码功能。
后续我也会在我的 jinghu-moon/typora-see-yue-theme: 主题中加入该功能。
code-block-enhanced.js
(() => {
// 配置项
const config = {
ENABLE: true, // 启用脚本,若为 false,以下配置全部失效
LOOP_DETECT_INTERVAL: 20, // 循环检测间隔,单位:毫秒
CLICK_CHECK_INTERVAL: 300, // 点击检测间隔,单位:毫秒
LANG_ENABLED: true, // 代码块显示编程语言功能开关
INTRO_ENABLED: true, // 代码块刷新功能开关
COPY_ENABLED: true, // 代码块复制功能开关
FOLD_ENABLED: true, // 代码块折叠功能开关
REFRESH_ENABLED: true, // 代码块刷新功能开关
MAX_LINE_NUM: 10, // 代码块最大行数,超过自动折叠
};
// 如果脚本未启用,直接返回
if (!config.ENABLE) return;
// 常用编程语言字母和对应的标准格式
const codeLangList = {
"ABAP": "ABAP",
"APL": "APL",
"ASCIIARMOR": "ASCII Armor",
"ASN1": "ASN.1",
"ASP": "ASP",
"ASSEMBLY": "Assembly",
"BASH": "Bash",
"BASIC": "BASIC",
"Batch": "Batch",
"C": "C",
"CSHARP": "C#",
"CPP": "C++",
"CASSANDRA": "Cassandra",
"CEYLON": "Ceylon",
"CLIKE": "C-like",
"CLOJURE": "Clojure",
"CMAKE": "CMake",
"CMD": "CMD",
"COBOL": "COBOL",
"COFFEESCRIPT": "CoffeeScript",
"COMMONLISP": "Common Lisp",
"CPP": "CPP",
"CQL": "CQL",
"CRYSTAL": "Crystal",
"CSHARP": "CSharp",
"CSS": "CSS",
"CYPHER": "Cypher",
"CYTHON": "Cython",
"D": "D",
"DART": "Dart",
"DIFF": "Diff",
"DJANGO": "Django",
"DOCKERFILE": "Dockerfile",
"DTD": "DTD",
"DYLAN": "Dylan",
"EJS": "EJS",
"ELIXIR": "Elixir",
"ELM": "Elm",
"EMBEDDEDJS": "EmbeddedJS",
"ERB": "ERB",
"ERLANG": "Erlang",
"FSHARP": "F#",
"FLOW": "Flow",
"FORTH": "Forth",
"FORTRAN": "Fortran",
"FSHARP": "FSharp",
"GAS": "Gas",
"GFM": "GFM",
"GHERKIN": "Gherkin",
"GLSL": "GLSL",
"GO": "Go",
"GROOVY": "Groovy",
"HANDLEBARS": "Handlebars",
"HASKELL": "Haskell",
"HAXE": "Haxe",
"HIVE": "Hive",
"HTACCESS": "htaccess",
"HTML": "HTML",
"HTTP": "HTTP",
"HXML": "HXML",
"IDL": "IDL",
"INI": "INI",
"JADE": "Jade",
"JAVA": "Java",
"JAVASCRIPT": "JavaScript",
"JINJA2": "Jinja2",
"JS": "JavaScript",
"JSON": "JSON",
"JSP": "JSP",
"JSX": "JSX",
"JULIA": "Julia",
"KOTLIN": "Kotlin",
"LATEX": "LaTeX",
"LESS": "Less",
"LISP": "Lisp",
"LIVESCRIPT": "LiveScript",
"LUA": "Lua",
"MAKEFILE": "Makefile",
"MARIADB": "MariaDB",
"MARKDOWN": "Markdown",
"MATHEMATICA": "Mathematica",
"MATLAB": "MATLAB",
"MBOX": "Mbox",
"MERMAID": "Mermaid",
"MODELICA": "Modelica",
"MSSQL": "MSSQL",
"MYSQL": "MySQL",
"NGINX": "Nginx",
"NIM": "Nim",
"NSIS": "NSIS",
"objc": "Objective-C",
"OBJECTIVE-C": "Objective-C",
"OCAML": "OCaml",
"OCTAVE": "Octave",
"OZ": "Oz",
"PASCAL": "Pascal",
"PEGJS": "PEG.js",
"PERL": "Perl",
"PERL6": "Perl6",
"PGP": "PGP",
"PHP": "PHP",
"PHPHTML": "PHP+HTML",
"PLSQL": "PL/SQL",
"POSTGRESQL": "PostgreSQL",
"POWERSHELL": "PowerShell",
"PROPERTIES": "Properties",
"PROTOBUF": "Protocol Buffers",
"PSEUDOCODE": "Pseudocode",
"PUG": "Pug",
"PYTHON": "Python",
"Q": "Q",
"R": "R",
"REACT": "React",
"RESTRUCTUREDTEXT": "reStructuredText",
"RST": "RST",
"RUBY": "Ruby",
"RUST": "Rust",
"SAS": "SAS",
"SCALA": "Scala",
"SCHEME": "Scheme",
"SCSS": "SCSS",
"SEQUENCE": "Sequence",
"SH": "Shell",
"SHELL": "Shell",
"SMALLTALK": "Smalltalk",
"SMARTY": "Smarty",
"SOLIDITY": "Solidity",
"SPARQL": "SPARQL",
"SPREADSHEET": "Spreadsheet",
"SQL": "SQL",
"SQLITE": "SQLite",
"SQUIRREL": "Squirrel",
"STATA": "Stata",
"STYLUS": "Stylus",
"SVELTE": "Svelte",
"SWIFT": "Swift",
"SYSTEMVERILOG": "SystemVerilog",
"TCL": "Tcl",
"TEX": "TeX",
"TIDDLYWIKI": "TiddlyWiki",
"TIKIWIki": "Tiki Wiki",
"TOML": "TOML",
"TS": "TypeScript",
"TSX": "TSX",
"TURTLE": "Turtle",
"TWIG": "Twig",
"TYPESCRIPT": "TypeScript",
"V": "V",
"VB": "VB",
"VBSCRIPT": "VBScript",
"VELOCITY": "Velocity",
"VERILOG": "Verilog",
"VHDL": "VHDL",
"VISUALBASIC": "Visual Basic",
"VUE": "Vue",
"WEBIDL": "Web IDL",
"WIKI": "Wi",
"XAML": "XAML",
"XML": "XML",
"XMLDTD": "XML DTD",
"XQUERY": "XQuery",
"YACAS": "Yacas",
"YAML": "YAML",
"YARA": "YARA"
};
// 创建并配置通用类型的标签/按钮
const createElement = (type, className, hint, contentOrIconName, isButton = false) => {
const element = document.createElement(type);
element.className = className;
element.setAttribute("ty-hint", hint);
element.innerHTML = isButton ? `<i class="iconfont icon-${contentOrIconName}"></i>` : contentOrIconName;
return element;
};
// 增强代码块功能
const enhanceCodeBlock = (target, language) => {
if (!config.ENABLE) return;
const codeBlockEnhanced = document.createElement("div");
codeBlockEnhanced.classList.add("code-enhanced");
// 功能按钮和标签的创建逻辑
const features = [
{ enabled: config.FOLD_ENABLED, className: 'fold', hint: "折叠代码块", iconName: 'regular-down' },
{ enabled: config.REFRESH_ENABLED, className: 'refresh', hint: "刷新代码块", iconName: 'shuaxin5' },
{ enabled: config.COPY_ENABLED, className: 'copy', hint: "复制代码", iconName: 'clipboard' },
];
features.forEach(({ enabled, className, hint, iconName }) => {
codeBlockEnhanced.appendChild(enabled ? createElement("div", `code-block-${className}`, hint, iconName, true) : document.createDocumentFragment());
});
// language 以第一个空格为界限,前部分为 codeLang,后部分为 codeIntro
const codeLang = config.LANG_ENABLED ? language.split(' ')[0] : '';
const codeIntro = config.INTRO_ENABLED ? language.replace(/^\S+\s*/, '').trim() : '';
codeBlockEnhanced.appendChild(config.LANG_ENABLED ? createElement('div', "code-block-lang", "编程语言", getCodeLangFullName(codeLang)) : document.createDocumentFragment());
codeBlockEnhanced.appendChild(config.INTRO_ENABLED ? createElement('div', "code-block-intro", "代码块说明", codeIntro) : document.createDocumentFragment());
// 设置滚动容器高度
const scroll = target.querySelector(".CodeMirror-scroll");
if (scroll) scroll.style.height = `${scroll.scrollHeight}px`;
target.appendChild(codeBlockEnhanced);
// 自动折叠过长代码块
// const codeBlockLines = target.querySelectorAll(".CodeMirror-scroll .CodeMirror-code .CodeMirror-line").length;
// if (codeBlockLines > config.MAX_LINE_NUM) {
// const foldButton = target.querySelector(".code-block-fold i");
// if (foldButton) {
// foldCodeBlock(null, foldButton); // 模拟点击折叠按钮
// }
// }
};
// 获取编程语言的标准形式
const getCodeLangFullName = (language) => codeLangList[language.toUpperCase()] || language.toUpperCase();
// 增强新添加的代码块
const enhanceNewCodeBlock = (codeBlock, language) => {
if (!codeBlock.querySelector('.code-enhanced')) enhanceCodeBlock(codeBlock, language);
}
// 装饰器函数,用于动态修改 File.editor.fences.addCodeBlock 函数的行为
const decorateAddCodeBlock = () => {
if (File?.editor?.fences?.addCodeBlock) {
const original = File.editor.fences.addCodeBlock;
File.editor.fences.addCodeBlock = function (...args) {
const result = original.apply(this, args);
const [cid] = args;
const codeBlock = document.querySelector(`pre.md-fences[cid="${cid}"]`);
if (codeBlock) {
const language = codeBlock.getAttribute('lang');
enhanceNewCodeBlock(codeBlock, language);
}
return result;
}
observer.disconnect(); // 目标函数可用,停止观察
}
}
// 使用 MutationObserver 监视 DOM 变化
const writeContainer = document.querySelector('#write');
const observer = new MutationObserver(decorateAddCodeBlock);
observer.observe(writeContainer, { childList: true, subtree: true });
// 设置点击事件监听器
document.getElementById("write").addEventListener("click", (ev) => {
const { target } = ev;
const handleAction = (actionFunction) => (ev) => {
ev.preventDefault();
ev.stopPropagation();
actionFunction(ev, ev.target);
};
config.COPY_ENABLED && target.closest(".code-block-copy") && handleAction(copyCodeBlock)(ev);
config.FOLD_ENABLED && target.closest(".code-block-fold") && handleAction(foldCodeBlock)(ev);
config.REFRESH_ENABLED && target.closest(".code-block-refresh") && handleAction(refreshCodeBlock)(ev);
});
// 成功反馈功能(刷新、复制按钮)
const successFeedback = (type, button) => {
button.style.visibility = 'hidden';
const parentRect = button.parentNode.getBoundingClientRect();
const buttonRect = button.getBoundingClientRect();
const successIcon = document.createElement('i');
successIcon.className = `iconfont icon-check code-block-${type}`;
successIcon.style.position = 'absolute';
successIcon.style.left = `${buttonRect.left - parentRect.left}px`;
successIcon.style.top = `${buttonRect.top - parentRect.top}px`;
button.parentNode.insertBefore(successIcon, button);
setTimeout(() => {
successIcon.remove();
button.style.visibility = 'visible';
}, 1000);
};
// 折叠代码块功能
const foldCodeBlock = (ev, foldButton) => {
document.activeElement.blur();
const scrollArea = foldButton.closest(".md-fences").querySelector(".CodeMirror-scroll");
const lineHeight = window.getComputedStyle(foldButton).lineHeight;
const isCollapsed = scrollArea.classList.contains('folded');
const [newHeight, newOverflowY] = isCollapsed ? [scrollArea.scrollHeight + 'px', ''] : [lineHeight, 'hidden'];
// 切换折叠状态并更新样式
[scrollArea, foldButton].forEach(el => el.classList.toggle('folded'));
foldButton.style.transform = `rotate(${!isCollapsed ? -90 : 0}deg)`;
foldButton.style.transition = 'transform 0.3s ease-in-out';
scrollArea.style.height = `${parseFloat(newHeight) + (!isCollapsed ? 3.2 : 0)}px`;
scrollArea.style.overflowY = newOverflowY;
scrollArea.style.transition = 'height 0.3s ease-in-out, overflow-y 0.3s ease-in-out';
};
let lastClickTime = 0;
// 刷新代码块功能(代码块高度、编程语言、说明)
const refreshCodeBlock = (ev, refreshButton) => {
document.activeElement.blur();
if (ev.timeStamp - lastClickTime < config.CLICK_CHECK_INTERVAL) return;
lastClickTime = ev.timeStamp;
const fenceContainer = refreshButton.closest(".md-fences");
const codeBlockMsg = fenceContainer.getAttribute("lang");
const codeLang = codeBlockMsg.split(' ')[0];
const codeIntro = codeBlockMsg.replace(/^\S+\s*/, '').trim();
const codeLangTag = fenceContainer.querySelector(".code-block-lang");
const codeIntroTag = fenceContainer.querySelector(".code-block-intro");
const scroll = fenceContainer.querySelector(".CodeMirror-scroll");
if (codeLangTag) codeLangTag.innerHTML = getCodeLangFullName(codeLang);
if (codeIntroTag) codeIntroTag.textContent = codeIntro || '';
if (scroll) {
// 获取实际内容的高度
const contentHeight = scroll.querySelector(".CodeMirror-code").scrollHeight;
scroll.style.transition = 'height 0.3s ease-in-out, overflow-y 0.3s ease-in-out';
// 设置高度为实际内容高度
scroll.style.height = `${contentHeight}px`;
scroll.classList.remove('folded');
// 移除折叠按钮的折叠状态
const foldButton = fenceContainer.querySelector(".code-block-fold .folded");
if (foldButton) {
foldButton.classList.remove('folded');
foldButton.style.transform = 'rotate(0deg)';
}
// 移除溢出设置
scroll.style.overflowY = '';
}
successFeedback("refresh", refreshButton);
};
// 复制代码功能
const copyCodeBlock = async (ev, copyButton) => {
document.activeElement.blur();
if (ev.timeStamp - lastClickTime < config.CLICK_CHECK_INTERVAL) return;
lastClickTime = ev.timeStamp;
const fenceContainer = copyButton.closest(".md-fences");
const lines = fenceContainer.querySelectorAll(".CodeMirror-code .CodeMirror-line");
if (!lines.length) return;
let content = '';
const badCharReplacements = {
'\u200b': '', // 零宽空格
'\u00A0': ' ', // 不换行空格
'\n': '\n' // 保持换行符
};
const badCharRegex = new RegExp(`[${Object.keys(badCharReplacements).join('')}]`, 'g');
lines.forEach(line => {
content += line.textContent.replace(badCharRegex, char => badCharReplacements[char]) + '\n';
});
content = content.slice(0, -1); // 移除最后一个多余的换行符
navigator.clipboard.writeText(content);
successFeedback("copy", copyButton);
};
// 监听是否导出,如果有,就展开所有已折叠的代码块
const exportObserver = new MutationObserver(mutationsList => {
mutationsList.forEach(mutation => {
if (mutation.attributeName === 'class' && document.body.classList.contains('ty-show-notification')) {
document.querySelectorAll('.md-fences .CodeMirror-scroll.folded').forEach(block => {
const foldButton = block.closest('.md-fences').querySelector('.code-block-fold i');
foldCodeBlock(null, foldButton);
});
}
});
});
exportObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });
console.log("代码块增加插件加载完成!");
})();
好像不兼容 macos
如果是 1,下载附件 iconfont.woff2
,在主题中引用该字体。
如果是 2,请自行修改。我没有 Mac 电脑,无能为力。 iconfont.zip
感觉这个功能挺不错的。是否可以实现呢?