@ -7,47 +7,44 @@ const removeMarkdown = (
gfm : true ,
gfm : true ,
useImgAltText : false ,
useImgAltText : false ,
preserveLinks : false ,
preserveLinks : false ,
}
} ,
) => {
) => {
let output = markdown || ''
let output = markdown || ""
output = output . replace ( /^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm , '' )
output = output . replace ( /^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm , "" )
try {
try {
if ( options . stripListLeaders ) {
if ( options . stripListLeaders ) {
if ( options . listUnicodeChar )
if ( options . listUnicodeChar )
output = output . replace (
output = output . replace ( /^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm , options . listUnicodeChar + " $1" )
/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm ,
else output = output . replace ( /^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm , "$1" )
options . listUnicodeChar + ' $1'
)
else output = output . replace ( /^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm , '$1' )
}
}
if ( options . gfm ) {
if ( options . gfm ) {
output = output
output = output
. replace ( /\n={2,}/g , '\n' )
. replace ( /\n={2,}/g , "\n" )
. replace ( /~{3}.*\n/g , '' )
. replace ( /~{3}.*\n/g , "" )
. replace ( /~~/g , '' )
. replace ( /~~/g , "" )
. replace ( /`{3}.*\n/g , '' )
. replace ( /`{3}.*\n/g , "" )
}
}
if ( options . preserveLinks ) {
if ( options . preserveLinks ) {
output = output . replace ( /\[(.*?)\][\[\(](.*?)[\]\)]/g , '$1 ($2)' )
output = output . replace ( /\[(.*?)\][\[\(](.*?)[\]\)]/g , "$1 ($2)" )
}
}
output = output
output = output
. replace ( /<[^>]*>/g , '' )
. replace ( /<[^>]*>/g , "" )
. replace ( /^[=\-]{2,}\s*$/g , '' )
. replace ( /^[=\-]{2,}\s*$/g , "" )
. replace ( /\[\^.+?\](\: .*?$)?/g , '' )
. replace ( /\[\^.+?\](\: .*?$)?/g , "" )
. replace ( /(#{1,6})\s+(.+)\1?/g , '<b>$2</b>' )
. replace ( /(#{1,6})\s+(.+)\1?/g , "<b>$2</b>" )
. replace ( /\s{0,2}\[.*?\]: .*?$/g , '' )
. replace ( /\s{0,2}\[.*?\]: .*?$/g , "" )
. replace ( /\!\[(.*?)\][\[\(].*?[\]\)]/g , options . useImgAltText ? '$1' : '' )
. replace ( /\!\[(.*?)\][\[\(].*?[\]\)]/g , options . useImgAltText ? "$1" : "" )
. replace ( /\[(.*?)\][\[\(].*?[\]\)]/g , '<a>$1</a>' )
. replace ( /\[(.*?)\][\[\(].*?[\]\)]/g , "<a>$1</a>" )
. replace ( /!?\[\[\S[^\[\]\|]*(?:\|([^\[\]]*))?\S\]\]/g , '<a>$1</a>' )
. replace ( /!?\[\[\S[^\[\]\|]*(?:\|([^\[\]]*))?\S\]\]/g , "<a>$1</a>" )
. replace ( /^\s{0,3}>\s?/g , '' )
. replace ( /^\s{0,3}>\s?/g , "" )
. replace ( /(^|\n)\s{0,3}>\s?/g , '\n\n' )
. replace ( /(^|\n)\s{0,3}>\s?/g , "\n\n" )
. replace ( /^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g , '' )
. replace ( /^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g , "" )
. replace ( /([\*_]{1,3})(\S.*?\S{0,1})\1/g , '$2' )
. replace ( /([\*_]{1,3})(\S.*?\S{0,1})\1/g , "$2" )
. replace ( /([\*_]{1,3})(\S.*?\S{0,1})\1/g , '$2' )
. replace ( /([\*_]{1,3})(\S.*?\S{0,1})\1/g , "$2" )
. replace ( /(`{3,})(.*?)\1/gm , '$2' )
. replace ( /(`{3,})(.*?)\1/gm , "$2" )
. replace ( /`(.+?)`/g , '$1' )
. replace ( /`(.+?)`/g , "$1" )
. replace ( /\n{2,}/g , '\n\n' )
. replace ( /\n{2,}/g , "\n\n" )
} catch ( e ) {
} catch ( e ) {
console . error ( e )
console . error ( e )
return markdown
return markdown
@ -64,27 +61,28 @@ const highlight = (content, term) => {
if ( directMatchIdx !== - 1 ) {
if ( directMatchIdx !== - 1 ) {
const h = highlightWindow / 2
const h = highlightWindow / 2
const before = content . substring ( 0 , directMatchIdx ) . split ( " " ) . slice ( - h )
const before = content . substring ( 0 , directMatchIdx ) . split ( " " ) . slice ( - h )
const after = content . substring ( directMatchIdx + term . length , content . length - 1 ) . split ( " " ) . slice ( 0 , h )
const after = content
return ( before . length == h ? ` ... ${ before . join ( " " ) } ` : before . join ( " " ) ) + ` <span class="search-highlight"> ${ term } </span> ` + after . join ( " " )
. substring ( directMatchIdx + term . length , content . length - 1 )
. split ( " " )
. slice ( 0 , h )
return (
( before . length == h ? ` ... ${ before . join ( " " ) } ` : before . join ( " " ) ) +
` <span class="search-highlight"> ${ term } </span> ` +
after . join ( " " )
)
}
}
const tokenizedTerm = term . split ( /\s+/ ) . filter ( ( t ) => t !== '' )
const tokenizedTerm = term . split ( /\s+/ ) . filter ( ( t ) => t !== "" )
const splitText = content . split ( /\s+/ ) . filter ( ( t ) => t !== '' )
const splitText = content . split ( /\s+/ ) . filter ( ( t ) => t !== "" )
const includesCheck = ( token ) =>
const includesCheck = ( token ) =>
tokenizedTerm . some ( ( term ) =>
tokenizedTerm . some ( ( term ) => token . toLowerCase ( ) . startsWith ( term . toLowerCase ( ) ) )
token . toLowerCase ( ) . startsWith ( term . toLowerCase ( ) )
)
const occurrencesIndices = splitText . map ( includesCheck )
const occurrencesIndices = splitText . map ( includesCheck )
// calculate best index
// calculate best index
let bestSum = 0
let bestSum = 0
let bestIndex = 0
let bestIndex = 0
for (
for ( let i = 0 ; i < Math . max ( occurrencesIndices . length - highlightWindow , 0 ) ; i ++ ) {
let i = 0 ;
i < Math . max ( occurrencesIndices . length - highlightWindow , 0 ) ;
i ++
) {
const window = occurrencesIndices . slice ( i , i + highlightWindow )
const window = occurrencesIndices . slice ( i , i + highlightWindow )
const windowSum = window . reduce ( ( total , cur ) => total + cur , 0 )
const windowSum = window . reduce ( ( total , cur ) => total + cur , 0 )
if ( windowSum >= bestSum ) {
if ( windowSum >= bestSum ) {
@ -94,10 +92,7 @@ const highlight = (content, term) => {
}
}
const startIndex = Math . max ( bestIndex - highlightWindow , 0 )
const startIndex = Math . max ( bestIndex - highlightWindow , 0 )
const endIndex = Math . min (
const endIndex = Math . min ( startIndex + 2 * highlightWindow , splitText . length )
startIndex + 2 * highlightWindow ,
splitText . length
)
const mappedText = splitText
const mappedText = splitText
. slice ( startIndex , endIndex )
. slice ( startIndex , endIndex )
. map ( ( token ) => {
. map ( ( token ) => {
@ -106,27 +101,28 @@ const highlight = (content, term) => {
}
}
return token
return token
} )
} )
. join ( ' ' )
. join ( " " )
. replaceAll ( '</span> <span class="search-highlight">' , ' ' )
. replaceAll ( '</span> <span class="search-highlight">' , " " )
return ` ${ startIndex === 0 ? '' : '...' } ${ mappedText } ${ endIndex === splitText . length ? '' : '...'
return ` ${ startIndex === 0 ? "" : "..." } ${ mappedText } ${
endIndex === splitText . length ? "" : "..."
} `
} `
} ;
}
( async function ( ) {
; ( async function ( ) {
const encoder = ( str ) => str . toLowerCase ( ) . split ( /([^a-z]|[^\x00-\x7F])+/ )
const encoder = ( str ) => str . toLowerCase ( ) . split ( /([^a-z]|[^\x00-\x7F])+/ )
const contentIndex = new FlexSearch . Document ( {
const contentIndex = new FlexSearch . Document ( {
cache : true ,
cache : true ,
charset : 'latin:extra' ,
charset : "latin:extra" ,
optimize : true ,
optimize : true ,
index : [
index : [
{
{
field : 'content' ,
field : "content" ,
tokenize : 'reverse' ,
tokenize : "reverse" ,
encode : encoder ,
encode : encoder ,
} ,
} ,
{
{
field : 'title' ,
field : "title" ,
tokenize : 'forward' ,
tokenize : "forward" ,
encode : encoder ,
encode : encoder ,
} ,
} ,
] ,
] ,
@ -154,10 +150,8 @@ const highlight = (content, term) => {
const redir = ( id , term ) => {
const redir = ( id , term ) => {
// SPA navigation
// SPA navigation
window . Million . navigate (
window . Million . navigate (
new URL (
new URL ( ` ${ BASE _URL . replace ( /\/$/g , "" ) } ${ id } #:~:text= ${ encodeURIComponent ( term ) } / ` ) ,
` ${ BASE _URL . replace ( /\/$/g , "" ) } ${ id } #:~:text= ${ encodeURIComponent ( term ) } / `
".singlePage" ,
) ,
'.singlePage'
)
)
closeSearch ( )
closeSearch ( )
}
}
@ -169,24 +163,24 @@ const highlight = (content, term) => {
content : content [ id ] . content ,
content : content [ id ] . content ,
} )
} )
const source = document . getElementById ( 'search-bar' )
const source = document . getElementById ( "search-bar" )
const results = document . getElementById ( 'results-container' )
const results = document . getElementById ( "results-container" )
let term
let term
source . addEventListener ( 'keyup' , ( e ) => {
source . addEventListener ( "keyup" , ( e ) => {
if ( e . key === 'Enter' ) {
if ( e . key === "Enter" ) {
const anchor = document . getElementsByClassName ( 'result-card' ) [ 0 ]
const anchor = document . getElementsByClassName ( "result-card" ) [ 0 ]
redir ( anchor . id , term )
redir ( anchor . id , term )
}
}
} )
} )
source . addEventListener ( 'input' , ( e ) => {
source . addEventListener ( "input" , ( e ) => {
term = e . target . value
term = e . target . value
const searchResults = contentIndex . search ( term , [
const searchResults = contentIndex . search ( term , [
{
{
field : 'content' ,
field : "content" ,
limit : 10 ,
limit : 10 ,
} ,
} ,
{
{
field : 'title' ,
field : "title" ,
limit : 5 ,
limit : 5 ,
} ,
} ,
] )
] )
@ -198,7 +192,7 @@ const highlight = (content, term) => {
return [ ... results [ 0 ] . result ]
return [ ... results [ 0 ] . result ]
}
}
}
}
const allIds = new Set ( [ ... getByField ( 'title' ) , ... getByField ( 'content' ) ] )
const allIds = new Set ( [ ... getByField ( "title" ) , ... getByField ( "content" ) ] )
const finalResults = [ ... allIds ] . map ( formatForDisplay )
const finalResults = [ ... allIds ] . map ( formatForDisplay )
// display
// display
@ -213,58 +207,55 @@ const highlight = (content, term) => {
resultToHTML ( {
resultToHTML ( {
... result ,
... result ,
term ,
term ,
} )
} ) ,
)
)
. join ( '\n' )
. join ( "\n" )
const anchors = [ ... document . getElementsByClassName ( 'result-card' ) ]
const anchors = [ ... document . getElementsByClassName ( "result-card" ) ]
anchors . forEach ( ( anchor ) => {
anchors . forEach ( ( anchor ) => {
anchor . onclick = ( ) => redir ( anchor . id , term )
anchor . onclick = ( ) => redir ( anchor . id , term )
} )
} )
}
}
} )
} )
const searchContainer = document . getElementById ( 'search-container' )
const searchContainer = document . getElementById ( "search-container" )
function openSearch ( ) {
function openSearch ( ) {
if (
if ( searchContainer . style . display === "none" || searchContainer . style . display === "" ) {
searchContainer . style . display === 'none' ||
source . value = ""
searchContainer . style . display === ''
results . innerHTML = ""
) {
searchContainer . style . display = "block"
source . value = ''
results . innerHTML = ''
searchContainer . style . display = 'block'
source . focus ( )
source . focus ( )
} else {
} else {
searchContainer . style . display = 'none'
searchContainer . style . display = "none"
}
}
}
}
function closeSearch ( ) {
function closeSearch ( ) {
searchContainer . style . display = 'none'
searchContainer . style . display = "none"
}
}
document . addEventListener ( 'keydown' , ( event ) => {
document . addEventListener ( "keydown" , ( event ) => {
if ( event . key === 'k' && ( event . ctrlKey || event . metaKey ) ) {
if ( event . key === "k" && ( event . ctrlKey || event . metaKey ) ) {
event . preventDefault ( )
event . preventDefault ( )
openSearch ( )
openSearch ( )
}
}
if ( event . key === 'Escape' ) {
if ( event . key === "Escape" ) {
event . preventDefault ( )
event . preventDefault ( )
closeSearch ( )
closeSearch ( )
}
}
} )
} )
const searchButton = document . getElementById ( 'search-icon' )
const searchButton = document . getElementById ( "search-icon" )
searchButton . addEventListener ( 'click' , ( evt ) => {
searchButton . addEventListener ( "click" , ( evt ) => {
openSearch ( )
openSearch ( )
} )
} )
searchButton . addEventListener ( 'keydown' , ( evt ) => {
searchButton . addEventListener ( "keydown" , ( evt ) => {
openSearch ( )
openSearch ( )
} )
} )
searchContainer . addEventListener ( 'click' , ( evt ) => {
searchContainer . addEventListener ( "click" , ( evt ) => {
closeSearch ( )
closeSearch ( )
} )
} )
document . getElementById ( 'search-space' ) . addEventListener ( 'click' , ( evt ) => {
document . getElementById ( "search-space" ) . addEventListener ( "click" , ( evt ) => {
evt . stopPropagation ( )
evt . stopPropagation ( )
} )
} )
} ) ( )
} ) ( )