Skip to content

Commit a059372

Browse files
authored
🎨 Improve code block language selection (#15966)
1 parent 370eb68 commit a059372

File tree

1 file changed

+72
-49
lines changed

1 file changed

+72
-49
lines changed

app/src/protyle/toolbar/index.ts

Lines changed: 72 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,29 +1236,33 @@ export class Toolbar {
12361236
hideElements(["hint"], protyle);
12371237
window.siyuan.menus.menu.remove();
12381238
this.range = getEditorRange(nodeElement);
1239-
let html = `<div class="b3-list-item">${window.siyuan.languages.clear}</div>`;
1239+
1240+
this.subElement.style.width = "";
1241+
this.subElement.style.padding = "";
1242+
this.subElement.innerHTML = `<div data-id="codeLanguage" class="fn__flex-column" style="max-height:50vh">
1243+
<input placeholder="${window.siyuan.languages.search}" style="margin: 0 8px 4px 8px" class="b3-text-field"/>
1244+
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative"></div>
1245+
</div>`;
1246+
const listElement = this.subElement.lastElementChild.lastElementChild as HTMLElement;
1247+
1248+
let html = `<div data-id="clearLanguage" class="b3-list-item">${window.siyuan.languages.clear}</div>`;
12401249
let hljsLanguages = Constants.ALIAS_CODE_LANGUAGES.concat(window.hljs?.listLanguages() ?? []).sort();
12411250

1242-
const eventDetail = {languages: hljsLanguages};
1251+
const eventDetail = {languages: hljsLanguages, type: "init", listElement};
12431252
if (protyle.app && protyle.app.plugins) {
12441253
protyle.app.plugins.forEach((plugin: any) => {
12451254
plugin.eventBus.emit("code-language-update", eventDetail);
12461255
});
12471256
}
12481257

12491258
hljsLanguages = eventDetail.languages;
1250-
hljsLanguages.forEach((item, index) => {
1251-
html += `<div class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">${item}</div>`;
1259+
hljsLanguages.forEach((item) => {
1260+
html += `<div data-id="${item}" class="b3-list-item">${item}</div>`;
12521261
});
12531262

1254-
this.subElement.style.width = "";
1255-
this.subElement.style.padding = "";
1256-
this.subElement.innerHTML = `<div class="fn__flex-column" style="max-height:50vh">
1257-
<input placeholder="${window.siyuan.languages.search}" style="margin: 0 8px 4px 8px" class="b3-text-field"/>
1258-
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative">${html}</div>
1259-
</div>`;
1263+
listElement.innerHTML = html;
1264+
listElement.firstElementChild.nextElementSibling.classList.add("b3-list-item--focus");
12601265

1261-
const listElement = this.subElement.lastElementChild.lastElementChild as HTMLElement;
12621266
const inputElement = this.subElement.querySelector("input");
12631267
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
12641268
event.stopPropagation();
@@ -1269,63 +1273,82 @@ export class Toolbar {
12691273
if (event.key === "Enter") {
12701274
this.updateLanguage(languageElements, protyle, this.subElement.querySelector(".b3-list-item--focus").textContent);
12711275
event.preventDefault();
1272-
event.stopPropagation();
12731276
return;
12741277
}
12751278
if (event.key === "Escape") {
12761279
this.subElement.classList.add("fn__none");
12771280
focusByRange(this.range);
12781281
}
12791282
});
1283+
1284+
const highlightText = (text: string, search: string) => {
1285+
// 转义正则特殊字符
1286+
const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1287+
// 创建不区分大小写的正则表达式
1288+
const regex = new RegExp(escapedSearch, "gi");
1289+
// 替换匹配内容并保留原始大小写
1290+
return text.replace(regex, match =>
1291+
`<b>${match}</b>`
1292+
);
1293+
};
1294+
12801295
inputElement.addEventListener("input", (event) => {
1281-
const lowerCaseValue = inputElement.value.toLowerCase();
1282-
const matchLanguages = hljsLanguages.filter(item => item.includes(lowerCaseValue));
1283-
let html = "";
1284-
// sort
1285-
let matchInput = false;
1286-
if (lowerCaseValue) {
1287-
matchLanguages.sort((a, b) => {
1288-
if (a.startsWith(lowerCaseValue) && b.startsWith(lowerCaseValue)) {
1289-
if (a.length < b.length) {
1290-
return -1;
1291-
} else if (a.length === b.length) {
1292-
return 0;
1293-
} else {
1294-
return 1;
1295-
}
1296-
} else if (a.startsWith(lowerCaseValue)) {
1297-
return -1;
1298-
} else if (b.startsWith(lowerCaseValue)) {
1299-
return 1;
1300-
} else {
1301-
return 0;
1302-
}
1296+
const value = inputElement.value.trim();
1297+
let matchLanguages;
1298+
let html = `<div data-id="clearLanguage" class="b3-list-item">${window.siyuan.languages.clear}</div>`;
1299+
let isMatchLanguages = false;
1300+
// Sort
1301+
if (value) {
1302+
const lowerCaseValue = value.toLowerCase();
1303+
matchLanguages = hljsLanguages.filter(
1304+
item => item.toLowerCase().includes(lowerCaseValue)
1305+
).sort((a, b) => {
1306+
// 不区分大小写
1307+
const aStartsWith = a.toLowerCase().startsWith(lowerCaseValue);
1308+
const bStartsWith = b.toLowerCase().startsWith(lowerCaseValue);
1309+
1310+
// 两者都匹配开头时,短字符串优先
1311+
if (aStartsWith && bStartsWith) return a.length - b.length;
1312+
if (aStartsWith) return -1;
1313+
if (bStartsWith) return 1;
1314+
1315+
// 都不匹配时保持原顺序
1316+
return 0;
13031317
});
1318+
1319+
if (window.hljs?.getLanguage(value)) {
1320+
// Default languages and their aliases
1321+
matchLanguages = [value].concat(matchLanguages.filter(item => item !== value));
1322+
}
13041323
}
13051324

1306-
const eventDetail = {languages: matchLanguages};
1325+
const eventDetail = {languages: value ? matchLanguages : hljsLanguages, type: "match", value, listElement};
13071326
if (protyle.app && protyle.app.plugins) {
13081327
protyle.app.plugins.forEach((plugin: any) => {
13091328
plugin.eventBus.emit("code-language-update", eventDetail);
13101329
});
13111330
}
13121331

1313-
matchLanguages.forEach((item) => {
1314-
if (inputElement.value === item) {
1315-
matchInput = true;
1316-
}
1317-
html += `<div class="b3-list-item">${item.replace(lowerCaseValue, "<b>" + lowerCaseValue + "</b>")}</div>`;
1318-
});
1319-
if (inputElement.value.trim() && !matchInput) {
1320-
html = `<div class="b3-list-item"><b>${escapeHtml(inputElement.value.replace(/`| /g, "_"))}</b></div>${html}`;
1321-
}
1322-
html = `<div class="b3-list-item">${window.siyuan.languages.clear}</div>` + html;
1323-
listElement.innerHTML = html;
1324-
if (listElement.childElementCount > 2 && !matchInput && inputElement.value.trim()) {
1325-
listElement.firstElementChild.nextElementSibling.nextElementSibling.classList.add("b3-list-item--focus");
1332+
matchLanguages = eventDetail.languages;
1333+
if (value) {
1334+
matchLanguages.forEach((item) => {
1335+
if (value === item) {
1336+
isMatchLanguages = true;
1337+
html += `<div data-id="${item}" class="b3-list-item"><b>${item}</b></div>`;
1338+
} else {
1339+
html += `<div data-id="${item}" class="b3-list-item">${highlightText(item, value)}</div>`;
1340+
}
1341+
});
13261342
} else {
1327-
listElement.firstElementChild.nextElementSibling.classList.add("b3-list-item--focus");
1343+
matchLanguages.forEach((item) => {
1344+
html += `<div data-id="${item}" class="b3-list-item">${item}</div>`;
1345+
});
13281346
}
1347+
if (value && !isMatchLanguages) {
1348+
html += `<div data-id="customLanguage" class="b3-list-item"><b>${escapeHtml(value.replace(/`| /g, "_"))}</b></div>`;
1349+
}
1350+
listElement.innerHTML = html;
1351+
listElement.firstElementChild.nextElementSibling.classList.add("b3-list-item--focus");
13291352
event.stopPropagation();
13301353
});
13311354
listElement.addEventListener("click", (event) => {

0 commit comments

Comments
 (0)