diff options
Diffstat (limited to 'web/cgit-extra.html')
| -rw-r--r-- | web/cgit-extra.html | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/web/cgit-extra.html b/web/cgit-extra.html new file mode 100644 index 0000000..cb30b54 --- /dev/null +++ b/web/cgit-extra.html @@ -0,0 +1,278 @@ +<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js" async></script> +<script> +const VROOT = "/"; + +function repoPathFromLink( a ) { + const href = new URL( a.getAttribute( "href" ), location.href ); + let p = href.pathname; + if( !p.startsWith( VROOT ) ) return null; + p = p.slice( VROOT.length ); + p = p.replace( /\/+$/, "" ); + if( !p ) return null; + return p; +} + +function branchFromForm() { + const formWrap = document.querySelector( ".form" ); + const form = formWrap.querySelector( "form" ); + + const branchEl = form.querySelector( "select" ); + if( !branchEl ) + return ""; + + const branch = branchEl.value; + if( !branch ) + return ""; + + return branch; +} + +async function getFile( repo, branch, file ) { + try { + const res = await fetch( `https://${location.host}/${repo}/plain/${file}` ); + if( !res.ok ) + return null; + return await res.text(); + } catch( e ) { + return null; + } +} + +function isWhiteChar( char ) { + return char === " " || char === "\t" || char === "\n" || char === "\r"; +} + +function removeTrailingWhiteSpace( text ) { + while( text.length > 1 + && isWhiteChar( text[text.length - 1] ) + && isWhiteChar( text[text.length - 2] ) + ) { + text = text.slice( 0, -1 ); + } + return text; +} + +async function getReadmeFile( repo, branch ) { + let file = await getFile( repo, branch, "README.md" ); + if( file ) + return { file: removeTrailingWhiteSpace( file ), isMd: true }; + + file = await getFile( repo, branch, "README" ); + if( file ) + return { file: removeTrailingWhiteSpace( file ), isMd: false }; + + return null; +} + +function isIndex( repo ) { + let path = location.pathname; + return path.replaceAll( "/", "" ) === repo; +} +function parseLink( text, start ) { + let isImg = 0; + + const open = text.indexOf( "[", start ); + if( open === -1 ) return null; + const close = text.indexOf( "]", open ); + if( close === -1 ) return null; + + const lopen = close + 1; + if( text[lopen] != '(' ) return { continue: true, end: lopen }; + const lclose = text.indexOf( ")", lopen ); + if( lclose === -1 ) return { continue: true, end: lopen }; + + const title = text.substring( open + 1, close ); + const link = text.substring( lopen + 1, lclose ); + + if( open > 0 && text[open-1] == "!" ) isImg = 1; + return { title, link, start: open - (isImg?1:0), end: lclose + 1, img: isImg }; +} + +function parseLinks( text ) { + let start = 0; + const links = []; + + while( true ) { + const res = parseLink( text, start ); + if( !res ) + break; + + if( res.continue ) { + start = res.end + 1; + continue; + } + + links.push( res ); + start = res.end + 1; + } + + return links; +} + +function replaceLinks( text, links ) { + let offset = 0; + for( let link of links ) { + const start = link.start + offset; + const end = link.end + offset; + + const textFirst = text.slice( 0, start ); + const textSecond = text.slice( end ); + + let textMid = ""; + if( link.img ) { + textMid = `<img src="${link.link}" alt="${link.title}" />`; + } else { + textMid = `<a href="${link.link}">${link.title}</a>`; + } + + text = textFirst + textMid + textSecond; + offset += textMid.length - ( link.end - link.start ); + } + + return text; +} + +function escapeHtml( html ) { + const entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }; + + return String( html ).replace( /[&<>"'`=\/]/g, ( s, i, str ) => { + if( str.slice( i, i + 6 ).toLowerCase() === "<code>" ) return "<"; + if( s === '<' ) { + if( str.slice( i, i + 6 ).toLowerCase() === "<code>" + || str.slice( i, i + 7 ).toLowerCase() === "</code>" ) { + return "<"; + } + } + + if( s === '>' ) { + if( str.slice( i - 5, i + 1 ).toLowerCase() === "<code>" + || str.slice( i - 6, i + 1 ).toLowerCase() === "</code>" ) { + return ">"; + } + } + + if( s === '/' ) { + if( str.slice( i - 1, i + 6 ).toLowerCase() === "</code>" ) { + return "/"; + } + } + + return entityMap[s]; + }); +} + +function replaceCodeTicks( text ) { + let inBlock = 0; + let tick = text.indexOf( "```" ); + while( tick != -1 ) { + if( !inBlock ) { + let second = text.slice( tick + 3 ); + while( isWhiteChar( second[0] ) ) + second = second.slice( 1 ); + text = text.slice( 0, tick ) + "<code>" + second; + tick = text.indexOf( "```", tick ); + } else { + let second = text.slice( tick + 3 ); + while( second.length > 1 && isWhiteChar( second[0] ) && isWhiteChar( second[1] ) ) + second = second.slice( 1 ); + text = text.slice( 0, tick ) + "</code>" + second; + tick = text.indexOf( "```", tick ); + } + inBlock = !inBlock; + } + + return escapeHtml( text ); +} + +function runHighlight() { + if( hljs === undefined || !hljs ) + return setTimeout( runHighlight, 200 ); + + hljs.highlightAll(); +} + + +function createReadme( text, isMd ) { + const tr = document.createElement( "tr" ); + const td = document.createElement( "td" ); + const pre = document.createElement( "pre" ); + tr.appendChild( td ); + td.appendChild( pre ); + td.setAttribute( "colspan", "4" ); + if( !isMd ) + pre.innerHTML = text; + else { + const noTicks = replaceCodeTicks( text ); + const links = parseLinks( noTicks ); + pre.innerHTML = replaceLinks( noTicks, links ); + } + + pre.style.whiteSpace = "pre-wrap"; + pre.style.fontFamily = "JPN12"; + pre.style.padding = 0; + pre.style.marginTop = "0px"; + pre.style.marginBottom = "0px"; + tr.classList.add( "nohover" ); + tr.style.backgroundColor = "#808080" + const list = document.querySelector( ".list" ); + const tbody = list.querySelector( "tbody" ); + + let insertTarget = tbody.children[3]; + for( let i = 2; i < tbody.children.length; ++i ) { + if( tbody.children[i-1].classList.contains( "nohover" ) ) { + insertTarget = tbody.children[i]; + break; + } + } + + tbody.insertBefore( tr, insertTarget ); + + const th = document.createElement( "th" ); + th.classList.add( "left" ); + th.setAttribute( "colspan", "4" ); + th.innerHTML = "Readme"; + tbody.insertBefore( th, tr ); + + //setTimeout( () => { setTimeout( runHighlight ) } ); +} + +function isCodeView( repo ) { + return location.pathname.includes( `/${repo}/tree` ); +} + +const main = async () => { + const mainEl = document.querySelector( ".main" ); + if( !mainEl ) + return; + + const repoEl = mainEl.querySelectorAll( "a" )[1]; + if( !repoEl ) + return; + + const repo = repoPathFromLink( repoEl ); + if( !repo ) + return; + + if( isCodeView( repo ) ) + return setTimeout( runHighlight ); + + if( !isIndex( repo ) ) + return; + + const branch = branchFromForm(); + const { file, isMd } = await getReadmeFile( repo, branch ); + createReadme( file, isMd ); +} + +setTimeout( main ); +</script> +<link rel="stylesheet" href="https://networkheaven.net/static/highlight.css" type="text/css" /> |
