🎨 Improve code block language selection (#15966)

This commit is contained in:
Jeffrey Chen 2025-10-20 18:21:06 +08:00 committed by GitHub
parent 370eb68266
commit a0593724a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1236,10 +1236,19 @@ export class Toolbar {
hideElements(["hint"], protyle); hideElements(["hint"], protyle);
window.siyuan.menus.menu.remove(); window.siyuan.menus.menu.remove();
this.range = getEditorRange(nodeElement); this.range = getEditorRange(nodeElement);
let html = `<div class="b3-list-item">${window.siyuan.languages.clear}</div>`;
this.subElement.style.width = "";
this.subElement.style.padding = "";
this.subElement.innerHTML = `<div data-id="codeLanguage" class="fn__flex-column" style="max-height:50vh">
<input placeholder="${window.siyuan.languages.search}" style="margin: 0 8px 4px 8px" class="b3-text-field"/>
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative"></div>
</div>`;
const listElement = this.subElement.lastElementChild.lastElementChild as HTMLElement;
let html = `<div data-id="clearLanguage" class="b3-list-item">${window.siyuan.languages.clear}</div>`;
let hljsLanguages = Constants.ALIAS_CODE_LANGUAGES.concat(window.hljs?.listLanguages() ?? []).sort(); let hljsLanguages = Constants.ALIAS_CODE_LANGUAGES.concat(window.hljs?.listLanguages() ?? []).sort();
const eventDetail = {languages: hljsLanguages}; const eventDetail = {languages: hljsLanguages, type: "init", listElement};
if (protyle.app && protyle.app.plugins) { if (protyle.app && protyle.app.plugins) {
protyle.app.plugins.forEach((plugin: any) => { protyle.app.plugins.forEach((plugin: any) => {
plugin.eventBus.emit("code-language-update", eventDetail); plugin.eventBus.emit("code-language-update", eventDetail);
@ -1247,18 +1256,13 @@ export class Toolbar {
} }
hljsLanguages = eventDetail.languages; hljsLanguages = eventDetail.languages;
hljsLanguages.forEach((item, index) => { hljsLanguages.forEach((item) => {
html += `<div class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">${item}</div>`; html += `<div data-id="${item}" class="b3-list-item">${item}</div>`;
}); });
this.subElement.style.width = ""; listElement.innerHTML = html;
this.subElement.style.padding = ""; listElement.firstElementChild.nextElementSibling.classList.add("b3-list-item--focus");
this.subElement.innerHTML = `<div class="fn__flex-column" style="max-height:50vh">
<input placeholder="${window.siyuan.languages.search}" style="margin: 0 8px 4px 8px" class="b3-text-field"/>
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative">${html}</div>
</div>`;
const listElement = this.subElement.lastElementChild.lastElementChild as HTMLElement;
const inputElement = this.subElement.querySelector("input"); const inputElement = this.subElement.querySelector("input");
inputElement.addEventListener("keydown", (event: KeyboardEvent) => { inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
event.stopPropagation(); event.stopPropagation();
@ -1269,7 +1273,6 @@ export class Toolbar {
if (event.key === "Enter") { if (event.key === "Enter") {
this.updateLanguage(languageElements, protyle, this.subElement.querySelector(".b3-list-item--focus").textContent); this.updateLanguage(languageElements, protyle, this.subElement.querySelector(".b3-list-item--focus").textContent);
event.preventDefault(); event.preventDefault();
event.stopPropagation();
return; return;
} }
if (event.key === "Escape") { if (event.key === "Escape") {
@ -1277,55 +1280,75 @@ export class Toolbar {
focusByRange(this.range); focusByRange(this.range);
} }
}); });
const highlightText = (text: string, search: string) => {
// 转义正则特殊字符
const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
// 创建不区分大小写的正则表达式
const regex = new RegExp(escapedSearch, "gi");
// 替换匹配内容并保留原始大小写
return text.replace(regex, match =>
`<b>${match}</b>`
);
};
inputElement.addEventListener("input", (event) => { inputElement.addEventListener("input", (event) => {
const lowerCaseValue = inputElement.value.toLowerCase(); const value = inputElement.value.trim();
const matchLanguages = hljsLanguages.filter(item => item.includes(lowerCaseValue)); let matchLanguages;
let html = ""; let html = `<div data-id="clearLanguage" class="b3-list-item">${window.siyuan.languages.clear}</div>`;
// sort let isMatchLanguages = false;
let matchInput = false; // Sort
if (lowerCaseValue) { if (value) {
matchLanguages.sort((a, b) => { const lowerCaseValue = value.toLowerCase();
if (a.startsWith(lowerCaseValue) && b.startsWith(lowerCaseValue)) { matchLanguages = hljsLanguages.filter(
if (a.length < b.length) { item => item.toLowerCase().includes(lowerCaseValue)
return -1; ).sort((a, b) => {
} else if (a.length === b.length) { // 不区分大小写
return 0; const aStartsWith = a.toLowerCase().startsWith(lowerCaseValue);
} else { const bStartsWith = b.toLowerCase().startsWith(lowerCaseValue);
return 1;
} // 两者都匹配开头时,短字符串优先
} else if (a.startsWith(lowerCaseValue)) { if (aStartsWith && bStartsWith) return a.length - b.length;
return -1; if (aStartsWith) return -1;
} else if (b.startsWith(lowerCaseValue)) { if (bStartsWith) return 1;
return 1;
} else { // 都不匹配时保持原顺序
return 0; return 0;
}
}); });
if (window.hljs?.getLanguage(value)) {
// Default languages and their aliases
matchLanguages = [value].concat(matchLanguages.filter(item => item !== value));
}
} }
const eventDetail = {languages: matchLanguages}; const eventDetail = {languages: value ? matchLanguages : hljsLanguages, type: "match", value, listElement};
if (protyle.app && protyle.app.plugins) { if (protyle.app && protyle.app.plugins) {
protyle.app.plugins.forEach((plugin: any) => { protyle.app.plugins.forEach((plugin: any) => {
plugin.eventBus.emit("code-language-update", eventDetail); plugin.eventBus.emit("code-language-update", eventDetail);
}); });
} }
matchLanguages.forEach((item) => { matchLanguages = eventDetail.languages;
if (inputElement.value === item) { if (value) {
matchInput = true; matchLanguages.forEach((item) => {
} if (value === item) {
html += `<div class="b3-list-item">${item.replace(lowerCaseValue, "<b>" + lowerCaseValue + "</b>")}</div>`; isMatchLanguages = true;
}); html += `<div data-id="${item}" class="b3-list-item"><b>${item}</b></div>`;
if (inputElement.value.trim() && !matchInput) { } else {
html = `<div class="b3-list-item"><b>${escapeHtml(inputElement.value.replace(/`| /g, "_"))}</b></div>${html}`; html += `<div data-id="${item}" class="b3-list-item">${highlightText(item, value)}</div>`;
} }
html = `<div class="b3-list-item">${window.siyuan.languages.clear}</div>` + html; });
listElement.innerHTML = html;
if (listElement.childElementCount > 2 && !matchInput && inputElement.value.trim()) {
listElement.firstElementChild.nextElementSibling.nextElementSibling.classList.add("b3-list-item--focus");
} else { } else {
listElement.firstElementChild.nextElementSibling.classList.add("b3-list-item--focus"); matchLanguages.forEach((item) => {
html += `<div data-id="${item}" class="b3-list-item">${item}</div>`;
});
} }
if (value && !isMatchLanguages) {
html += `<div data-id="customLanguage" class="b3-list-item"><b>${escapeHtml(value.replace(/`| /g, "_"))}</b></div>`;
}
listElement.innerHTML = html;
listElement.firstElementChild.nextElementSibling.classList.add("b3-list-item--focus");
event.stopPropagation(); event.stopPropagation();
}); });
listElement.addEventListener("click", (event) => { listElement.addEventListener("click", (event) => {