Autocompletion and toolbar button for collapsable blocks (#615)

* Add autocompletion for <details construct

* Add toolbar button for <details>-construct

* Added CHANGELOG notice
This commit is contained in:
Erik Michelson 2020-09-30 23:35:10 +02:00 committed by GitHub
parent 2b6ba82b4b
commit 0f31c3b0b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 171 additions and 2 deletions

View file

@ -0,0 +1,35 @@
import { Editor, Hint, Hints, Pos } from 'codemirror'
import { findWordAtCursor, Hinter } from './index'
const allowedChars = /[<\w>]/
const wordRegExp = /^(<d(?:e|et|eta|etai|etail|etails)?)$/
const collapsableBlockHint = (editor: Editor): Promise< Hints| null > => {
return new Promise((resolve) => {
const searchTerm = findWordAtCursor(editor, allowedChars)
const searchResult = wordRegExp.exec(searchTerm.text)
if (searchResult === null) {
resolve(null)
return
}
const suggestions = ['<details>\n <summary>Toggle label</summary>\n Toggled content\n</details>']
const cursor = editor.getCursor()
if (!suggestions) {
resolve(null)
} else {
resolve({
list: suggestions.map((suggestion: string): Hint => ({
text: suggestion
})),
from: Pos(cursor.line, searchTerm.start),
to: Pos(cursor.line, searchTerm.end + 1)
})
}
})
}
export const CollapsableBlockHinter: Hinter = {
allowedChars,
wordRegExp,
hint: collapsableBlockHint
}

View file

@ -1,5 +1,6 @@
import { Editor, Hints } from 'codemirror'
import { CodeBlockHinter } from './code-block'
import { CollapsableBlockHinter } from './collapsable-block'
import { ContainerHinter } from './container'
import { EmojiHinter } from './emoji'
import { HeaderHinter } from './header'
@ -55,5 +56,6 @@ export const allHinters: Hinter[] = [
HeaderHinter,
ImageHinter,
LinkAndExtraTagHinter,
PDFHinter
PDFHinter,
CollapsableBlockHinter
]

View file

@ -8,6 +8,7 @@ import { EmojiPickerButton } from './emoji-picker/emoji-picker-button'
import './tool-bar.scss'
import {
addCodeFences,
addCollapsableBlock,
addComment,
addHeaderLevel,
addImage,
@ -101,6 +102,9 @@ export const ToolBar: React.FC<ToolBarProps> = ({ editor }) => {
<Button variant='light' onClick={() => addLine(editor)} title={t('editor.editorToolbar.line')}>
<ForkAwesomeIcon icon="minus"/>
</Button>
<Button variant='light' onClick={() => addCollapsableBlock(editor)} title={t('editor.editorToolbar.collapsableBlock')}>
<ForkAwesomeIcon icon="caret-square-o-down"/>
</Button>
<Button variant='light' onClick={() => addComment(editor)} title={t('editor.editorToolbar.comment')}>
<ForkAwesomeIcon icon="comment"/>
</Button>

View file

@ -3,6 +3,7 @@ import { EmojiData } from 'emoji-mart'
import { Mock } from 'ts-mockery'
import {
addCodeFences,
addCollapsableBlock,
addComment,
addEmoji,
addHeaderLevel,
@ -1499,6 +1500,93 @@ describe('test addLine', () => {
addLine(editor)
})
})
describe('test collapsable block', () => {
const { cursor, firstLine, multiline, multilineOffset } = buildRanges()
const textFirstLine = testContent.split('\n')[0]
it('just cursor', done => {
Mock.extend(editor).with({
listSelections: () => (
Mock.of<Range[]>([{
anchor: cursor.from,
head: cursor.to,
from: () => cursor.from,
to: () => cursor.to,
empty: () => true
}])
),
getLine: (): string => (textFirstLine),
replaceRange: (replacement: string | string[]) => {
expect(replacement).toEqual(`${textFirstLine}\n<details>\n <summary>Toggle label</summary>\n Toggled content\n</details>`)
done()
}
})
addCollapsableBlock(editor)
})
it('1st line', done => {
Mock.extend(editor).with({
listSelections: () => (
Mock.of<Range[]>([{
anchor: firstLine.from,
head: firstLine.to,
from: () => firstLine.from,
to: () => firstLine.to,
empty: () => false
}])
),
getLine: (): string => (textFirstLine),
replaceRange: (replacement: string | string[], from: CodeMirror.Position, to?: CodeMirror.Position) => {
expect(from).toEqual(firstLine.from)
expect(to).toEqual(firstLine.to)
expect(replacement).toEqual(`${textFirstLine}\n<details>\n <summary>Toggle label</summary>\n Toggled content\n</details>`)
done()
}
})
addCollapsableBlock(editor)
})
it('multiple lines', done => {
Mock.extend(editor).with({
listSelections: () => (
Mock.of<Range[]>([{
anchor: multiline.from,
head: multiline.to,
from: () => multiline.from,
to: () => multiline.to,
empty: () => false
}])
),
getLine: (): string => '2nd line',
replaceRange: (replacement: string | string[]) => {
expect(replacement).toEqual('2nd line\n<details>\n <summary>Toggle label</summary>\n Toggled content\n</details>')
done()
}
})
addCollapsableBlock(editor)
})
it('multiple lines with offset', done => {
Mock.extend(editor).with({
listSelections: () => (
Mock.of<Range[]>([{
anchor: multilineOffset.from,
head: multilineOffset.to,
from: () => multilineOffset.from,
to: () => multilineOffset.to,
empty: () => false
}])
),
getLine: (): string => '2nd line',
replaceRange: (replacement: string | string[]) => {
expect(replacement).toEqual('2nd line\n<details>\n <summary>Toggle label</summary>\n Toggled content\n</details>')
done()
}
})
addCollapsableBlock(editor)
})
})
describe('test addComment', () => {
const { cursor, firstLine, multiline, multilineOffset } = buildRanges()
const textFirstLine = testContent.split('\n')[0]

View file

@ -21,6 +21,7 @@ export const addTaskList = (editor: Editor): void => createList(editor, () => '-
export const addImage = (editor: Editor): void => addLink(editor, '!')
export const addLine = (editor: Editor): void => changeLines(editor, line => `${line}\n----`)
export const addCollapsableBlock = (editor: Editor): void => changeLines(editor, line => `${line}\n<details>\n <summary>Toggle label</summary>\n Toggled content\n</details>`)
export const addComment = (editor: Editor): void => changeLines(editor, line => `${line}\n> []`)
export const addTable = (editor: Editor): void => changeLines(editor, line => `${line}\n| # 1 | # 2 | # 3 |\n| ---- | ---- | ---- |\n| Text | Text | Text |`)