Skip to content

Commit 79dbe6c

Browse files
committed
fix(commands): be more context aware when inserting a horizontal rule to avoid creating a heading
fixes #192
1 parent a852d7a commit 79dbe6c

File tree

2 files changed

+95
-2
lines changed

2 files changed

+95
-2
lines changed

src/commonmark/commands.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,49 @@ export function insertCommonmarkTableCommand(
684684
}
685685
}
686686

687+
/**
688+
* Inserts a horizontal rule at the cursor
689+
* @param state The current editor state
690+
* @param dispatch the dispatch function used to dispatch the transaction, set to "null" if you don't want to dispatch
691+
*/
692+
export function insertCommonmarkHorizontalRuleCommand(
693+
state: EditorState,
694+
dispatch: (tr: Transaction) => void
695+
) {
696+
// figure out how many leading newlines we need to add
697+
// adding only a single newline after text will result in the thematic break
698+
// being parsed as an setext heading; e.g. header1\n---
699+
const precedingText = state.doc.cut(0, state.selection.from).textContent;
700+
const lastNewlineIdx = precedingText.lastIndexOf("\n");
701+
const currentLine = precedingText.slice(lastNewlineIdx + 1);
702+
703+
// check the previous line as well
704+
let prevLine: string = null;
705+
if (lastNewlineIdx > -1) {
706+
const prevNewlineIdx = precedingText.lastIndexOf(
707+
"\n",
708+
lastNewlineIdx - 1
709+
);
710+
// even if no additional newline is found, we can assume an index of (-1 + 1), which is the beginning of the text
711+
prevLine = precedingText.slice(prevNewlineIdx + 1, lastNewlineIdx);
712+
}
713+
714+
let newlines: string;
715+
716+
if (!precedingText || (!prevLine && !currentLine)) {
717+
// beginning of doc or multiple empty lines - no newlines
718+
newlines = "";
719+
} else if (!currentLine) {
720+
// no text in current line - one newline
721+
newlines = "\n";
722+
} else {
723+
// text in current line - two newlines
724+
newlines = "\n\n";
725+
}
726+
727+
return insertRawTextCommand(newlines + "---\n", 4, 4)(state, dispatch);
728+
}
729+
687730
//TODO
688731
function indentBlockCommand(): boolean {
689732
return false;
@@ -740,8 +783,6 @@ export const strikethroughCommand = wrapInCommand("~~", null);
740783
export const blockquoteCommand = setBlockTypeCommand(">");
741784
export const orderedListCommand = setBlockTypeCommand("1.");
742785
export const unorderedListCommand = setBlockTypeCommand("-");
743-
export const insertCommonmarkHorizontalRuleCommand =
744-
insertRawTextCommand("\n---\n");
745786
export const insertCodeblockCommand = blockWrapInCommand("```");
746787
export const spoilerCommand = setBlockTypeCommand(">!");
747788
export const supCommand = wrapInCommand("<sup>", "</sup>");

test/commonmark/commands.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,4 +661,56 @@ some text`;
661661
expect(spy).toHaveBeenCalledTimes(2);
662662
});
663663
});
664+
665+
describe("insertCommonmarkHorizontalRuleCommand", () => {
666+
it.each([
667+
// empty doc
668+
["", "---\n"],
669+
// no text in current or previous line
670+
["test\n\n", "test\n\n---\n"],
671+
// no text in current line
672+
["test\n", "test\n\n---\n"],
673+
// text in current line
674+
["test1\ntest2", "test1\ntest2\n\n---\n"],
675+
])(
676+
"should detect and prepend whitespace correctly (%#)",
677+
(content, expected) => {
678+
// create a state with the cursor at the end of the doc
679+
const state = createState(content, content.length);
680+
681+
expect(state).transactionSuccess(
682+
commands.insertCommonmarkHorizontalRuleCommand,
683+
expected,
684+
""
685+
);
686+
}
687+
);
688+
689+
it("should replace selected text", () => {
690+
const state = createSelectedState("text");
691+
692+
expect(state).transactionSuccess(
693+
commands.insertCommonmarkHorizontalRuleCommand,
694+
"---\n",
695+
""
696+
);
697+
});
698+
699+
it("should place the cursor after the inserted text", () => {
700+
// create a state with the cursor at the end of the doc
701+
let state = createState("");
702+
703+
const isValid = commands.insertCommonmarkHorizontalRuleCommand(
704+
state,
705+
(t) => {
706+
state = state.apply(t);
707+
}
708+
);
709+
710+
expect(isValid).toBe(true);
711+
expect(state.doc.textContent).toBe("---\n");
712+
expect(state.selection.from).toBe(5);
713+
expect(state.selection.to).toBe(5);
714+
});
715+
});
664716
});

0 commit comments

Comments
 (0)