diff --git a/__tests__/empty-line-around-blockquotes.test.ts b/__tests__/empty-line-around-blockquotes.test.ts index 70fa4d1a..e4e3e985 100644 --- a/__tests__/empty-line-around-blockquotes.test.ts +++ b/__tests__/empty-line-around-blockquotes.test.ts @@ -126,5 +126,46 @@ ruleTest({ > Content `, }, + { // accounts for https://github.com/platers/obsidian-linter/issues/684 + testName: 'Make sure that a nested set of callouts are not merged when only a blank lines is between them with no content', + before: dedent` + > [!multi-column] + > + >> [!note]+ Use Case + >> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + >> ##### User Case Background + >> Vitae nunc sed velit dignissim sodales. In cursus turpis massa tincidunt dui ut ornare lectus. + > + >> [!warning]+ Resources + >> #### Requirement + >> - Lorem ipsum dolor sit amet + >> - Vitae nunc sed velit dignissim sodales. + >> - In cursus turpis massa tincidunt dui ut ornare lectus. + > + >> [!todo]+ + >> - [x] Define Use Case + >> - [ ] Craft User Story + >> - [ ] Develop draft sketches + `, + after: dedent` + > [!multi-column] + > + >> [!note]+ Use Case + >> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + >> ##### User Case Background + >> Vitae nunc sed velit dignissim sodales. In cursus turpis massa tincidunt dui ut ornare lectus. + > + >> [!warning]+ Resources + >> #### Requirement + >> - Lorem ipsum dolor sit amet + >> - Vitae nunc sed velit dignissim sodales. + >> - In cursus turpis massa tincidunt dui ut ornare lectus. + > + >> [!todo]+ + >> - [x] Define Use Case + >> - [ ] Craft User Story + >> - [ ] Develop draft sketches + `, + }, ], }); diff --git a/src/utils/regex.ts b/src/utils/regex.ts index 8ad1743b..9024df4e 100644 --- a/src/utils/regex.ts +++ b/src/utils/regex.ts @@ -42,6 +42,7 @@ export const indentedOrBlockquoteNestedChecklistIndicatorRegex = new RegExp(`^${ export const nonBlockquoteChecklistRegex = new RegExp(`^\\s*- ${checklistBoxIndicator} `); export const footnoteDefinitionIndicatorAtStartOfLine = /^(\[\^\w+\]) ?([,.;!:?])/gm; +export const calloutRegex = /^(>\s*)+\[![^\s]*\]/m; // https://stackoverflow.com/questions/38866071/javascript-replace-method-dollar-signs // Important to use this for any regex replacements where the replacement string diff --git a/src/utils/strings.ts b/src/utils/strings.ts index 66354b27..bdcb228a 100644 --- a/src/utils/strings.ts +++ b/src/utils/strings.ts @@ -1,3 +1,4 @@ +import {calloutRegex} from './regex'; /** * Inserts a string at the given position in a string. * @param {string} str - The string to insert into @@ -59,6 +60,17 @@ function getEmptyLine(priorLine: string = ''): string { return '\n' + priorLineStart.trim(); } +function getEmptyLineForBlockqute(priorLine: string = '', isCallout: boolean = false, blockquoteLevel: number = 1): string { + const potentialEmptyLine = getEmptyLine(priorLine); + const previousBlockquoteLevel = countInstances(potentialEmptyLine, '>'); + const dealingWithACallout = isCallout || calloutRegex.test(priorLine); + if (dealingWithACallout && blockquoteLevel === previousBlockquoteLevel) { + return potentialEmptyLine.substring(0, potentialEmptyLine.lastIndexOf('>')); + } + + return potentialEmptyLine; +} + function makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFile(text: string, startOfContent: number): string { if (startOfContent === 0) { return text; @@ -83,7 +95,7 @@ function makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFile(text: str return text.substring(0, startOfNewContent) + '\n' + text.substring(startOfContent); } -function makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFileForBlockquote(text: string, startOfLine: string, startOfContent: number): string { +function makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFileForBlockquote(text: string, startOfLine: string, startOfContent: number, isCallout: boolean = false): string { if (startOfContent === 0) { return text; } @@ -139,7 +151,7 @@ function makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFileForBlockqu priorLine = text.substring(indexOfLastNewLine, startOfNewContent); } - return text.substring(0, startOfNewContent) + getEmptyLine(priorLine) + text.substring(startOfContent); + return text.substring(0, startOfNewContent) + getEmptyLineForBlockqute(priorLine, isCallout, nestingLevel) + text.substring(startOfContent); } function makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFile(text: string, endOfContent: number): string { @@ -171,7 +183,7 @@ function makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFile(text: string return text.substring(0, endOfContent) + '\n' + text.substring(endOfNewContent); } -function makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFileForBlockquote(text: string, startOfLine: string, endOfContent: number): string { +function makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFileForBlockquote(text: string, startOfLine: string, endOfContent: number, isCallout: boolean = false): string { if (endOfContent === (text.length - 1)) { return text; } @@ -233,8 +245,7 @@ function makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFileForBlockquote nextLine = text.substring(endOfNewContent + 1, indexOfSecondNewLineAfterContent); } - - return text.substring(0, endOfContent) + getEmptyLine(nextLine) + text.substring(endOfNewContent); + return text.substring(0, endOfContent) + getEmptyLineForBlockqute(nextLine, isCallout, nestingLevel) + text.substring(endOfNewContent); } /** @@ -247,9 +258,10 @@ function makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFileForBlockquote export function makeSureContentHasEmptyLinesAddedBeforeAndAfter(text: string, start: number, end: number): string { const [startOfLine, startOfLineIndex] = getStartOfLineWhitespaceOrBlockquoteLevel(text, start); if (startOfLine.trim() !== '') { - const newText = makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFileForBlockquote(text, startOfLine, end); + const isCallout = calloutRegex.test(text.substring(start, end)); + const newText = makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFileForBlockquote(text, startOfLine, end, isCallout); - return makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFileForBlockquote(newText, startOfLine, startOfLineIndex); + return makeSureContentHasASingleEmptyLineBeforeItUnlessItStartsAFileForBlockquote(newText, startOfLine, startOfLineIndex, isCallout); } const newText = makeSureContentHasASingleEmptyLineAfterItUnlessItEndsAFile(text, end);