@@ -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