main

mattermost/focalboard

Last updated at: 28/12/2023 01:34

codeBlockStrategy.ts

TLDR

This file contains a function createCodeBlockStrategy that creates a code block strategy for a Markdown editor. The code block strategy applies a custom block type and language metadata to code blocks in the editor.

Methods

createCodeBlockStrategy

The createCodeBlockStrategy function creates a code block strategy for a Markdown editor. A code block strategy is used to apply a custom block type and language metadata to code blocks in the editor. The function takes no arguments and returns a BlockStrategy object.

Classes

None

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ContentBlock, ContentState, Modifier, SelectionState} from 'draft-js'

import {BlockStrategy} from '../pluginStrategy'

import findRangesWithRegex from '../utils/findRangesWithRegex'

const createCodeBlockStrategy = (): BlockStrategy => {
    const blockType = 'code-block'
    const CODE_BLOCK_REGEX = /^```/g

    return {
        type: blockType,
        className: 'code-block',
        mapBlockType: (contentState) => {
            // Takes a ContentState and returns a ContentState with code block content
            // block type applied
            const blockMap = contentState.getBlockMap()
            let newContentState = contentState
            let codeBlockKeys: string[] = []
            let notCodeBlockKeys: string[] = []
            let tempKeys: string[] = []
            let language: string

            // Find all code blocks
            blockMap.forEach((block, blockKey) => {
                if (!block || !blockKey) {
                    return
                }
                const text = block.getText()
                const codeBlockDelimiterRanges = findRangesWithRegex(
                    text,
                    CODE_BLOCK_REGEX,
                )
                const precededByDelimiter = tempKeys.length > 0

                // Parse out the language specified after the delimiter for use with the
                // draft-js-prism-plugin for syntax highlighting
                if (codeBlockDelimiterRanges.length > 0 && !precededByDelimiter) {
                    language = (text.match(/\w+/g) || [])[0] || 'javascript'
                }

                // If we find the opening code block delimiter we must maintain an array
                // of all keys for content blocks that might need to be code blocks if we
                // later find a closing code block delimiter
                if (codeBlockDelimiterRanges.length > 0 || precededByDelimiter) {
                    tempKeys.push(blockKey)
                } else {
                    notCodeBlockKeys.push(blockKey)
                }

                // If we find the closing code block delimiter ``` then store the keys for
                // the sandwiched content blocks
                if (codeBlockDelimiterRanges.length > 0 && precededByDelimiter) {
                    codeBlockKeys = codeBlockKeys.concat(tempKeys)
                    tempKeys = []
                }
            })

            // Loop through keys for blocks that should not have code block type and remove
            // code block type if necessary
            notCodeBlockKeys = notCodeBlockKeys.concat(tempKeys)
            notCodeBlockKeys.forEach((blockKey) => {
                if (newContentState.getBlockForKey(blockKey).getType() === blockType) {
                    newContentState = Modifier.setBlockType(
                        newContentState,
                        SelectionState.createEmpty(blockKey),
                        'unstyled',
                    )
                }
            })

            // Loop through found code block keys and apply the block style and language
            // metadata to the block
            codeBlockKeys.forEach((blockKey, i) => {
                // Apply language metadata to block (ignore delimiter blocks)
                const isDelimiterBlock = i === 0 || i === codeBlockKeys.length - 1
                const block = newContentState.getBlockForKey(blockKey)
                const newBlockMap = newContentState.getBlockMap()
                const data = block.
                    getData().
                    merge({language: isDelimiterBlock ? undefined : language})
                const newBlock = block.merge({data}) as ContentBlock
                newContentState = newContentState.merge({
                    blockMap: newBlockMap.set(blockKey, newBlock),
                }) as ContentState

                // Apply block type to block
                newContentState = Modifier.setBlockType(
                    newContentState,
                    SelectionState.createEmpty(blockKey),
                    blockType,
                )
            })

            return newContentState
        },
    }
}

export default createCodeBlockStrategy