/// // Import necessary libraries import { marked } from 'marked'; import kebabCase from 'lodash/kebabCase'; const { extractStars } = require('../helper/extract-stars.function.js'); const IsBot = require('../app/modules/web/util/is-bot.js'); import hljs from 'highlight.js/lib/core'; // Import languages using standard import syntax import nginx from 'highlight.js/lib/languages/nginx'; import xml from 'highlight.js/lib/languages/xml'; import css from 'highlight.js/lib/languages/css'; import scss from 'highlight.js/lib/languages/scss'; import yaml from 'highlight.js/lib/languages/yaml'; import powershell from 'highlight.js/lib/languages/powershell'; import javascript from 'highlight.js/lib/languages/javascript'; import json from 'highlight.js/lib/languages/json'; import shell from 'highlight.js/lib/languages/shell'; import typescript from 'highlight.js/lib/languages/typescript'; import ini from 'highlight.js/lib/languages/ini'; // Register languages with highlight.js hljs.registerLanguage('conf', nginx); hljs.registerLanguage('xml', xml); hljs.registerLanguage('css', css); hljs.registerLanguage('scss', scss); hljs.registerLanguage('yaml', yaml); hljs.registerLanguage('yml', yaml); hljs.registerLanguage('powershell', powershell); hljs.registerLanguage('javascript', javascript); hljs.registerLanguage('js', javascript); hljs.registerLanguage('json', json); hljs.registerLanguage('bash', shell); hljs.registerLanguage('sh', shell); hljs.registerLanguage('typescript', typescript); hljs.registerLanguage('ts', typescript); hljs.registerLanguage('ini', ini); // Initialize the custom renderer const markdownRenderer = new marked.Renderer(); // Initialize counters and variables let codeIndex: number = 0; let currentRepo: any; let settings: any; let currentRepoPath: any; let locationOrigin: any; let locationPathname: any; let locationHref: any; let locationHostname: any; // Helper function to strip HTML tags function htmlStrip(html: string): string { return html.replace(/<\/?[^>]+(>|$)/g, ""); } // Helper function to sanitize strings to prevent XSS const sanitize = (str: any): string => { if (typeof str !== 'string') return ''; return str.replace(/&/g, '&') .replace(/"/g, '"') .replace(//g, '>'); }; // Helper function to notify missing markdown code const notifyMissingMarkdownCode = ({ code, language, currentRepo, currentRepoPath, coreUrl }: any): void => { const url = `${coreUrl}/api/patrikx3/git/notify-markdown-error`; fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ info: ` Repo: ${sanitize(currentRepo)}
Path: ${sanitize(currentRepoPath)}
Code:
${sanitize(code)}

Language: ${sanitize(language)}
` }) }).then((response: any) => { if (response.status !== 200) { console.error('notifyMissingMarkdownCode invalid response', response); } else { console.info('notifyMissingMarkdownCode info response', response); } }).catch((e: any) => { console.error('notifyMissingMarkdownCode error', e); }); }; // Helper function to render images from tokens const renderImages = (tokens: any[]): string => { return tokens.map((childToken: any) => { if (childToken.type === 'image') { const imgTitle = childToken.title ? ` title="${sanitize(childToken.title)}"` : ''; const altText = sanitize(childToken.text); //console.log('childToken', childToken.href) if (childToken.href === 'https://snapcraft.io/static/images/badges/en/snap-store-black.svg') { childToken.href = 'https://cdn.corifeus.com/assets/svg/snap-store-black.svg' } return `${altText}`; } // Handle other child token types if necessary return ''; }).join(''); }; // Custom heading renderer markdownRenderer.heading = (token: any): string => { const text: string = token.text; const level: number = token.depth; const raw: string = token.raw; const ref: string = kebabCase(htmlStrip(raw)).replace(/[^\x00-\xFF]/g, ""); const id: string = `${ref}-parent`; let navClick: string = ''; if (!IsBot()) { navClick = `onclick="return window.coryAppWebPagesNavigateHash('${sanitize(id)}');"`; } const element: string = `${sanitize(text)} `; return element; }; // Custom image renderer markdownRenderer.image = (token: any): string => { let { href, title, text } = token; title = title || ''; text = text || ''; if (!href.startsWith('http')) { href = `https://cdn.corifeus.com/git/${sanitize(currentRepo)}/${sanitize(href)}`; } let result: string; if (text.toLowerCase().trim() === 'link') { result = `LINK`; } else { result = ` ${sanitize(title)} ${sanitize(text)} ${sanitize(text)} `; } return result; }; // Custom link renderer markdownRenderer.link = (token: any): string => { const title: any = token.title; let href: any = token.href; const text: any = token.text; let a: string; let tooltip: string = ''; if (title !== null && title !== undefined) { tooltip = `tooltip="${sanitize(title)}"`; } let fixed: boolean = false; let path: any; const testHref: any = href.toLowerCase(); const fixedUrl = (): void => { try { const url = new URL(href); href = url.pathname; path = `${href}`; fixed = true; } catch (error: any) { console.error('Invalid URL:', href, error); } }; if (typeof testHref === 'string' && (testHref.startsWith('https://') || testHref.startsWith('http://'))) { try { const testUrl = new URL(testHref); for (let defaultDomain of settings.pages.defaultDomain) { if (testUrl.hostname === defaultDomain) { fixedUrl(); break; } } } catch (error: any) { console.error('Invalid testHref URL:', testHref, error); } } else if (testHref.includes('localhost:8080')) { fixedUrl(); } // Detect if the link contains image tokens const hasImage: boolean = token.tokens && token.tokens.some((childToken: any) => childToken.type === 'image'); // Determine if the link is external const isExternal: boolean = ( !href.startsWith(locationOrigin) && (href.startsWith('https://') || href.startsWith('http://') || href.startsWith('mailto:')) ); if (isExternal) { if (href.endsWith('#cory-non-external')) { if (hasImage) { // Render image inside link without external link icon a = `${renderImages(token.tokens)}`; } else { a = `${sanitize(text)}`; } } else { if (hasImage) { // Render image inside link with external link icon a = `${renderImages(token.tokens)} `; } else { a = `${sanitize(text)} `; } } } else { if (!fixed) { if (href.endsWith('.md')) { href = href.substr(0, href.length - 3) + '.html'; } if (href.startsWith(locationOrigin)) { path = `/${href.substring(locationOrigin.length + 1)}`; } else if (href.startsWith('./')) { let base = locationHref; if (!base.includes('.')) { base = locationHref + '/'; } try { path = `${new URL(href, base).pathname}`; } catch (error: any) { console.error('Invalid relative URL:', href, error); path = href; // Fallback to original href } } else { path = `/${sanitize(currentRepo)}/${sanitize(href)}`; } } const navClick: string = !IsBot() ? `onclick="window.coryAppWebPagesNavigate('${sanitize(path)}'); return false;"` : ''; if (hasImage) { // Render image inside link a = `${renderImages(token.tokens)}`; } else { // Render link with text a = `${sanitize(text)}`; } } return a; }; // Custom code block renderer markdownRenderer.code = (token: any): string => { // code, language const code: string = token.text; let language: any = token.lang; if (language === undefined) { language = 'text'; } language = language.toLowerCase(); if ((hljs.getLanguage(language) === 'undefined' || hljs.getLanguage(language) === undefined) && language !== 'text' && language !== 'txt') { console.error(`Please add highlight.js as a language (could be a marked error as well, sometimes it thinks a language): ${language} We are not loading everything, since it is about 500kb`); notifyMissingMarkdownCode({ code, language, currentRepo, currentRepoPath, coreUrl: settings.core.server.url }); } // language = language === 'text' || language === 'txt' || language === undefined ? 'html' : language; const validLang: boolean = !!(language && hljs.getLanguage(language)); const highlighted: string = validLang ? hljs.highlight(code, { language: language as string }).value : sanitize(code); codeIndex++; return `
${highlighted}
`; }; // Custom inline code renderer markdownRenderer.codespan = (token: any): string => { //console.warn('codespan', token) const code: string = token.text; const lang: string = 'html'; const highlighted: string = hljs.highlight(code, { language: lang, }).value; return `${highlighted}`; }; // Customize the rendering of tables markdownRenderer.table = function (token: any): string { console.log('table', token) const header = token.header.map((cell: any) => this.tablecell(cell)).join(''); const rows = token.rows .map((row: any[]) => this.tablerow({ text: row.map((cell: any) => this.tablecell(cell)).join('') })) .join(''); return `${header}${rows}
`; }; // Customize the rendering of table rows markdownRenderer.tablerow = function (token: any): string { return `${token.text}`; }; // Customize the rendering of table cells markdownRenderer.tablecell = function (token: any): string { const tag = token.header ? 'th' : 'td'; const alignment = token.header ? ` style="text-align: ${token.header ? 'center' : 'left'};"` : ''; // Parse markdown content in cells const content = marked.parseInline(token.text); return `<${tag} class="corifeus-marked-table-cell"${alignment}>${content}`; }; // Helper function to extract specific sections from Markdown const extract = (template: any, area: any): string => { // [//]: #@corifeus-header // [//]: #corifeus-header:end // [//]: #@corifeus-footer // [//]: #@corifeus-footer:end const start: string = `[//]: #@${area}`; const end: string = `[//]: #@${area}:end`; const startIndex: number = template.indexOf(start); const endIndex: number = template.indexOf(end); // If start or end markers are not found, return the original template if (startIndex === -1 || endIndex === -1) { return template; } let result: string = template.substring(0, startIndex + start.length); result += template.substring(endIndex + end.length); return result; }; // Helper function to escape regex special characters const RegexpEscape = (s: string): string => { return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); }; // Construct function to process Markdown and convert to HTML const construct = (data: any): string => { currentRepo = data.currentRepo; settings = data.settings; //console.log('data', data) locationOrigin = data.location.origin; currentRepoPath = data.path; locationPathname = data.location.pathname; locationHref = data.location.href; locationHostname = data.location.hostname; let { md, packages, path } = data; md = md.trim(); md = extract(md, 'corifeus-header'); md = extract(md, 'corifeus-footer'); /* md = twemoji.parse(md, { folder: 'svg', ext: '.svg', }) */ let html: string = marked(md, { renderer: markdownRenderer }); html = html.replace(/{/g, '{').replace(/}/g, '}'); html = html.replace(/&/g, '&'); if (currentRepo === 'corifeus' && path === 'index.html') { //console.info('decorated corifeus index.html') for (let pkgName of Object.keys(packages)) { const pkg: any = packages[pkgName]; //console.log(pkgName, pkg.corifeus.stargazers_count) //if (pkg.corifeus.stargazers_count > -1) { // Construct the hidden star placeholder dynamically const hiddenStars: string = `<!--@star|${pkg.name}-->`; let title: string = ''; // Handle large star counts by adding a title if (pkg.corifeus.stargazers_count > 999) { title = pkg.corifeus.stargazers_count.toString(); } let stars: string = ''; if (pkg.corifeus.stargazers_count > 0) { // Construct the star count HTML element stars = ` ${extractStars(pkg.corifeus.stargazers_count)}`; } // Escape the placeholder string so it can be safely used in the regex const re: RegExp = new RegExp(RegexpEscape(hiddenStars), 'g'); // Perform the replacement html = html.replace(re, stars); //} } } return html; }; // Web Worker message handling onmessage = function (e: MessageEvent): void { const data: any = { requestId: e.data.requestId } try { data.html = construct(e.data); data.success = true; } catch (error: any) { console.error(error); data.success = false; data.errorMessage = error.message; } postMessage(data); }