diff --git a/app/assets/javascripts/highlight_js/index.js b/app/assets/javascripts/highlight_js/index.js new file mode 100644 index 0000000000000000000000000000000000000000..aa66c721b9ab69cfde8ef632f27b0312df86bd9c --- /dev/null +++ b/app/assets/javascripts/highlight_js/index.js @@ -0,0 +1,55 @@ +import hljsCore from 'highlight.js/lib/core'; +import languageLoader from '~/content_editor/services/highlight_js_language_loader'; +import { ROUGE_TO_HLJS_LANGUAGE_MAP } from '~/vue_shared/components/source_viewer/constants'; +import { registerPlugins } from './plugins/index'; + +const loadLanguage = async (language, hljs) => { + const languageDefinition = await languageLoader[language](); + hljs.registerLanguage(language, languageDefinition.default); +}; + +const loadSubLanguages = async (languageDefinition, hljs) => { + // Some files can contain sub-languages (i.e., Svelte); this ensures that sub-languages are also loaded + if (!languageDefinition?.contains) return; + + // generate list of languages to load + const languages = new Set( + languageDefinition.contains + .filter((component) => Boolean(component.subLanguage)) + .map((component) => component.subLanguage), + ); + + if (languageDefinition.subLanguage) { + languages.add(languageDefinition.subLanguage); + } + + await Promise.all([...languages].map((language) => loadLanguage(language, hljs))); +}; + +const registerLanguage = async (hljs, language) => { + await loadLanguage(language, hljs); + await loadSubLanguages(hljs.getLanguage(language), hljs); +}; + +const initHighlightJs = async (language, plugins) => { + const hljs = hljsCore.newInstance(); + + registerPlugins(hljs, plugins); + await registerLanguage(hljs, language); + + return hljs; +}; + +const highlightContent = async (lang, rawContent, plugins) => { + const language = ROUGE_TO_HLJS_LANGUAGE_MAP[lang.toLowerCase()]; + let highlightedContent; + + if (language) { + const hljs = await initHighlightJs(language, plugins); + highlightedContent = hljs.highlight(rawContent, { language }).value; + } + + return highlightedContent; +}; + +export { highlightContent }; diff --git a/app/assets/javascripts/highlight_js/plugins/index.js b/app/assets/javascripts/highlight_js/plugins/index.js new file mode 100644 index 0000000000000000000000000000000000000000..43309611e8d5c1300d1469f89fdac4e66c6895e4 --- /dev/null +++ b/app/assets/javascripts/highlight_js/plugins/index.js @@ -0,0 +1,29 @@ +import wrapChildNodes from '~/vue_shared/components/source_viewer/plugins/wrap_child_nodes'; +import wrapBidiChars from '~/vue_shared/components/source_viewer/plugins/wrap_bidi_chars'; +import linkDependencies from '~/vue_shared/components/source_viewer/plugins/link_dependencies'; +import wrapLines from '~/vue_shared/components/source_viewer/plugins/wrap_lines'; + +const HLJS_ON_AFTER_HIGHLIGHT = 'after:highlight'; + +// Registers our plugins for Highlight.js +// Plugin API: https://github.com/highlightjs/highlight.js/blob/main/docs/plugin-api.rst +export const highlightPlugins = (fileType, rawContent, shouldWrapLines) => { + const plugins = [ + wrapChildNodes, + wrapBidiChars, + (result) => linkDependencies(result, fileType, rawContent), + ]; + if (shouldWrapLines) { + plugins.push(wrapLines); + } + return plugins; +}; + +export const registerPlugins = (hljs, plugins) => { + if (!plugins) { + return; + } + for (const plugin of plugins) { + hljs.addPlugin({ [HLJS_ON_AFTER_HIGHLIGHT]: plugin }); + } +}; diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/index.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/index.js deleted file mode 100644 index 3f8a9258fc3304185d9e7cab6a4070b51fc8a257..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import wrapChildNodes from './wrap_child_nodes'; -import linkDependencies from './link_dependencies'; -import wrapBidiChars from './wrap_bidi_chars'; -import wrapLines from './wrap_lines'; - -export const HLJS_ON_AFTER_HIGHLIGHT = 'after:highlight'; - -/** - * Registers our plugins for Highlight.js - * - * Plugin API: https://github.com/highlightjs/highlight.js/blob/main/docs/plugin-api.rst - * - * @param {Object} hljs - the Highlight.js instance. - */ -export const registerPlugins = (hljs, fileType, rawContent, shouldWrapLines) => { - hljs.addPlugin({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapChildNodes }); - hljs.addPlugin({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapBidiChars }); - hljs.addPlugin({ - [HLJS_ON_AFTER_HIGHLIGHT]: (result) => linkDependencies(result, fileType, rawContent), - }); - if (shouldWrapLines) hljs.addPlugin({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapLines }); -}; diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/mark_multiple_lines.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/mark_multiple_lines.js new file mode 100644 index 0000000000000000000000000000000000000000..055efc8d077bc43010a219cab442223542d347d6 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/mark_multiple_lines.js @@ -0,0 +1,41 @@ +/** + * Highlight.js plugin for wrapping lines in the correct classes and attributes. + * Needed to highlight a large amount of lines + * + * Plugin API: https://github.com/highlightjs/highlight.js/blob/main/docs/plugin-api.rst + * + * @param {String} content - represent highlighted line of code + * @param {number} lineNum - represent highlighted line number + * @param {Object} markLineInfo - highlighting info for the current line + */ +function markLinesWithDiv(content, lineNum, markLineInfo) { + let wrappedLine = content; + + const stepNumberSpan = + lineNum === markLineInfo?.startLine + ? `${markLineInfo.index + 1}` + : ''; + const stepNumberSpanNone = + lineNum !== markLineInfo?.startLine && markLineInfo + ? `${markLineInfo.index + 1}` + : ''; + + if (markLineInfo) { + const contentStartIndex = content.indexOf(content.trimStart()); + wrappedLine = `${content.slice( + 0, + contentStartIndex, + )}${stepNumberSpanNone}${stepNumberSpan}${content.slice(contentStartIndex)}`; + } + return `
+
+