mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-06-04 16:54:11 -04:00
Upgrade to CodeMirror 6 (#1787)
Upgrade to CodeMirror 6 Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
1a09bfa5f1
commit
6a6f6105b9
103 changed files with 1906 additions and 2615 deletions
|
@ -36,19 +36,15 @@ describe('build state from add table at cursor', () => {
|
|||
const actual = buildStateFromAddTableAtCursor(
|
||||
{
|
||||
...initialState,
|
||||
markdownContentLines: ['a', 'b', 'c'],
|
||||
markdownContent: 'a\nb\nc',
|
||||
markdownContent: { plain: 'a\nb\nc', lines: ['a', 'b', 'c'], lineStartIndexes: [0, 2, 4] },
|
||||
selection: {
|
||||
from: {
|
||||
line: 1,
|
||||
character: 0
|
||||
}
|
||||
from: 2
|
||||
}
|
||||
},
|
||||
3,
|
||||
3
|
||||
)
|
||||
expect(actual.markdownContent).toEqual(
|
||||
expect(actual.markdownContent.plain).toEqual(
|
||||
'a\n\n| # 1 | # 2 | # 3 |\n' +
|
||||
'| ---- | ---- | ---- |\n' +
|
||||
'| Text | Text | Text |\n' +
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import type { NoteDetails } from '../types/note-details'
|
||||
import { buildStateFromUpdatedMarkdownContentLines } from '../build-state-from-updated-markdown-content'
|
||||
import { buildStateFromUpdatedMarkdownContent } from '../build-state-from-updated-markdown-content'
|
||||
import { replaceSelection } from '../format-selection/formatters/replace-selection'
|
||||
import { createNumberRangeArray } from '../../../components/common/number-range/number-range'
|
||||
|
||||
|
@ -19,10 +19,16 @@ import { createNumberRangeArray } from '../../../components/common/number-range/
|
|||
*/
|
||||
export const buildStateFromAddTableAtCursor = (state: NoteDetails, rows: number, columns: number): NoteDetails => {
|
||||
const table = createMarkdownTable(rows, columns)
|
||||
return buildStateFromUpdatedMarkdownContentLines(
|
||||
state,
|
||||
replaceSelection(state.markdownContentLines, { from: state.selection.to ?? state.selection.from }, table)
|
||||
const [newContent, newSelection] = replaceSelection(
|
||||
state.markdownContent.plain,
|
||||
{ from: state.selection.to ?? state.selection.from },
|
||||
table
|
||||
)
|
||||
const newState = buildStateFromUpdatedMarkdownContent(state, newContent)
|
||||
return {
|
||||
...newState,
|
||||
selection: newSelection
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,7 +26,10 @@ describe('build state from replace in markdown content', () => {
|
|||
})
|
||||
|
||||
it('updates the markdown content with the replacement', () => {
|
||||
const startState = { ...initialState, markdownContent: 'replaceable' }
|
||||
const startState: NoteDetails = {
|
||||
...initialState,
|
||||
markdownContent: { ...initialState.markdownContent, plain: 'replaceable' }
|
||||
}
|
||||
const result = buildStateFromReplaceInMarkdownContent(startState, 'replaceable', 'replacement')
|
||||
expect(result).toBe(mockedNoteDetails)
|
||||
expect(buildStateFromUpdatedMarkdownContentMock).toHaveBeenCalledWith(startState, 'replacement')
|
||||
|
|
|
@ -35,5 +35,5 @@ export const buildStateFromReplaceInMarkdownContent = (
|
|||
replaceable: string,
|
||||
replacement: string
|
||||
): NoteDetails => {
|
||||
return buildStateFromUpdatedMarkdownContent(state, replaceAll(state.markdownContent, replaceable, replacement))
|
||||
return buildStateFromUpdatedMarkdownContent(state, replaceAll(state.markdownContent.plain, replaceable, replacement))
|
||||
}
|
||||
|
|
|
@ -13,47 +13,55 @@ import { initialState } from '../initial-state'
|
|||
import type { CursorSelection } from '../../editor/types'
|
||||
|
||||
describe('build state from replace selection', () => {
|
||||
const buildStateFromUpdatedMarkdownContentLinesMock = jest.spyOn(
|
||||
const buildStateFromUpdatedMarkdownContentMock = jest.spyOn(
|
||||
buildStateFromUpdatedMarkdownContentLinesModule,
|
||||
'buildStateFromUpdatedMarkdownContentLines'
|
||||
'buildStateFromUpdatedMarkdownContent'
|
||||
)
|
||||
const replaceSelectionMock = jest.spyOn(replaceSelectionModule, 'replaceSelection')
|
||||
const mockedNoteDetails = Mock.of<NoteDetails>()
|
||||
const mockedReplacedLines = ['replaced']
|
||||
const mockedNoteDetails = { content: 'mocked' } as unknown as NoteDetails
|
||||
const mockedFormattedContent = 'formatted'
|
||||
const mockedCursor = Mock.of<CursorSelection>()
|
||||
|
||||
beforeAll(() => {
|
||||
buildStateFromUpdatedMarkdownContentLinesMock.mockImplementation(() => mockedNoteDetails)
|
||||
replaceSelectionMock.mockImplementation(() => mockedReplacedLines)
|
||||
buildStateFromUpdatedMarkdownContentMock.mockImplementation(() => mockedNoteDetails)
|
||||
replaceSelectionMock.mockImplementation(() => [mockedFormattedContent, mockedCursor])
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
buildStateFromUpdatedMarkdownContentLinesMock.mockReset()
|
||||
buildStateFromUpdatedMarkdownContentMock.mockReset()
|
||||
replaceSelectionMock.mockReset()
|
||||
})
|
||||
|
||||
it('builds a new state with the provided cursor', () => {
|
||||
const originalLines = ['original']
|
||||
const startState = { ...initialState, markdownContentLines: originalLines }
|
||||
const originalLines = 'original'
|
||||
const startState = {
|
||||
...initialState,
|
||||
markdownContent: { plain: originalLines, lines: [originalLines], lineStartIndexes: [0] }
|
||||
}
|
||||
const customCursor = Mock.of<CursorSelection>()
|
||||
const textReplacement = 'replacement'
|
||||
|
||||
const result = buildStateFromReplaceSelection(startState, 'replacement', customCursor)
|
||||
|
||||
expect(result).toBe(mockedNoteDetails)
|
||||
expect(buildStateFromUpdatedMarkdownContentLinesMock).toHaveBeenCalledWith(startState, mockedReplacedLines)
|
||||
expect(result).toStrictEqual({ content: 'mocked', selection: mockedCursor })
|
||||
expect(buildStateFromUpdatedMarkdownContentMock).toHaveBeenCalledWith(startState, mockedFormattedContent)
|
||||
expect(replaceSelectionMock).toHaveBeenCalledWith(originalLines, customCursor, textReplacement)
|
||||
})
|
||||
|
||||
it('builds a new state with the state cursor', () => {
|
||||
const originalLines = ['original']
|
||||
const originalLines = 'original'
|
||||
const selection = Mock.of<CursorSelection>()
|
||||
const startState = { ...initialState, markdownContentLines: originalLines, selection }
|
||||
const startState: NoteDetails = {
|
||||
...initialState,
|
||||
markdownContent: { plain: originalLines, lines: [originalLines], lineStartIndexes: [0] },
|
||||
selection
|
||||
}
|
||||
const textReplacement = 'replacement'
|
||||
|
||||
const result = buildStateFromReplaceSelection(startState, 'replacement')
|
||||
|
||||
expect(result).toBe(mockedNoteDetails)
|
||||
expect(buildStateFromUpdatedMarkdownContentLinesMock).toHaveBeenCalledWith(startState, mockedReplacedLines)
|
||||
expect(result).toStrictEqual({ content: 'mocked', selection: mockedCursor })
|
||||
expect(buildStateFromUpdatedMarkdownContentMock).toHaveBeenCalledWith(startState, mockedFormattedContent)
|
||||
expect(replaceSelectionMock).toHaveBeenCalledWith(originalLines, selection, textReplacement)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -6,12 +6,18 @@
|
|||
|
||||
import type { NoteDetails } from '../types/note-details'
|
||||
import type { CursorSelection } from '../../editor/types'
|
||||
import { buildStateFromUpdatedMarkdownContentLines } from '../build-state-from-updated-markdown-content'
|
||||
import { buildStateFromUpdatedMarkdownContent } from '../build-state-from-updated-markdown-content'
|
||||
import { replaceSelection } from '../format-selection/formatters/replace-selection'
|
||||
|
||||
export const buildStateFromReplaceSelection = (state: NoteDetails, text: string, cursorSelection?: CursorSelection) => {
|
||||
return buildStateFromUpdatedMarkdownContentLines(
|
||||
state,
|
||||
replaceSelection(state.markdownContentLines, cursorSelection ? cursorSelection : state.selection, text)
|
||||
const [newContent, newSelection] = replaceSelection(
|
||||
state.markdownContent.plain,
|
||||
cursorSelection ? cursorSelection : state.selection,
|
||||
text
|
||||
)
|
||||
const newState = buildStateFromUpdatedMarkdownContent(state, newContent)
|
||||
return {
|
||||
...newState,
|
||||
selection: newSelection
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,34 +14,38 @@ import { FormatType } from '../types'
|
|||
import type { CursorSelection } from '../../editor/types'
|
||||
|
||||
describe('build state from selection format', () => {
|
||||
const buildStateFromUpdatedMarkdownContentLinesMock = jest.spyOn(
|
||||
const buildStateFromUpdatedMarkdownContentMock = jest.spyOn(
|
||||
buildStateFromUpdatedMarkdownContentLinesModule,
|
||||
'buildStateFromUpdatedMarkdownContentLines'
|
||||
'buildStateFromUpdatedMarkdownContent'
|
||||
)
|
||||
const mockedNoteDetails = Mock.of<NoteDetails>()
|
||||
const mockedNoteDetails = { content: 'mocked' } as unknown as NoteDetails
|
||||
const applyFormatTypeToMarkdownLinesMock = jest.spyOn(
|
||||
applyFormatTypeToMarkdownLinesModule,
|
||||
'applyFormatTypeToMarkdownLines'
|
||||
)
|
||||
const mockedFormattedLines = ['formatted']
|
||||
const mockedFormattedContent = 'formatted'
|
||||
const mockedCursor = Mock.of<CursorSelection>()
|
||||
|
||||
beforeAll(() => {
|
||||
buildStateFromUpdatedMarkdownContentLinesMock.mockImplementation(() => mockedNoteDetails)
|
||||
applyFormatTypeToMarkdownLinesMock.mockImplementation(() => mockedFormattedLines)
|
||||
buildStateFromUpdatedMarkdownContentMock.mockImplementation(() => mockedNoteDetails)
|
||||
applyFormatTypeToMarkdownLinesMock.mockImplementation(() => [mockedFormattedContent, mockedCursor])
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
buildStateFromUpdatedMarkdownContentLinesMock.mockReset()
|
||||
buildStateFromUpdatedMarkdownContentMock.mockReset()
|
||||
applyFormatTypeToMarkdownLinesMock.mockReset()
|
||||
})
|
||||
|
||||
it('builds a new state with the formatted code', () => {
|
||||
const originalLines = ['original']
|
||||
const customCursor = Mock.of<CursorSelection>()
|
||||
const startState = { ...initialState, markdownContentLines: originalLines, selection: customCursor }
|
||||
const originalContent = 'original'
|
||||
const startState: NoteDetails = {
|
||||
...initialState,
|
||||
markdownContent: { ...initialState.markdownContent, plain: originalContent },
|
||||
selection: mockedCursor
|
||||
}
|
||||
const result = buildStateFromSelectionFormat(startState, FormatType.BOLD)
|
||||
expect(result).toBe(mockedNoteDetails)
|
||||
expect(buildStateFromUpdatedMarkdownContentLinesMock).toHaveBeenCalledWith(startState, mockedFormattedLines)
|
||||
expect(applyFormatTypeToMarkdownLinesMock).toHaveBeenCalledWith(originalLines, customCursor, FormatType.BOLD)
|
||||
expect(result).toStrictEqual({ content: 'mocked', selection: mockedCursor })
|
||||
expect(buildStateFromUpdatedMarkdownContentMock).toHaveBeenCalledWith(startState, mockedFormattedContent)
|
||||
expect(applyFormatTypeToMarkdownLinesMock).toHaveBeenCalledWith(originalContent, mockedCursor, FormatType.BOLD)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
|
||||
import type { NoteDetails } from '../types/note-details'
|
||||
import type { FormatType } from '../types'
|
||||
import { buildStateFromUpdatedMarkdownContentLines } from '../build-state-from-updated-markdown-content'
|
||||
import { buildStateFromUpdatedMarkdownContent } from '../build-state-from-updated-markdown-content'
|
||||
import { applyFormatTypeToMarkdownLines } from '../format-selection/apply-format-type-to-markdown-lines'
|
||||
|
||||
export const buildStateFromSelectionFormat = (state: NoteDetails, type: FormatType): NoteDetails => {
|
||||
return buildStateFromUpdatedMarkdownContentLines(
|
||||
state,
|
||||
applyFormatTypeToMarkdownLines(state.markdownContentLines, state.selection, type)
|
||||
)
|
||||
const [newContent, newSelection] = applyFormatTypeToMarkdownLines(state.markdownContent.plain, state.selection, type)
|
||||
const newState = buildStateFromUpdatedMarkdownContent(state, newContent)
|
||||
return {
|
||||
...newState,
|
||||
selection: newSelection
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,10 +118,12 @@ describe('build state from set note data from server', () => {
|
|||
slideOptions: initialSlideOptions
|
||||
},
|
||||
noteTitle: '',
|
||||
selection: { from: { line: 0, character: 0 } },
|
||||
|
||||
markdownContent: 'line1\nline2',
|
||||
markdownContentLines: ['line1', 'line2'],
|
||||
selection: { from: 0 },
|
||||
markdownContent: {
|
||||
plain: 'line1\nline2',
|
||||
lines: ['line1', 'line2'],
|
||||
lineStartIndexes: [0, 6]
|
||||
},
|
||||
firstHeading: '',
|
||||
rawFrontmatter: '',
|
||||
id: 'id',
|
||||
|
|
|
@ -9,6 +9,7 @@ import type { NoteDetails } from '../types/note-details'
|
|||
import { buildStateFromUpdatedMarkdownContent } from '../build-state-from-updated-markdown-content'
|
||||
import { initialState } from '../initial-state'
|
||||
import { DateTime } from 'luxon'
|
||||
import { calculateLineStartIndexes } from '../calculate-line-start-indexes'
|
||||
|
||||
/**
|
||||
* Builds a {@link NoteDetails} redux state from a DTO received as an API response.
|
||||
|
@ -17,7 +18,7 @@ import { DateTime } from 'luxon'
|
|||
*/
|
||||
export const buildStateFromServerDto = (dto: NoteDto): NoteDetails => {
|
||||
const newState = convertNoteDtoToNoteDetails(dto)
|
||||
return buildStateFromUpdatedMarkdownContent(newState, newState.markdownContent)
|
||||
return buildStateFromUpdatedMarkdownContent(newState, newState.markdownContent.plain)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,10 +28,14 @@ export const buildStateFromServerDto = (dto: NoteDto): NoteDetails => {
|
|||
* @return The NoteDetails object corresponding to the DTO.
|
||||
*/
|
||||
const convertNoteDtoToNoteDetails = (note: NoteDto): NoteDetails => {
|
||||
const newLines = note.content.split('\n')
|
||||
return {
|
||||
...initialState,
|
||||
markdownContent: note.content,
|
||||
markdownContentLines: note.content.split('\n'),
|
||||
markdownContent: {
|
||||
plain: note.content,
|
||||
lines: newLines,
|
||||
lineStartIndexes: calculateLineStartIndexes(newLines)
|
||||
},
|
||||
rawFrontmatter: '',
|
||||
id: note.metadata.id,
|
||||
createTime: DateTime.fromISO(note.metadata.createTime),
|
||||
|
|
|
@ -28,14 +28,20 @@ describe('build state from task list update', () => {
|
|||
const markdownContentLines = ['no task', '- [ ] not checked', '- [x] checked']
|
||||
|
||||
it(`doesn't change the state if the line doesn't contain a task`, () => {
|
||||
const startState = { ...initialState, markdownContentLines: markdownContentLines }
|
||||
const startState: NoteDetails = {
|
||||
...initialState,
|
||||
markdownContent: { ...initialState.markdownContent, lines: markdownContentLines }
|
||||
}
|
||||
const result = buildStateFromTaskListUpdate(startState, 0, true)
|
||||
expect(result).toBe(startState)
|
||||
expect(buildStateFromUpdatedMarkdownContentLinesMock).toBeCalledTimes(0)
|
||||
})
|
||||
|
||||
it(`can change the state of a task to checked`, () => {
|
||||
const startState = { ...initialState, markdownContentLines: markdownContentLines }
|
||||
const startState: NoteDetails = {
|
||||
...initialState,
|
||||
markdownContent: { ...initialState.markdownContent, lines: markdownContentLines }
|
||||
}
|
||||
const result = buildStateFromTaskListUpdate(startState, 1, true)
|
||||
expect(result).toBe(mockedNoteDetails)
|
||||
expect(buildStateFromUpdatedMarkdownContentLinesMock).toBeCalledWith(startState, [
|
||||
|
@ -46,7 +52,10 @@ describe('build state from task list update', () => {
|
|||
})
|
||||
|
||||
it(`can change the state of a task to unchecked`, () => {
|
||||
const startState = { ...initialState, markdownContentLines: markdownContentLines }
|
||||
const startState: NoteDetails = {
|
||||
...initialState,
|
||||
markdownContent: { ...initialState.markdownContent, lines: markdownContentLines }
|
||||
}
|
||||
const result = buildStateFromTaskListUpdate(startState, 2, false)
|
||||
expect(result).toBe(mockedNoteDetails)
|
||||
expect(buildStateFromUpdatedMarkdownContentLinesMock).toBeCalledWith(startState, [
|
||||
|
|
|
@ -21,7 +21,7 @@ export const buildStateFromTaskListUpdate = (
|
|||
changedLineIndex: number,
|
||||
checkboxChecked: boolean
|
||||
): NoteDetails => {
|
||||
const lines = [...state.markdownContentLines]
|
||||
const lines = [...state.markdownContent.lines]
|
||||
return Optional.ofNullable(TASK_REGEX.exec(lines[changedLineIndex]))
|
||||
.map((results) => {
|
||||
const [, beforeCheckbox, afterCheckbox] = results
|
||||
|
|
|
@ -8,8 +8,28 @@ import type { NoteDetails } from '../types/note-details'
|
|||
import type { CursorSelection } from '../../editor/types'
|
||||
|
||||
export const buildStateFromUpdateCursorPosition = (state: NoteDetails, selection: CursorSelection): NoteDetails => {
|
||||
const correctedSelection = isFromAfterTo(selection)
|
||||
? {
|
||||
to: selection.from,
|
||||
from: selection.to as number
|
||||
}
|
||||
: selection
|
||||
|
||||
return {
|
||||
...state,
|
||||
selection
|
||||
selection: correctedSelection
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the from cursor position in the given selection is after the to cursor position.
|
||||
*
|
||||
* @param selection The cursor selection to check
|
||||
* @return {@code true} if the from cursor position is after the to position
|
||||
*/
|
||||
const isFromAfterTo = (selection: CursorSelection): boolean => {
|
||||
if (selection.to === undefined) {
|
||||
return false
|
||||
}
|
||||
return selection.from > selection.to
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue