A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 181 lines 9.3 kB view raw
1import {addScript} from "../util/addScript"; 2import {Constants} from "../../constants"; 3import {focusByOffset} from "../util/selection"; 4import {setCodeTheme} from "../../util/assets"; 5 6export const highlightRender = (element: Element, cdn = Constants.PROTYLE_CDN, zoom = 1) => { 7 let codeElements: NodeListOf<Element>; 8 let isPreview = false; 9 if (element.classList.contains("code-block")) { 10 // 编辑器内代码块编辑渲染 11 codeElements = element.querySelectorAll(".hljs"); 12 } else { 13 if (element.classList.contains("item__readme")) { 14 // bazaar reademe 15 codeElements = element.querySelectorAll("pre code"); 16 codeElements.forEach(item => { 17 item.parentElement.setAttribute("linenumber", "false"); 18 }); 19 } else if (element.classList.contains("b3-typography")) { 20 // preview & export html markdown 21 codeElements = element.querySelectorAll(".code-block code"); 22 isPreview = true; 23 } else { 24 codeElements = element.querySelectorAll(".code-block .hljs"); 25 } 26 } 27 if (codeElements.length === 0) { 28 return; 29 } 30 31 setCodeTheme(cdn); 32 33 addScript(`${cdn}/js/highlight.js/highlight.min.js?v=11.11.1`, "protyleHljsScript").then(() => { 34 addScript(`${cdn}/js/highlight.js/third-languages.js?v=2.0.1`, "protyleHljsThirdScript").then(() => { 35 codeElements.forEach((block: HTMLElement) => { 36 const iconElements = block.parentElement.querySelectorAll(".protyle-icon"); 37 if (iconElements.length === 2) { 38 iconElements[0].setAttribute("aria-label", window.siyuan.languages.copy); 39 iconElements[1].setAttribute("aria-label", window.siyuan.languages.more); 40 } 41 if (block.getAttribute("data-render") === "true") { 42 return; 43 } 44 const wbrElement = block.querySelector("wbr"); 45 let startIndex = 0; 46 if (wbrElement) { 47 let previousSibling = wbrElement.previousSibling; 48 while (previousSibling) { 49 startIndex += previousSibling.textContent.length; 50 while (!previousSibling.previousSibling && previousSibling.parentElement.tagName !== "DIV") { 51 // 高亮 span 中输入 52 previousSibling = previousSibling.parentElement; 53 } 54 previousSibling = previousSibling.previousSibling; 55 } 56 wbrElement.remove(); 57 } 58 59 let language; 60 if (isPreview) { 61 language = block.parentElement.getAttribute("data-language"); // preview 62 } else if (block.previousElementSibling) { 63 language = block.previousElementSibling.firstElementChild.textContent; 64 } else { 65 // bazaar readme 66 language = block.className.replace("language-", ""); 67 } 68 if (!window.hljs.getLanguage(language)) { 69 language = "plaintext"; 70 } 71 block.classList.add("hljs"); 72 block.setAttribute("data-render", "true"); 73 const autoEnter = block.parentElement.getAttribute("linewrap"); 74 const ligatures = block.parentElement.getAttribute("ligatures"); 75 const lineNumber = block.parentElement.getAttribute("linenumber"); 76 const hljsElement = block.lastElementChild ? block.lastElementChild as HTMLElement : block; 77 if (autoEnter === "true" || (autoEnter !== "false" && window.siyuan.config.editor.codeLineWrap)) { 78 hljsElement.style.setProperty("white-space", "pre-wrap"); 79 hljsElement.style.setProperty("word-break", "break-word"); 80 } else { 81 // https://ld246.com/article/1684031600711 该属性会导致有 tab 后光标跳至末尾,目前无解 82 hljsElement.style.setProperty("white-space", "pre"); 83 hljsElement.style.setProperty("word-break", "initial"); 84 } 85 if (ligatures === "true" || (ligatures !== "false" && window.siyuan.config.editor.codeLigatures)) { 86 hljsElement.style.fontVariantLigatures = "normal"; 87 } else { 88 hljsElement.style.fontVariantLigatures = "none"; 89 } 90 const codeText = hljsElement.textContent; 91 if (block.firstElementChild) { 92 if (!isPreview && (lineNumber === "true" || (lineNumber !== "false" && window.siyuan.config.editor.codeSyntaxHighlightLineNum))) { 93 // 需要先添加 class 以防止抖动 https://ld246.com/article/1648116585443 94 block.firstElementChild.className = "protyle-linenumber__rows"; 95 block.firstElementChild.setAttribute("contenteditable", "false"); 96 lineNumberRender(block, zoom); 97 block.style.display = ""; 98 } else { 99 block.firstElementChild.className = "fn__none"; 100 block.firstElementChild.innerHTML = ""; 101 hljsElement.style.paddingLeft = ""; 102 block.style.display = "block"; 103 } 104 } 105 hljsElement.innerHTML = window.hljs.highlight( 106 codeText + (codeText.endsWith("\n") ? "" : "\n"), // https://github.com/siyuan-note/siyuan/issues/4609 107 { 108 language, 109 ignoreIllegals: true 110 }).value; 111 if (wbrElement && getSelection().rangeCount > 0) { 112 focusByOffset(block, startIndex, startIndex); 113 } 114 }); 115 }); 116 }); 117}; 118 119export const lineNumberRender = (block: HTMLElement, zoom = 1) => { 120 const lineNumber = block.parentElement.getAttribute("lineNumber"); 121 if (lineNumber === "false") { 122 return; 123 } 124 if (!window.siyuan.config.editor.codeSyntaxHighlightLineNum && lineNumber !== "true") { 125 return; 126 } 127 // clientHeight 总是取的整数 128 block.parentElement.style.lineHeight = `${((parseInt(block.parentElement.style.fontSize) || window.siyuan.config.editor.fontSize) * 1.625 * 0.85).toFixed(0)}px`; 129 const codeElement = block.lastElementChild as HTMLElement; 130 131 const lineList = codeElement.textContent.split(/\r\n|\r|\n|\u2028|\u2029/g); 132 if (lineList[lineList.length - 1] === "" && lineList.length > 1) { 133 lineList.pop(); 134 } 135 block.firstElementChild.innerHTML = `<span>${lineList.length}</span>`; 136 codeElement.style.paddingLeft = `${block.firstElementChild.clientWidth + 16}px`; 137 let lineNumberHTML = ""; 138 if (codeElement.style.wordBreak === "break-word") { 139 // 代码块开启了换行 140 const codeElementStyle = window.getComputedStyle(codeElement); 141 const lineNumberTemp = document.createElement("div"); 142 lineNumberTemp.className = "hljs"; 143 // 不能使用 codeElement.clientWidth,被忽略小数点导致宽度不一致 144 lineNumberTemp.setAttribute("style", `padding-left:${codeElement.style.paddingLeft}; 145width: ${codeElement.getBoundingClientRect().width / zoom}px; 146white-space:${codeElementStyle.whiteSpace}; 147word-break:${codeElementStyle.wordBreak}; 148font-variant-ligatures:${codeElementStyle.fontVariantLigatures}; 149padding-right:0;max-height: none;box-sizing: border-box;position: absolute;padding-top:0 !important;padding-bottom:0 !important;min-height:auto !important;`); 150 lineNumberTemp.setAttribute("contenteditable", "true"); 151 block.insertAdjacentElement("afterend", lineNumberTemp); 152 153 lineList.map((line) => { 154 // windows 下空格高度为 0 https://github.com/siyuan-note/siyuan/issues/12346 155 lineNumberTemp.textContent = line.trim() ? line : "<br>"; 156 // 不能使用 lineNumberTemp.getBoundingClientRect().height.toFixed(1) 否则 157 // windows 需等待字体下载完成再计算,否则导致不换行,高度计算错误 158 // https://github.com/siyuan-note/siyuan/issues/9029 159 // https://github.com/siyuan-note/siyuan/issues/9140 160 lineNumberHTML += `<span style="height:${lineNumberTemp.clientHeight}px"></span>`; 161 }); 162 lineNumberTemp.remove(); 163 } else { 164 lineNumberHTML = "<span></span>".repeat(lineList.length); 165 } 166 167 block.firstElementChild.innerHTML = lineNumberHTML; 168 169 // https://github.com/siyuan-note/siyuan/issues/12726 170 if (block.scrollHeight > block.clientHeight) { 171 if (getSelection().rangeCount > 0) { 172 const range = getSelection().getRangeAt(0); 173 if (block.contains(range.startContainer)) { 174 const brElement = document.createElement("br"); 175 range.insertNode(brElement); 176 brElement.scrollIntoView({block: "nearest"}); 177 brElement.remove(); 178 } 179 } 180 } 181};