Skip to content

Commit

Permalink
Add history functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Skalakid committed Jan 18, 2024
1 parent 854d388 commit 7dd23be
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 26 deletions.
104 changes: 80 additions & 24 deletions src/MarkdownTextInput.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as CursorUtils from './web/cursorUtils';
import * as StyleUtils from './styleUtils';
import type * as MarkdownTextInputDecoratorViewNativeComponent from './MarkdownTextInputDecoratorViewNativeComponent';
import './web/MarkdownTextInput.css';
import MarkdownInputHistory from './web/historyClass';

// TODO: remove require
require('../parser/react-native-live-markdown-parser.js');
Expand All @@ -38,6 +39,10 @@ interface MarkdownTextInputProps extends TextInputProps {
dir?: string;
}

interface MarkdownNativeEvent extends Event {
inputType: string;
}

type Selection = {
start: number;
end: number;
Expand All @@ -56,7 +61,6 @@ function isEventComposing(nativeEvent: globalThis.KeyboardEvent) {
return nativeEvent.isComposing || nativeEvent.keyCode === 229;
}

const parseText = ParseUtils.parseText;
const getCurrentCursorPosition = CursorUtils.getCurrentCursorPosition;
const setCursorPosition = CursorUtils.setCursorPosition;

Expand Down Expand Up @@ -95,6 +99,32 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
const contentSelection = useRef<Selection | null>(null);
const className = `react-native-live-markdown-input-${multiline ? 'multiline' : 'single-line'}`;

const history = useRef(new MarkdownInputHistory(50));

function parseText(target: HTMLDivElement, text: string, markdownStyles: MarkdownStyle, singleLine: boolean) {
const parsedText = ParseUtils.parseText(target, text, markdownStyles, singleLine);
history.current.debouncedAdd(parsedText);
return parsedText;
}

const undo = (target: HTMLDivElement, markdownStyles: MarkdownStyle, singleLine: boolean) => {
const text = history.current.undo();
if (text === null) {
return target.innerText;
}
ParseUtils.parseText(target, text, markdownStyles, singleLine, true);
return text;
};

const redo = (target: HTMLDivElement, markdownStyles: MarkdownStyle, singleLine: boolean) => {
const text = history.current.redo();
if (text === null) {
return target.innerText;
}
ParseUtils.parseText(target, text, markdownStyles, singleLine, true);
return text;
};

const processedMarkdownStyle = React.useMemo(() => {
const newMarkdownStyle = StyleUtils.mergeMarkdownStyleWithDefault(markdownStyle, true);

Expand Down Expand Up @@ -130,11 +160,60 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
node.style.color = String(placeholder && (text === '' || text === '\n') ? placeholderTextColor : (style as TextStyle).color || 'black');
}, []);

const handleOnChangeText = useCallback(
(e: FormEvent<HTMLDivElement>) => {
if (!divRef.current || !(e.target instanceof HTMLElement)) {
return;
}

let text = '';
const nativeEvent = e.nativeEvent as MarkdownNativeEvent;
if (nativeEvent.inputType === 'historyUndo') {
text = undo(divRef.current, processedMarkdownStyle, !multiline);
} else if (nativeEvent.inputType === 'historyRedo') {
text = redo(divRef.current, processedMarkdownStyle, !multiline);
} else {
text = parseText(divRef.current, e.target.innerText, processedMarkdownStyle, !multiline);
}

updateTextColor(divRef.current, e.target.innerText);

if (onChange) {
const event = e as unknown as NativeSyntheticEvent<any>;
setEventProps(event);
onChange(event);
}

if (onChangeText) {
const normalizedText = normalizeValue(text);
onChangeText(normalizedText);
}
},
[multiline, onChange, onChangeText, setEventProps],
);

const handleKeyPress = useCallback(
(e: KeyboardEvent<HTMLDivElement>) => {
if (!divRef.current) {
return;
}

const hostNode = e.target;
e.stopPropagation();

if (e.key === 'z' && e.metaKey) {
e.preventDefault();
const nativeEvent = e.nativeEvent as unknown as MarkdownNativeEvent;
if (e.shiftKey) {
nativeEvent.inputType = 'historyRedo';
} else {
nativeEvent.inputType = 'historyUndo';
}

handleOnChangeText(e);
return;
}

const blurOnSubmitDefault = !multiline;
const shouldBlurOnSubmit = blurOnSubmit === null ? blurOnSubmitDefault : blurOnSubmit;

Expand Down Expand Up @@ -173,29 +252,6 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
[onKeyPress],
);

const handleOnChangeText = useCallback(
(e: FormEvent<HTMLDivElement>) => {
if (!divRef.current || !(e.target instanceof HTMLElement)) {
return;
}

updateTextColor(divRef.current, e.target.innerText);

const text = parseText(divRef.current, e.target.innerText, processedMarkdownStyle, !multiline);
if (onChange) {
const event = e as unknown as NativeSyntheticEvent<any>;
setEventProps(event);
onChange(event);
}

if (onChangeText) {
const normalizedText = normalizeValue(text);
onChangeText(normalizedText);
}
},
[multiline, onChange, onChangeText, setEventProps],
);

const handleSelectionChange: ReactEventHandler<HTMLDivElement> = useCallback(
(event) => {
const e = event as unknown as NativeSyntheticEvent<TextInputSelectionChangeEventData>;
Expand Down
79 changes: 79 additions & 0 deletions src/web/historyClass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
export default class MarkdownInputHistory {
depth: number;

history: string[];

pointerIndex: number;

currentlyWritten: string | null = null;

timeout: NodeJS.Timeout | null = null;

debounceTime: number;

constructor(historyDepth: number, debounceTime = 200) {
this.depth = historyDepth;
this.history = [];
this.pointerIndex = 0;
this.debounceTime = debounceTime;
}

debouncedAdd(text: string): void {
this.currentlyWritten = text;

if (this.timeout) {
clearTimeout(this.timeout);
}

this.timeout = setTimeout(() => {
if (this.currentlyWritten == null) {
return;
}
this.add(this.currentlyWritten);
this.currentlyWritten = null;
}, this.debounceTime);
}

add(text: string): void {
if (this.pointerIndex < this.history.length - 1) {
this.history.splice(this.pointerIndex + 1);
}

this.history.push(text);
if (this.history.length > this.depth) {
this.history.shift();
}

this.pointerIndex = this.history.length - 1;
}

undo(): string | null {
if (this.currentlyWritten !== null && this.timeout) {
clearTimeout(this.timeout);
return this.history[this.history.length - 1] || this.currentlyWritten || null;
}

if (this.history.length === 0) {
return null;
}

if (this.pointerIndex >= 0) {
this.pointerIndex -= 1;
}
return this.history[this.pointerIndex] || null;
}

redo(): string | null {
if (this.currentlyWritten !== null && this.timeout) {
return this.currentlyWritten;
}
if (this.history.length === 0) {
return null;
}

if (this.pointerIndex < this.history.length - 1) {
this.pointerIndex += 1;
}
return this.history[this.pointerIndex] || null;
}
}
6 changes: 4 additions & 2 deletions src/web/parserUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ function parseRangesToHTMLNodes(text: string, ranges: MarkdownRange[], markdownS
return root;
}

function parseText(target: HTMLElement, text: string, markdownStyle: PartialMarkdownStyle = {}, disableNewLinesInCursorPositioning = false) {
function parseText(target: HTMLElement, text: string, markdownStyle: PartialMarkdownStyle = {}, disableNewLinesInCursorPositioning = false, alwaysMoveToTheEnd = false) {
const targetElement = target;

let cursorPosition: number | null = null;
Expand All @@ -194,7 +194,9 @@ function parseText(target: HTMLElement, text: string, markdownStyle: PartialMark
target.appendChild(dom);
}

if (isFocused && cursorPosition !== null) {
if (alwaysMoveToTheEnd) {
CursorUtils.moveCursorToEnd(target);
} else if (isFocused && cursorPosition !== null) {
CursorUtils.setCursorPosition(target, cursorPosition, disableNewLinesInCursorPositioning);
}

Expand Down

0 comments on commit 7dd23be

Please sign in to comment.