1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-02-01 19:14:35 +02:00

Fix #1928. Use draft-js-live-markdown-plugin. (#1942)

* Fix #1928. Use draft-js-live-markdown-plugin.

* Ported markdown plugin to TS and checked in directly

* Fix type

* Fix types
This commit is contained in:
Chen-I Lim 2021-12-15 13:23:30 -08:00 committed by GitHub
parent 2e4d015586
commit a8b7a6a556
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 771 additions and 14 deletions

View File

@ -0,0 +1,102 @@
// 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

View File

@ -0,0 +1,69 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Modifier, SelectionState} from 'draft-js'
import {BlockStrategy} from '../pluginStrategy'
import findRangesWithRegex from '../utils/findRangesWithRegex'
const createHeadingBlockStrategy = (): BlockStrategy => {
const HEADING_REGEX = /(^#{1,6})\s(.*)/gm
const HEADING_LEVELS = [
'header-one',
'header-two',
'header-three',
'header-four',
'header-five',
'header-six',
]
return {
type: 'heading',
className: 'heading-block',
mapBlockType: (contentState) => {
// Takes a ContentState and returns a ContentState with heading content block
// type applied
const blockMap = contentState.getBlockMap()
let newContentState = contentState
// Find all heading blocks
blockMap.forEach((block, blockKey) => {
if (!block || !blockKey) {
return
}
const text = block.getText()
const headingBlockDelimiterRanges = findRangesWithRegex(
text,
HEADING_REGEX,
)
let headingLevel = 1
// Determine what heading level it should be
if (headingBlockDelimiterRanges.length > 0) {
headingLevel = (text.match(/#/g) || []).length
}
// Apply the corresponding heading block type
if (headingBlockDelimiterRanges.length > 0) {
newContentState = Modifier.setBlockType(
newContentState,
SelectionState.createEmpty(blockKey),
HEADING_LEVELS[headingLevel - 1],
)
} else if (HEADING_LEVELS.includes(newContentState.getBlockForKey(blockKey).getType())) {
// Remove any existing heading block type if there shouldn't be one
newContentState = Modifier.setBlockType(
newContentState,
SelectionState.createEmpty(blockKey),
'unstyled',
)
}
})
return newContentState
},
}
}
export default createHeadingBlockStrategy

View File

@ -0,0 +1,48 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {InlineStrategy} from '../pluginStrategy'
import findRangesWithRegex from '../utils/findRangesWithRegex'
// Bold can be delimited by: **, __, ***, and ___
const createBoldStyleStrategy = (): InlineStrategy => {
const asteriskDelimitedRegex =
'(\\*\\*\\*)(.+?)(\\*\\*\\*)|(\\*\\*)(.+?)(\\*\\*)(?!\\*)'
const underscoreDelimitedRegex = '(___)(.+?)(___)|(__)(.+?)(__)(?!_)'
const boldRegex = new RegExp(
`${asteriskDelimitedRegex}|${underscoreDelimitedRegex}`,
'g',
)
const boldDelimiterRegex = /^(\*\*\*|\*\*|___|__)|(\*\*\*|\*\*|___|__)$/g
return {
style: 'BOLD',
delimiterStyle: 'BOLD-DELIMITER',
findStyleRanges: (block) => {
// Return an array of arrays containing start and end indices for ranges of
// text that should be bolded
// e.g. [[0,6], [10,20]]
const text = block.getText()
const boldRanges = findRangesWithRegex(text, boldRegex)
return boldRanges
},
findDelimiterRanges: (block, styleRanges) => {
// Find ranges for delimiters at the beginning/end of styled text ranges
// Returns an array of arrays containing start and end indices for delimiters
const text = block.getText()
let boldDelimiterRanges: number[][] = []
styleRanges.forEach((styleRange) => {
const delimiterRange = findRangesWithRegex(
text.substring(styleRange[0], styleRange[1] + 1),
boldDelimiterRegex,
).map((indices) => indices.map((x) => x + styleRange[0]))
boldDelimiterRanges = boldDelimiterRanges.concat(delimiterRange)
})
return boldDelimiterRanges
},
delimiterStyles: {
opacity: 0.4,
},
}
}
export default createBoldStyleStrategy

View File

@ -0,0 +1,30 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {InlineStrategy} from '../pluginStrategy'
import findRangesWithRegex from '../utils/findRangesWithRegex'
const createHeadingDelimiterStyleStrategy = (): InlineStrategy => {
const headingDelimiterRegex = /(^#{1,6})\s/g
return {
style: 'HEADING-DELIMITER',
findStyleRanges: (block) => {
// Skip the text search if the block isn't a header block
if (block.getType().indexOf('header') < 0) {
return []
}
const text = block.getText()
const headingDelimiterRanges = findRangesWithRegex(
text,
headingDelimiterRegex,
)
return headingDelimiterRanges
},
styles: {
opacity: 0.4,
},
}
}
export default createHeadingDelimiterStyleStrategy

View File

@ -0,0 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {InlineStrategy} from '../pluginStrategy'
import findRangesWithRegex from '../utils/findRangesWithRegex'
const createInlineCodeStyleStrategy = (): InlineStrategy => {
const codeRegex = /(`)([^\n\r`]+?)(`)/g
return {
style: 'INLINE-CODE',
findStyleRanges: (block) => {
// Don't allow inline code inside of code blocks
if (block.getType() === 'code-block') {
return []
}
const text = block.getText()
const codeRanges = findRangesWithRegex(text, codeRegex)
return codeRanges
},
styles: {
fontFamily: 'monospace',
},
}
}
export default createInlineCodeStyleStrategy

View File

@ -0,0 +1,52 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {InlineStrategy} from '../pluginStrategy'
import findRangesWithRegex from '../utils/findRangesWithRegex'
const createItalicStyleStrategy = (): InlineStrategy => {
const asteriskDelimitedRegex = '(?<!\\*)(\\*)(?!\\*)(.+?)(?<!\\*)\\*(?!\\*)' // *italic*
const underscoreDelimitedRegex = '(?<!_)(_)(?!_)(.+?)(?<!_)_(?!_)' // _italic_
const strongEmphasisRegex = '(\\*\\*\\*|___)(.+?)(\\*\\*\\*|___)' // ***bolditalic*** ___bolditalic___
const boldWrappedAsteriskRegex =
'(?<=\\*\\*)(\\*)(?!\\*)(.*?[^\\*]+)(?<!\\*)\\*(?![^\\*]\\*)|(?<!\\*)(\\*)(?!\\*)(.*?[^\\*]+)(?<!\\*)\\*(?=\\*\\*)' // ***italic* and bold** **bold and *italic***
const boldWrappedUnderscoreRegex =
'(?<=__)(_)(?!_)(.*?[^_]+)(?<!_)_(?![^_]_)|(?<!_)(_)(?!_)(.*?[^_]+)(?<!_)_(?=__)' // ___italic_ and bold__ __bold and _italic___
const italicRegex = new RegExp(
`${asteriskDelimitedRegex}|${underscoreDelimitedRegex}|${strongEmphasisRegex}|${boldWrappedAsteriskRegex}|${boldWrappedUnderscoreRegex}`,
'g',
)
const italicDelimiterRegex = /^(\*\*\*|\*|___|_)|(\*\*\*|\*|___|_)$/g
return {
style: 'ITALIC',
delimiterStyle: 'ITALIC-DELIMITER',
findStyleRanges: (block) => {
// Return an array of arrays containing start and end indices for ranges of
// text that should be italicized
// e.g. [[0,6], [10,20]]
const text = block.getText()
const italicRanges = findRangesWithRegex(text, italicRegex)
return italicRanges
},
findDelimiterRanges: (block, styleRanges) => {
// Find ranges for delimiters at the beginning/end of styled text ranges
// Returns an array of arrays containing start and end indices for delimiters
const text = block.getText()
let italicDelimiterRanges: number[][] = []
styleRanges.forEach((styleRange) => {
const delimiterRange = findRangesWithRegex(
text.substring(styleRange[0], styleRange[1] + 1),
italicDelimiterRegex,
).map((indices) => indices.map((x) => x + styleRange[0]))
italicDelimiterRanges = italicDelimiterRanges.concat(delimiterRange)
})
return italicDelimiterRanges
},
delimiterStyles: {
opacity: 0.4,
},
}
}
export default createItalicStyleStrategy

View File

@ -0,0 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {InlineStrategy} from '../pluginStrategy'
import findRangesWithRegex from '../utils/findRangesWithRegex'
const createOLDelimiterStyleStrategy = (): InlineStrategy => {
const olDelimiterRegex = /^\d{1,3}\. /g
return {
style: 'OL-DELIMITER',
findStyleRanges: (block) => {
const text = block.getText()
const olDelimiterRanges = findRangesWithRegex(text, olDelimiterRegex)
return olDelimiterRanges
},
styles: {
fontWeight: 'bold',
},
}
}
export default createOLDelimiterStyleStrategy

View File

@ -0,0 +1,39 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {InlineStrategy} from '../pluginStrategy'
import findRangesWithRegex from '../utils/findRangesWithRegex'
const createQuoteStyleStrategy = (): InlineStrategy => {
const quoteRegex = /^> (.*)/g
const quoteDelimiterRegex = /^> /g
return {
style: 'QUOTE',
delimiterStyle: 'QUOTE-DELIMITER',
findStyleRanges: (block) => {
const text = block.getText()
const quoteRanges = findRangesWithRegex(text, quoteRegex)
return quoteRanges
},
findDelimiterRanges: (block, styleRanges) => {
const text = block.getText()
let quoteDelimiterRanges: number[][] = []
styleRanges.forEach((styleRange) => {
const delimiterRange = findRangesWithRegex(
text.substring(styleRange[0], styleRange[1] + 1),
quoteDelimiterRegex,
).map((indices) => indices.map((x) => x + styleRange[0]))
quoteDelimiterRanges = quoteDelimiterRanges.concat(delimiterRange)
})
return quoteDelimiterRanges
},
styles: {
opacity: 0.75,
},
delimiterStyles: {
opacity: 0.4,
},
}
}
export default createQuoteStyleStrategy

View File

@ -0,0 +1,47 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {InlineStrategy} from '../pluginStrategy'
import findRangesWithRegex from '../utils/findRangesWithRegex'
const createStrikethroughStyleStrategy = (): InlineStrategy => {
const strikethroughRegex = /(~~)(.+?)(~~)/g
const strikethroughDelimiterRegex = /^(~~)|(~~)$/g
return {
style: 'STRIKETHROUGH',
delimiterStyle: 'STRIKETHROUGH-DELIMITER',
findStyleRanges: (block) => {
// Return an array of arrays containing start and end indices for ranges of
// text that should be crossed out
// e.g. [[0,6], [10,20]]
const text = block.getText()
const strikethroughRanges = findRangesWithRegex(text, strikethroughRegex)
return strikethroughRanges
},
findDelimiterRanges: (block, styleRanges) => {
// Find ranges for delimiters at the beginning/end of styled text ranges
// Returns an array of arrays containing start and end indices for delimiters
const text = block.getText()
let strikethroughDelimiterRanges: number[][] = []
styleRanges.forEach((styleRange) => {
const delimiterRange = findRangesWithRegex(
text.substring(styleRange[0], styleRange[1] + 1),
strikethroughDelimiterRegex,
).map((indices) => indices.map((x) => x + styleRange[0]))
strikethroughDelimiterRanges = strikethroughDelimiterRanges.concat(
delimiterRange,
)
})
return strikethroughDelimiterRanges
},
styles: {
textDecoration: 'line-through',
},
delimiterStyles: {
opacity: 0.4,
textDecoration: 'none',
},
}
}
export default createStrikethroughStyleStrategy

View File

@ -0,0 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {InlineStrategy} from '../pluginStrategy'
import findRangesWithRegex from '../utils/findRangesWithRegex'
const createULDelimiterStyleStrategy = (): InlineStrategy => {
const ulDelimiterRegex = /^\* /g
return {
style: 'UL-DELIMITER',
findStyleRanges: (block) => {
const text = block.getText()
const ulDelimiterRanges = findRangesWithRegex(text, ulDelimiterRegex)
return ulDelimiterRanges
},
styles: {
fontWeight: 'bold',
},
}
}
export default createULDelimiterStyleStrategy

View File

@ -0,0 +1,263 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {
EditorState,
CharacterMetadata,
ContentState,
ContentBlock,
EditorChangeType,
DraftStyleMap,
} from 'draft-js'
import {EditorPlugin} from '@draft-js-plugins/editor'
import {Repeat, List} from 'immutable'
// Inline style handlers
import createBoldStyleStrategy from './inline-styles/boldStyleStrategy'
import createItalicStyleStrategy from './inline-styles/italicStyleStrategy'
import createStrikethroughStyleStrategy from './inline-styles/strikethroughStyleStrategy'
import createHeadingDelimiterStyleStrategy from './inline-styles/headingDelimiterStyleStrategy'
import createULDelimiterStyleStrategy from './inline-styles/ulDelimiterStyleStrategy'
import createOLDelimiterStyleStrategy from './inline-styles/olDelimiterStyleStrategy'
import createQuoteStyleStrategy from './inline-styles/quoteStyleStrategy'
import createInlineCodeStyleStrategy from './inline-styles/inlineCodeStyleStrategy'
// Block type handlers
import createCodeBlockStrategy from './block-types/codeBlockStrategy'
import createHeadingBlockStrategy from './block-types/headingBlockStrategy'
import {BlockStrategy, InlineStrategy} from './pluginStrategy'
export interface LiveMarkdownPluginConfig {
inlineStyleStrategies?: InlineStrategy[],
blockTypeStrategies?: BlockStrategy[],
}
function createLiveMarkdownPlugin(config: LiveMarkdownPluginConfig = {}): EditorPlugin {
const {
inlineStyleStrategies = [
createBoldStyleStrategy(),
createItalicStyleStrategy(),
createStrikethroughStyleStrategy(),
createHeadingDelimiterStyleStrategy(),
createULDelimiterStyleStrategy(),
createOLDelimiterStyleStrategy(),
createQuoteStyleStrategy(),
createInlineCodeStyleStrategy(),
],
blockTypeStrategies = [
createCodeBlockStrategy(),
createHeadingBlockStrategy(),
],
} = config
// Construct the editor style map from our inline style strategies
const customStyleMap: DraftStyleMap = {}
inlineStyleStrategies.forEach((styleStrategy) => {
if (styleStrategy.style && styleStrategy.styles) {
customStyleMap[styleStrategy.style] = styleStrategy.styles
}
if (styleStrategy.delimiterStyle && styleStrategy.delimiterStyles) {
customStyleMap[styleStrategy.delimiterStyle] =
styleStrategy.delimiterStyles
}
})
// Construct the block style fn
const blockStyleMap = blockTypeStrategies.reduce((map: Record<string, string>, blockStrategy) => {
map[blockStrategy.type] = blockStrategy.className
return map
}, {})
const blockStyleFn = (block: ContentBlock) => {
const blockType = block.getType()
return blockStyleMap[blockType]
}
return {
// We must handle the maintenance of block types and inline styles on changes.
// To make sure the code is efficient we only perform maintenance on content
// blocks that have been changed. We only perform maintenance for change types
// that result in actual text changes (ignore cursing through text, etc).
onChange: (editorState) => {
// if (editorState.getLastChangeType() === 'insert-fragment')
// return maintainWholeEditorState();
return maintainEditorState(
editorState,
blockTypeStrategies,
inlineStyleStrategies,
)
},
customStyleMap,
blockStyleFn,
}
}
// Takes an EditorState and returns a ContentState updated with block types and
// inline styles according to the provided strategies
// Takes a targeted approach that only updates the modified block/blocks
const maintainEditorState = (
editorState: EditorState,
blockTypeStrategies: BlockStrategy[],
inlineStyleStrategies: InlineStrategy[],
) => {
// Bypass maintenance if text was not changed
const lastChangeType = editorState.getLastChangeType()
const bypassOnChangeTypes = [
'adjust-depth',
'apply-entity',
'change-block-data',
'change-block-type',
'change-inline-style',
'maintain-markdown',
]
if (bypassOnChangeTypes.includes(lastChangeType)) {
return editorState
}
// Maintain block types then inline styles
// Order is important bc we want the inline style strategies to be able to
// look at block type to avoid unnecessary regex searching when possible
const contentState = editorState.getCurrentContent()
let newContentState = maintainBlockTypes(contentState, blockTypeStrategies)
newContentState = maintainInlineStyles(
newContentState,
editorState,
inlineStyleStrategies,
)
// Apply the updated content state
let newEditorState = editorState
if (contentState !== newContentState) {
newEditorState = EditorState.push(
editorState,
newContentState,
'maintain-markdown' as EditorChangeType,
)
}
newEditorState = EditorState.forceSelection(
newEditorState,
editorState.getSelection(),
)
return newEditorState
}
// Takes a ContentState and returns a ContentState with block types and inline styles
// applied or removed as necessary
const maintainBlockTypes = (contentState: ContentState, blockTypeStrategies: BlockStrategy[]) => {
return blockTypeStrategies.reduce((cs, blockTypeStrategy) => {
return blockTypeStrategy.mapBlockType(cs)
}, contentState)
}
// Takes a ContentState (and EditorState for getting the selection and change type)
// and returns a ContentState with inline styles applied or removed as necessary
const maintainInlineStyles = (
contentState: ContentState,
editorState: EditorState,
inlineStyleStrategies: InlineStrategy[],
): ContentState => {
const lastChangeType = editorState.getLastChangeType()
const selection = editorState.getSelection()
const blockKey = selection.getStartKey()
const block = contentState.getBlockForKey(blockKey)
const blockMap = contentState.getBlockMap()
let newBlockMap = blockMap
// If text has been pasted (potentially modifying/creating multiple blocks) or
// the editor is new we must maintain the styles for all content blocks
if (lastChangeType === 'insert-fragment' || !lastChangeType) {
blockMap.forEach((b, k) => {
if (!b || !k) {
return
}
const newBlock = mapInlineStyles(b, inlineStyleStrategies) as ContentBlock
newBlockMap = newBlockMap.set(k, newBlock)
})
} else {
const newBlock = mapInlineStyles(block, inlineStyleStrategies) as ContentBlock
newBlockMap = newBlockMap.set(blockKey, newBlock)
}
// If enter was pressed (or the block was otherwise split) we must maintain
// styles in the previous block as well
if (lastChangeType === 'split-block') {
const newPrevBlock = mapInlineStyles(
contentState.getBlockBefore(blockKey)!,
inlineStyleStrategies,
) as ContentBlock
newBlockMap = newBlockMap.set(
contentState.getKeyBefore(blockKey),
newPrevBlock,
)
}
const newContentState = contentState.merge({
blockMap: newBlockMap,
}) as ContentState
return newContentState
}
// Maps inline styles to the provided ContentBlock's CharacterMetadata list based
// on the plugin's inline style strategies
const mapInlineStyles = (block: ContentBlock, strategies: InlineStrategy[]) => {
// This will be called upon any change that has the potential to effect the styles
// of a content block.
// Find all of the ranges that should have styles applied to them (i.e. all bold,
// italic, or strikethrough delimited ranges of the block).
const blockText = block.getText()
// Create a list of empty CharacterMetadata to map styles to
// eslint-disable-next-line new-cap
let characterMetadataList = List(
// eslint-disable-next-line new-cap
Repeat(CharacterMetadata.create(), blockText.length),
)
// Evaluate block text with each style strategy and apply styles to matching
// ranges of text and delimiters
strategies.forEach((strategy) => {
const styleRanges = strategy.findStyleRanges(block)
const delimiterRanges = strategy.findDelimiterRanges ? strategy.findDelimiterRanges(block, styleRanges) : []
characterMetadataList = applyStyleRangesToCharacterMetadata(
strategy.style,
styleRanges,
characterMetadataList,
)
characterMetadataList = applyStyleRangesToCharacterMetadata(
strategy.delimiterStyle,
delimiterRanges,
characterMetadataList,
)
})
// Apply the list of CharacterMetadata to the content block
return block.set('characterList', characterMetadataList)
}
// Applies the provided style to the corresponding ranges of the character metadata
const applyStyleRangesToCharacterMetadata = (
style: string | undefined,
ranges: number[][],
characterMetadataList: List<CharacterMetadata>,
) => {
let styledCharacterMetadataList = characterMetadataList
if (!style) {
return styledCharacterMetadataList
}
ranges.forEach((range) => {
for (let i = range[0]; i <= range[1]; i++) {
const styled = CharacterMetadata.applyStyle(
characterMetadataList.get(i),
style,
)
styledCharacterMetadataList = styledCharacterMetadataList.set(i, styled)
}
})
return styledCharacterMetadataList
}
export default createLiveMarkdownPlugin

View File

@ -0,0 +1,20 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import * as React from 'react'
import {ContentBlock, ContentState} from 'draft-js'
export interface InlineStrategy {
style: string,
findStyleRanges: (text: ContentBlock) => number[][],
findDelimiterRanges?: (text: ContentBlock, styleRanges: number[][]) => number[][],
delimiterStyle?: string,
styles?: React.CSSProperties,
delimiterStyles?: React.CSSProperties,
}
export interface BlockStrategy {
type: string,
className: string,
mapBlockType: (state: ContentState) => ContentState
}

View File

@ -0,0 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
const findRangesWithRegex = (text: string, regex: RegExp): number[][] => {
const ranges: number[][] = []
let matches
do {
matches = regex.exec(text)
if (matches) {
ranges.push([matches.index, (matches.index + matches[0].length) - 1])
}
} while (matches)
return ranges
}
export default findRangesWithRegex

View File

@ -1,28 +1,25 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {
ReactElement,
useEffect,
useMemo,
useCallback,
useRef,
useState,
} from 'react'
import {getDefaultKeyBinding, EditorState, ContentState, DraftHandleValue} from 'draft-js'
import Editor from '@draft-js-plugins/editor'
import createEmojiPlugin from '@draft-js-plugins/emoji'
import '@draft-js-plugins/emoji/lib/plugin.css'
import createMentionPlugin, {
defaultSuggestionsFilter,
MentionData,
} from '@draft-js-plugins/mention'
import '@draft-js-plugins/mention/lib/plugin.css'
import './markdownEditorInput.scss'
import {ContentState, DraftHandleValue, EditorState, getDefaultKeyBinding} from 'draft-js'
import React, {
ReactElement, useCallback, useEffect,
useMemo, useRef,
useState,
} from 'react'
import createEmojiPlugin from '@draft-js-plugins/emoji'
import '@draft-js-plugins/emoji/lib/plugin.css'
import {getWorkspaceUsersList} from '../../store/users'
import {useAppSelector} from '../../store/hooks'
import {getWorkspaceUsersList} from '../../store/users'
import {IUser} from '../../user'
import createLiveMarkdownPlugin from '../live-markdown-plugin/liveMarkdownPlugin'
import './markdownEditorInput.scss'
import Entry from './entryComponent/entryComponent'
@ -60,6 +57,7 @@ const MarkdownEditorInput = (props: Props): ReactElement => {
const {MentionSuggestions, plugins, EmojiSuggestions} = useMemo(() => {
const mentionPlugin = createMentionPlugin({mentionPrefix: '@'})
const emojiPlugin = createEmojiPlugin()
const markdownPlugin = createLiveMarkdownPlugin()
// eslint-disable-next-line no-shadow
const {EmojiSuggestions} = emojiPlugin
@ -69,6 +67,7 @@ const MarkdownEditorInput = (props: Props): ReactElement => {
const plugins = [
mentionPlugin,
emojiPlugin,
markdownPlugin,
]
return {plugins, MentionSuggestions, EmojiSuggestions}
}, [])