Closed jinghu-moon closed 1 year ago
可以暂时尝试在控制台输入以下代码段临时实现这个功能,它只会更改显示,不会影响文本内容
document.getElementsByClassName("md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded").forEach((el)=>{el.outerHTML="<details open>"+el.outerHTML+"</details>"})
感谢作者开源了这个项目,让我开了眼界,学习到了不少
目标:从我使用Typora以来便发现代码块没有一键复制功能,上网搜索发现2021年有给Typora开发者提过这方面issue,但之后杳无音信
问题:看到了作者的成品,我便想尝试一番,看看能不能实现,如果单纯在网页实现,问题不大,但在typora内我发现学习作者注入js代码不运行,控制台可运行
以下三张图辅助说明
请问有法子解决嘛
感谢作者开源了这个项目,让我开了眼界,学习到了不少
目标:从我使用 Typora 以来便发现 代码块没有一键复制功能 ,上网搜索发现 2021 年有给 Typora 开发者提过这方面 issue,但之后杳无音信
问题:看到了作者的成品,我便想尝试一番,看看能不能实现,如果单纯在网页实现,问题不大,但在 typora 内我发现学习作者注入 js 代码不运行,控制台可运行
以下三张图辅助说明
请问有法子解决嘛
你掉坑里了。
Typora 是延迟加载页面的,文件内容都是通过延迟执行的 js 写入 document 的。
md-fences 在 frame.js 中是很晚生成的,并且有清空 innerHTML 操作。所以你必须等到 frame.js 执行完毕后才能执行脚本。
这里给出清空 md-fences innerHTML 的函数链条,打个断点就知道了: restoreEditStateFromData -> refresh -> refreshUnder -> refreshEditor -> addCodeBlock -> b.addClass("ty-contain-cm").html("");
我花了点时间,整了一个我的版本的 copy_code.js。代码文件已经上传,里面有更加详细的注释。你可以看看。有什么问题可以继续在这说。 代码本身很简单,主要还是花在了逆向上。
@jinghu-moon @lion-no-back 折叠和复制功能已经做了,你们可以用用看。
不过折叠功能做到了标题上,我感觉标题折叠可能更有用一些 : )
下午闲着没事做把代码完善了下。效果如下:
修改后的 js 代码:
(() => {
const config = {
// 启用脚本,若为false,以下配置全部失效
ENABLE: true,
LOOP_DETECT_INTERVAL: 20,
CLICK_CHECK_INTERVAL: 300,
DEBUG: false
}
if (!config.ENABLE) {
return;
}
const addCopyElement = (target) => {
let a = target.querySelector(".typora-copy-code");
if (!a) {
a = document.createElement("a");
a.setAttribute("class", "typora-copy-code");
a.innerText = "Copy";
target.appendChild(a);
}
};
const decorator = (original, after) => {
return function () {
const result = original.apply(this, arguments);
after.call(this, result, ...arguments);
return result;
};
};
const after = (result, ...args) => {
const cid = args[0];
if (cid) {
const ele = document.querySelector(`#write .md-fences[cid=${cid}]`);
addCopyElement(ele);
}
};
const _timer = setInterval(() => {
if (File?.editor?.fences?.addCodeBlock) {
clearInterval(_timer);
File.editor.fences.addCodeBlock = decorator(
File.editor.fences.addCodeBlock,
after
);
}
}, config.LOOP_DETECT_INTERVAL);
const badChars = [
"%E2%80%8B", // ZERO WIDTH SPACE \u200b
"%C2%A0", // NO-BREAK SPACE \u00A0
"%0A" // NO-BREAK SPACE \u0A
];
const replaceChars = ["", "%20", ""];
let lastClickTime = 0;
document.getElementById("write").addEventListener("click", (ev) => {
const button = ev.target.closest(".typora-copy-code");
if (!button) {
return;
}
ev.preventDefault();
ev.stopPropagation();
if (ev.timeStamp - lastClickTime < config.CLICK_CHECK_INTERVAL) {
return;
}
lastClickTime = ev.timeStamp;
const lines = button
.closest(".md-fences")
.querySelectorAll(".CodeMirror-code .CodeMirror-line");
if (!lines) {
return;
}
const contentList = [];
lines.forEach((line) => {
let encodeText = encodeURI(line.textContent);
for (let i = 0; i < badChars.length; i++) {
if (encodeText.indexOf(badChars[i]) !== -1) {
encodeText = encodeText.replace(
new RegExp(badChars[i], "g"),
replaceChars[i]
);
}
}
encodeText = decodeURI(encodeText);
contentList.push(encodeText);
});
const result = contentList.join("\n");
navigator.clipboard.writeText(result);
button.classList.toggle("copied");
setTimeout(() => {
button.classList.toggle("copied");
}, 2000);
});
if (config.DEBUG) {
JSBridge.invoke("window.toggleDevTools");
}
console.log("copy_code.js has been injected");
})();
使用的 css 代码:
@font-face {
font-family : "iconfont";
src : url("./icon/iconfont.woff2");
font-display: swap;
}
#write .md-fences .typora-copy-code {
position: absolute;
top : .1em;
right : .5em;
z-index : 10;
opacity : 0.6;
cursor : pointer;
color : transparent;
}
#write .md-fences .typora-copy-code::before,
#write .md-fences .typora-copy-code.copied::before {
font-family: "iconfont";
color : #999;
position : absolute;
font-size : 13px;
right : 25px;
top : -1px;
}
#write .md-fences .typora-copy-code::before {
content: "\e62b";
}
#write .md-fences .typora-copy-code.copied::before {
content: "\e666";
font-weight: bold;
}
css 也是在 window.html 里引入,或者直接添加到用户当前使用的主题代码里。
打算直接把 css 写入 js 里面,但是 css 代码里的转义字符,js 识别不出来,就只能分开了。老哥看看可以合在一起不。
此外,我想把这个功能加到我写的 Typora 主题,可以吗?会注明出处。
此外,我想把这个功能加到我写的 Typora 主题,可以吗?会注明出处。
没问题的,如果能贴出 github 链接就更好了。
@jinghu-moon @lion-no-back 折叠和复制功能已经做了,你们可以用用看。
不过折叠功能做到了标题上,我感觉标题折叠可能更有用一些 : )
真厉害啊,感觉标题折叠和代码块复制这两个功能可解决很多燃眉之急
我也想提供代码块复制交互效果,奈何俺太菜了,在typora上尝试了一天,哪怕对照你写的源码和注释(俺菜了,大部分看不懂),依旧无计可施,于是在热心网友@jinghu-moon提供的代码基础上做了点改进,自己用了感觉可以接受,所以先到此吧
(() => {
const config = {
// 启用脚本,若为false,以下配置全部失效
ENABLE: true,
LOOP_DETECT_INTERVAL: 20,
CLICK_CHECK_INTERVAL: 300,
DEBUG: false
}
if (!config.ENABLE) {
return
}
(() => {
const css = `
#write .md-fences .typora-copy-code {
position: absolute;
top : .1em;
right : .5em;
z-index : 10;
opacity : 0.6;
cursor : pointer;
color : transparent;
}
#write .md-fences .typora-copy-code::before,
#write .md-fences .typora-copy-code.copied::before {
font-family: "OPPOSans";
color : #999;
position : absolute;
font-size : 15px;
right : 3px;
top : -1px;
}
#write .md-fences .typora-copy-code::before {
content: "Copy";
}
#write .md-fences .typora-copy-code.copied::before {
content: "Copied";
color: #32cd32;
font-weight: bold;
}
`
const style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css;
document.getElementsByTagName("head")[0].appendChild(style);
})()
const addCopyElement = (target) => {
let a = target.querySelector(".typora-copy-code");
if (!a) {
a = document.createElement("a");
a.setAttribute("class", "typora-copy-code");
target.appendChild(a);
}
}
const decorator = (original, after) => {
return function () {
const result = original.apply(this, arguments);
after.call(this, result, ...arguments);
return result;
};
}
const after = (result, ...args) => {
const cid = args[0];
if (cid) {
const ele = document.querySelector(`#write .md-fences[cid=${cid}]`);
addCopyElement(ele);
}
}
const _timer = setInterval(() => {
if (File?.editor?.fences?.addCodeBlock) {
clearInterval(_timer);
File.editor.fences.addCodeBlock = decorator(File.editor.fences.addCodeBlock, after);
}
}, config.LOOP_DETECT_INTERVAL);
const badChars = [
"%E2%80%8B", // ZERO WIDTH SPACE \u200b
"%C2%A0", // NO-BREAK SPACE \u00A0
"%0A" // NO-BREAK SPACE \u0A
];
const replaceChars = ["", "%20", ""];
let lastClickTime = 0;
document.getElementById("write").addEventListener("click", ev => {
const button = ev.target.closest(".typora-copy-code");
if (!button) {
return
}
ev.preventDefault();
ev.stopPropagation();
if (ev.timeStamp - lastClickTime < config.CLICK_CHECK_INTERVAL) {
return
}
lastClickTime = ev.timeStamp;
const lines = button.closest(".md-fences").querySelectorAll(".CodeMirror-code .CodeMirror-line")
if (!lines) {
return
}
document.activeElement.blur();
const contentList = [];
lines.forEach(line => {
let encodeText = encodeURI(line.textContent);
for (let i = 0; i < badChars.length; i++) {
if (encodeText.indexOf(badChars[i]) !== -1) {
encodeText = encodeText.replace(new RegExp(badChars[i], "g"), replaceChars[i]);
}
}
const decodeText = decodeURI(encodeText);
contentList.push(decodeText)
})
const result = contentList.join("\n");
navigator.clipboard.writeText(result);
button.classList.toggle("copied");
setTimeout(() => {
button.classList.toggle("copied");
}, 1000);
})
if (config.DEBUG) {
JSBridge.invoke("window.toggleDevTools");
}
console.log("copy_code.js had been injected");
})()
谢谢分享
又做了些改动。可实现代码块复制与显示代码块编程语言的功能。使用效果与前面发的图一致。前面那张图显示代码块编程语言是通过 CSS 实现的。
js 代码如下:
(() => {
const config = {
// 启用脚本,若为 false,以下配置全部失效
ENABLE: true,
LOOP_DETECT_INTERVAL: 20,
CLICK_CHECK_INTERVAL: 300,
DEBUG: false
};
// 添加常用编程语言字母和对应的全名
const codeLang = {
ABAP: "ABAP",
APL: "APL",
ASCIIARMOR: "ASCII Armor",
ASN1: "ASN.1",
ASP: "ASP",
ASSEMBLY: "Assembly",
BASH: "Bash",
BASIC: "BASIC",
C: "C",
CSHARP: "C#",
CPP: "C++",
CASSANDRA: "Cassandra",
CEYLON: "Ceylon",
CLIKE: "C-like",
CLOJURE: "Clojure",
CMAKE: "CMake",
COBOL: "COBOL",
COFFEESCRIPT: "CoffeeScript",
COMMONLISP: "Common Lisp",
CQL: "CQL",
CRYSTAL: "Crystal",
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",
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",
MSSQL: "MSSQL",
MYSQL: "MySQL",
NGINX: "Nginx",
NIM: "Nim",
NSIS: "NSIS",
OBJC: "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",
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: "Wiki",
XAML: "XAML",
XML: "XML",
XMLDTD: "XML DTD",
XQUERY: "XQuery",
YACAS: "Yacas",
YAML: "YAML",
YARA: "YARA",
};
if (!config.ENABLE) {
return;
}
const addCopyElement = (target, language) => {
const codeEnhanced = document.createElement("div");
codeEnhanced.classList.add("code-enhanced");
// 复制按钮
const copyButton = document.createElement("div");
copyButton.classList.add("typora-copy-code");
copyButton.setAttribute("ty-hint", "复制代码"); // 添加 ty-hint 属性
copyButton.innerHTML = "Copy";
codeEnhanced.appendChild(copyButton);
// 代码块编程语言
const codeLangTag = document.createElement("div");
codeLangTag.classList.add("code-lang-tag");
codeLangTag.innerHTML = getCodeLangFullName(language);
codeEnhanced.appendChild(codeLangTag);
target.appendChild(codeEnhanced);
};
const getCodeLangFullName = (language) => {
const lang = language.toUpperCase();
return codeLang[lang] || language;
};
const decorator = (original, after) => {
return function () {
const result = original.apply(this, arguments);
after.call(this, result, ...arguments);
return result;
};
};
const after = (result, ...args) => {
const cid = args[0];
if (cid) {
const ele = document.querySelector(`pre.md-fences[cid=${cid}]`);
const language = ele.getAttribute("lang");
const codeEnhanced = ele.querySelector(".code-enhanced");
if (!codeEnhanced) {
addCopyElement(ele, language);
}
}
};
const _timer = setInterval(() => {
if (File?.editor?.fences?.addCodeBlock) {
clearInterval(_timer);
File.editor.fences.addCodeBlock = decorator(
File.editor.fences.addCodeBlock,
after
);
}
}, config.LOOP_DETECT_INTERVAL);
const badChars = [
"%E2%80%8B", // ZERO WIDTH SPACE \u200b
"%C2%A0", // NO-BREAK SPACE \u00A0
"%0A" // NO-BREAK SPACE \u0A
];
const replaceChars = ["", "%20", ""];
let lastClickTime = 0;
document.getElementById("write").addEventListener("click", (ev) => {
const button = ev.target.closest(".typora-copy-code");
if (!button) {
return;
}
ev.preventDefault();
ev.stopPropagation();
if (ev.timeStamp - lastClickTime < config.CLICK_CHECK_INTERVAL) {
return;
}
lastClickTime = ev.timeStamp;
const lines = button
.closest("pre.md-fences")
.querySelectorAll(".CodeMirror-code .CodeMirror-line");
if (!lines) {
return;
}
const contentList = [];
lines.forEach((line) => {
let encodeText = encodeURI(line.textContent);
for (let i = 0; i < badChars.length; i++) {
if (encodeText.indexOf(badChars[i]) !== -1) {
encodeText = encodeText.replace(
new RegExp(badChars[i], "g"),
replaceChars[i]
);
}
}
encodeText = decodeURI(encodeText);
contentList.push(encodeText);
});
const result = contentList.join("\n");
navigator.clipboard.writeText(result);
button.classList.toggle("copied");
setTimeout(() => {
button.classList.toggle("copied");
}, 2000);
});
if (config.DEBUG) {
JSBridge.invoke("window.toggleDevTools");
}
console.log("copy_code.js has been injected");
})();
直接覆盖原有的 copy_code.js。
CSS 代码如下:
/* 代码块增强 ———— 复制、编程语言 */
#write .md-fences .code-enhanced {
position: absolute;
top : .1em;
right : .5em;
z-index : 10 !important;
cursor : pointer;
color : transparent;
display : flex;
}
#write .md-fences .code-enhanced .typora-copy-code {
position: relative;
}
#write .md-fences .code-enhanced .typora-copy-code::after,
#write .md-fences .code-enhanced .typora-copy-code.copied::after {
font-family: "iconfont";
color : var(--code-block-right-program-lang-text-color);
position : absolute;
font-size : 13px;
right : 0;
top : 0;
opacity : 0.6;
}
#write .md-fences .code-enhanced .typora-copy-code::after {
content: "\e62b";
}
#write .md-fences .code-enhanced .typora-copy-code.copied::after {
content : "\e666";
font-weight: bold;
}
.code-lang-tag {
color : #aaa;
margin-left: 5px;
font-size : 15px;
}
CSS 代码放在当前使用主题的 CSS 文件里。
注意:CSS 代码使用了 iconfont 的图标字,。需要自行调整。直接复制无法显示复制按钮。
目前存在一个问题:在代码块语言输入框更改编程语言,右上角无法及时显示当前代码块编程语言,必须关闭窗口,再次打开,才行。
如题,对于程序员来说,这两个功能挺有用的。老哥可以实现吗?