Upgrade to CodeMirror 6 (#1787)

Upgrade to CodeMirror 6

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-02-13 12:14:01 +01:00 committed by GitHub
parent 1a09bfa5f1
commit 6a6f6105b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
103 changed files with 1906 additions and 2615 deletions

View file

@ -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' +

View file

@ -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
}
}
/**

View file

@ -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')

View file

@ -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))
}

View file

@ -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)
})
})

View file

@ -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
}
}

View file

@ -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)
})
})

View file

@ -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
}
}

View file

@ -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',

View file

@ -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),

View file

@ -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, [

View file

@ -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

View file

@ -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
}