You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-24 20:19:10 +02:00
Mobile: Rich Text Editor: Fix adding headings moves the cursor to the next line (#12934)
This commit is contained in:
@@ -4,47 +4,133 @@ import extractSelectedLinesTo from './extractSelectedLinesTo';
|
||||
import schema from '../schema';
|
||||
|
||||
describe('extractSelectedLinesTo', () => {
|
||||
test('should extract a single line containing the cursor to a heading', () => {
|
||||
test.each([
|
||||
{
|
||||
label: 'should extract a single line containing the cursor to a heading',
|
||||
initial: {
|
||||
docHtml: '<p>Line 1<br>Line 2<br>Line 3</p>',
|
||||
// Put the cursor in the middle of the second line
|
||||
cursorPosition: '<Line 1|Line'.length,
|
||||
},
|
||||
convertTo: { type: schema.nodes.heading, attrs: { level: 1 } },
|
||||
expected: {
|
||||
// The section of the document containing the cursor should now be a new line
|
||||
docJson: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'Line 1' }],
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: { level: 1 },
|
||||
content: [{ type: 'text', text: 'Line 2' }],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'Line 3' }],
|
||||
},
|
||||
],
|
||||
// The cursor should move to the end of the extracted line
|
||||
cursorPosition: '<Line 1><Line 2'.length,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'should convert an empty paragraph to a heading',
|
||||
initial: {
|
||||
docHtml: '<p>Line 1</p><p></p><p>Line 3</p>',
|
||||
cursorPosition: '<Line 1><'.length,
|
||||
},
|
||||
convertTo: { type: schema.nodes.heading },
|
||||
expected: {
|
||||
docJson: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'Line 1' }],
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'Line 3' }],
|
||||
},
|
||||
],
|
||||
cursorPosition: '<Line 1><'.length,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'should convert the last line in a paragraph to a heading',
|
||||
initial: {
|
||||
docHtml: '<p>Line 1<br/></p><p>End</p>',
|
||||
cursorPosition: '<Line 1|'.length,
|
||||
},
|
||||
convertTo: { type: schema.nodes.heading },
|
||||
expected: {
|
||||
docJson: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'Line 1' }],
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'End' }],
|
||||
},
|
||||
],
|
||||
cursorPosition: '<Line 1><'.length,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'should convert the first line in a paragraph to a heading',
|
||||
initial: {
|
||||
docHtml: '<p><br/>Line 1</p><p>End</p>',
|
||||
cursorPosition: '<'.length,
|
||||
},
|
||||
convertTo: { type: schema.nodes.heading },
|
||||
expected: {
|
||||
docJson: [
|
||||
{
|
||||
type: 'heading',
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'Line 1' }],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'End' }],
|
||||
},
|
||||
],
|
||||
cursorPosition: '<'.length,
|
||||
},
|
||||
},
|
||||
])('$label', ({ initial, expected, convertTo }) => {
|
||||
const view = createTestEditor({
|
||||
html: '<p>Line 1<br>Line 2<br>Line 3</p>',
|
||||
html: initial.docHtml,
|
||||
});
|
||||
view.dispatch(view.state.tr.setSelection(
|
||||
// Put the cursor in the middle of the second line
|
||||
TextSelection.create(view.state.doc, '<Line 1|Line'.length),
|
||||
TextSelection.create(
|
||||
view.state.doc,
|
||||
initial.cursorPosition,
|
||||
),
|
||||
));
|
||||
|
||||
const { transaction } = extractSelectedLinesTo(
|
||||
{ type: schema.nodes.heading, attrs: { level: 1 } },
|
||||
{ type: convertTo.type, attrs: convertTo.attrs ?? { } },
|
||||
view.state.tr,
|
||||
view.state.selection,
|
||||
);
|
||||
|
||||
view.dispatch(transaction);
|
||||
|
||||
// The section of the document containing the cursor should now be a new line
|
||||
expect(view.state.doc.toJSON()).toMatchObject({
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'Line 1' }],
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: { level: 1 },
|
||||
content: [{ type: 'text', text: 'Line 2' }],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'Line 3' }],
|
||||
},
|
||||
],
|
||||
content: expected.docJson,
|
||||
});
|
||||
|
||||
// The selection should still be in the heading
|
||||
expect(view.state.selection.$anchor.parent.toJSON()).toMatchObject({
|
||||
type: 'heading',
|
||||
attrs: { level: 1 },
|
||||
});
|
||||
// All of the tests in this group expect a single cursor
|
||||
expect(view.state.selection.empty).toBe(true);
|
||||
expect(view.state.selection.from).toBe(expected.cursorPosition);
|
||||
});
|
||||
|
||||
test('should extract multiple lines in the same paragraph to a new paragraph', () => {
|
||||
|
@@ -33,7 +33,8 @@ const extractSelectedLinesTo = (extractTo: ExtractToOptions, transaction: Transa
|
||||
|
||||
const firstParagraphFrom = firstParagraphPos;
|
||||
const lastParagraph = transaction.doc.nodeAt(lastParagraphPos);
|
||||
const lastParagraphTo = lastParagraphPos + lastParagraph.nodeSize;
|
||||
// -1: Exclude the end token
|
||||
const lastParagraphTo = lastParagraphPos + lastParagraph.nodeSize - 1;
|
||||
|
||||
// Find the previous and next <br/>s (or the start/end of the paragraph)
|
||||
let fromBreakPosition = firstParagraphFrom;
|
||||
|
Reference in New Issue
Block a user