@uiw/react-md-editor
Version:
A markdown editor with preview, implemented with React.js and TypeScript.
103 lines (88 loc) • 3.1 kB
text/typescript
import { TextRange } from '../commands';
export interface TextSection {
text: string;
selection: TextRange;
}
export function selectWord({ text, selection }: TextSection): TextRange {
if (text && text.length && selection.start === selection.end) {
// the user is pointing to a word
return getSurroundingWord(text, selection.start);
}
return selection;
}
/**
* Gets the number of line-breaks that would have to be inserted before the given 'startPosition'
* to make sure there's an empty line between 'startPosition' and the previous text
*/
export function getBreaksNeededForEmptyLineBefore(text = '', startPosition: number): number {
if (startPosition === 0) return 0;
// rules:
// - If we're in the first line, no breaks are needed
// - Otherwise there must be 2 breaks before the previous character. Depending on how many breaks exist already, we
// may need to insert 0, 1 or 2 breaks
let neededBreaks = 2;
let isInFirstLine = true;
for (let i = startPosition - 1; i >= 0 && neededBreaks >= 0; i--) {
switch (text.charCodeAt(i)) {
case 32: // blank space
continue;
case 10: // line break
neededBreaks--;
isInFirstLine = false;
break;
default:
return neededBreaks;
}
}
return isInFirstLine ? 0 : neededBreaks;
}
/**
* Gets the number of line-breaks that would have to be inserted after the given 'startPosition'
* to make sure there's an empty line between 'startPosition' and the next text
*/
export function getBreaksNeededForEmptyLineAfter(text = '', startPosition: number): number {
if (startPosition === text.length - 1) return 0;
// rules:
// - If we're in the first line, no breaks are needed
// - Otherwise there must be 2 breaks before the previous character. Depending on how many breaks exist already, we
// may need to insert 0, 1 or 2 breaks
let neededBreaks = 2;
let isInLastLine = true;
for (let i = startPosition; i < text.length && neededBreaks >= 0; i++) {
switch (text.charCodeAt(i)) {
case 32:
continue;
case 10: {
neededBreaks--;
isInLastLine = false;
break;
}
default:
return neededBreaks;
}
}
return isInLastLine ? 0 : neededBreaks;
}
export function getSurroundingWord(text: string, position: number): TextRange {
if (!text) throw Error("Argument 'text' should be truthy");
const isWordDelimiter = (c: string) => c === ' ' || c.charCodeAt(0) === 10;
// leftIndex is initialized to 0 because if selection is 0, it won't even enter the iteration
let start = 0;
// rightIndex is initialized to text.length because if selection is equal to text.length it won't even enter the interation
let end = text.length;
// iterate to the left
for (let i = position; i - 1 > -1; i--) {
if (isWordDelimiter(text[i - 1])) {
start = i;
break;
}
}
// iterate to the right
for (let i = position; i < text.length; i++) {
if (isWordDelimiter(text[i])) {
end = i;
break;
}
}
return { start, end };
}