hedgedoc/src/components/editor-page/editor-pane/autocompletion/code-block.ts
Tilman Vatteroth 87d6285da5
Fix security related problems (#1522)
* Remove unnecessary capture group from regex

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Rename component to make name more expressive

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Remove redundant expression

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Filter vbscript links

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Remove superfluous parameter

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Check if handler is set

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>

* Fix doc

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
2021-10-01 22:51:57 +02:00

114 lines
3.1 KiB
TypeScript

/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Editor, Hint, Hints, Pos } from 'codemirror'
import { findWordAtCursor, generateHintListByPrefix, Hinter } from './index'
import { showErrorNotification } from '../../../../redux/ui-notifications/methods'
import { Logger } from '../../../../utils/logger'
type highlightJsImport = typeof import('../../../common/hljs/hljs')
const log = new Logger('Autocompletion > CodeBlock')
const wordRegExp = /^```((?:\w|-|_|\+)*)$/
let allSupportedLanguages: string[] = []
/**
* Fetches the highlight js chunk.
* @return the retrieved highlight js api
*/
const loadHighlightJs = async (): Promise<highlightJsImport | null> => {
try {
return await import('../../../common/hljs/hljs')
} catch (error) {
showErrorNotification('common.errorWhileLoadingLibrary', { name: 'highlight.js' })(error as Error)
log.error('Error while loading highlight.js', error)
return null
}
}
/**
* Extracts the language from the current line in the editor.
*
* @param editor The editor that contains the search time
* @return null if no search term could be found or the found word and the cursor position.
*/
const extractSearchTerm = (
editor: Editor
): null | {
searchTerm: string
startIndex: number
endIndex: number
} => {
const searchTerm = findWordAtCursor(editor)
const searchResult = wordRegExp.exec(searchTerm.text)
if (searchResult === null) {
return null
}
return {
searchTerm: searchResult[1],
startIndex: searchTerm.start,
endIndex: searchTerm.end
}
}
/**
* Builds the list of languages that are supported by highlight js or custom embeddings.
* @return An array of language names
*/
const buildLanguageList = async (): Promise<string[]> => {
const highlightJs = await loadHighlightJs()
if (highlightJs === null) {
return []
}
if (allSupportedLanguages.length === 0) {
allSupportedLanguages = highlightJs.default
.listLanguages()
.concat('csv', 'flow', 'html', 'js', 'markmap', 'abc', 'graphviz', 'mermaid', 'vega-lite')
}
return allSupportedLanguages
}
/**
* Creates a codemirror autocompletion hint with supported highlight js languages.
*
* @param editor The codemirror editor that requested the autocompletion
* @return The generated {@link Hints} or null if no hints exist.
*/
const codeBlockHint = async (editor: Editor): Promise<Hints | null> => {
const searchResult = extractSearchTerm(editor)
if (!searchResult) {
return null
}
const languages = await buildLanguageList()
if (languages.length === 0) {
return null
}
const suggestions = generateHintListByPrefix(searchResult.searchTerm, languages)
if (!suggestions) {
return null
}
const lineIndex = editor.getCursor().line
return {
list: suggestions.map(
(suggestion: string): Hint => ({
text: '```' + suggestion + '\n\n```\n',
displayText: suggestion
})
),
from: Pos(lineIndex, searchResult.startIndex),
to: Pos(lineIndex, searchResult.endIndex)
}
}
export const CodeBlockHinter: Hinter = {
wordRegExp,
hint: codeBlockHint
}