got to a nice point, right now I can do bi-directional transformations, and also have my spelling and grammar error markers that are also cleaned up so they don't end up in the source text
This commit is contained in:
parent
8e0eee5847
commit
f4d59b30f5
20 changed files with 414 additions and 279 deletions
|
@ -2,6 +2,8 @@
|
|||
|
||||
const regex = /\w+\s+\w+/gi;
|
||||
export function defaultChecker(subject: string, lang: string): [number, number][] {
|
||||
return [];
|
||||
|
||||
return Array.from<RegExpExecArray>(subject.matchAll(regex)).filter(() => Math.random() >= .5).map(({ 0: match, index }) => {
|
||||
return [index, index + match.length - 1];
|
||||
});
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
const regex = /\w+/gi;
|
||||
export function defaultChecker(subject: string, lang: string): [number, number][] {
|
||||
return [];
|
||||
|
||||
return Array.from<RegExpExecArray>(subject.matchAll(regex)).filter(() => Math.random() >= .5).map(({ 0: match, index }) => {
|
||||
return [index, index + match.length - 1];
|
||||
});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
.textarea {
|
||||
/* Make sure resizing works as intended */
|
||||
display: block;
|
||||
overflow: clip auto;
|
||||
resize: block;
|
||||
|
@ -9,16 +8,13 @@
|
|||
max-block-size: 50em;
|
||||
|
||||
unicode-bidi: plaintext;
|
||||
white-space-collapse: preserve;
|
||||
text-wrap-mode: wrap;
|
||||
overflow-wrap: break-word;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.spellingError {
|
||||
text-decoration-line: spelling-error;
|
||||
}
|
||||
& [data-marker="spelling"] {
|
||||
text-decoration-line: spelling-error;
|
||||
}
|
||||
|
||||
.grammarError {
|
||||
text-decoration-line: grammar-error;
|
||||
& [data-marker="grammar"] {
|
||||
text-decoration-line: grammar-error;
|
||||
}
|
||||
}
|
|
@ -1,109 +1,71 @@
|
|||
import { createEffect, createMemo, createSignal, untrack } from 'solid-js';
|
||||
import { decode } from '~/utilities';
|
||||
import { createEffect, createMemo, untrack } from 'solid-js';
|
||||
import { debounce } from '@solid-primitives/scheduled';
|
||||
import { createSelection } from '@solid-primitives/selection';
|
||||
import { defaultChecker as spellChecker } from './spellChecker';
|
||||
import { defaultChecker as grammarChecker } from './grammarChecker';
|
||||
import { createSource } from '~/features/source';
|
||||
import css from './textarea.module.css';
|
||||
|
||||
interface TextareaProps {
|
||||
class?: string;
|
||||
value: string;
|
||||
lang: string;
|
||||
oninput?: (event: InputEvent) => any;
|
||||
placeholder?: string;
|
||||
oninput?: (next: string) => any;
|
||||
spellChecker?: any;
|
||||
grammarChecker?: any;
|
||||
}
|
||||
|
||||
export function Textarea(props: TextareaProps) {
|
||||
const [selection, setSelection] = createSelection();
|
||||
const [value, setValue] = createSignal<string>(decode(props.value));
|
||||
const [element, setElement] = createSignal<HTMLTextAreaElement>();
|
||||
|
||||
const source = createSource(props.value);
|
||||
|
||||
createEffect(() => {
|
||||
setValue(decode(props.value));
|
||||
props.oninput?.(source.in);
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
// For tracking
|
||||
value();
|
||||
source.in = props.value;
|
||||
});
|
||||
|
||||
const root = untrack(() => element());
|
||||
const onInput = debounce(() => {
|
||||
const [el, start, end] = untrack(() => selection());
|
||||
|
||||
if (el !== root) {
|
||||
return;
|
||||
if (el) {
|
||||
source.out = el.innerHTML;
|
||||
|
||||
el.style.height = `1px`;
|
||||
el.style.height = `${2 + el.scrollHeight}px`;
|
||||
|
||||
setSelection([el, start, end]);
|
||||
}
|
||||
|
||||
// TODO :: this needs to be calculated based on the modification done
|
||||
const offset = 1;
|
||||
|
||||
setSelection([el, start + offset, end + offset]);
|
||||
});
|
||||
|
||||
const resize = () => {
|
||||
const el = element();
|
||||
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
el.style.height = `1px`;
|
||||
el.style.height = `${2 + element()!.scrollHeight}px`;
|
||||
};
|
||||
|
||||
const mutate = debounce(() => {
|
||||
props.oninput?.(new InputEvent('input', {
|
||||
data: value(),
|
||||
}))
|
||||
}, 300);
|
||||
|
||||
const onInput = (e: InputEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const spellingErrors = createMemo(() => spellChecker(source.out, props.lang));
|
||||
const grammarErrors = createMemo(() => grammarChecker(source.out, props.lang));
|
||||
|
||||
console.log(e);
|
||||
console.log(target.innerText, target.textContent, target.innerHTML);
|
||||
};
|
||||
// const html = createMemo(() => {
|
||||
// return source.out.split('').map((letter, index) => {
|
||||
// const spellingOpen = spellingErrors().some(([start]) => start === index) ? `<span class="${css.spellingError}">` : '';
|
||||
// const spellingClose = spellingErrors().some(([, end]) => end === index) ? `</span>` : '';
|
||||
|
||||
const onKeyUp = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
// const grammarOpen = grammarErrors().some(([start]) => start === index) ? `<span class="${css.grammarError}">` : '';
|
||||
// const grammarClose = grammarErrors().some(([, end]) => end === index) ? `</span>` : '';
|
||||
|
||||
setValue(element()!.textContent!);
|
||||
|
||||
resize();
|
||||
mutate();
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// const spellingErrors = createMemo(() => spellChecker(value(), props.lang));
|
||||
// const grammarErrors = createMemo(() => grammarChecker(value(), props.lang));
|
||||
const spellingErrors = createMemo(() => []);
|
||||
const grammarErrors = createMemo(() => []);
|
||||
|
||||
const html = createMemo(() => {
|
||||
return value().split('').map((letter, index) => {
|
||||
const spellingOpen = spellingErrors().some(([start]) => start === index) ? `<span class="${css.spellingError}">` : '';
|
||||
const spellingClose = spellingErrors().some(([, end]) => end === index) ? `</span>` : '';
|
||||
|
||||
const grammarOpen = grammarErrors().some(([start]) => start === index) ? `<span class="${css.grammarError}">` : '';
|
||||
const grammarClose = grammarErrors().some(([, end]) => end === index) ? `</span>` : '';
|
||||
|
||||
return `${grammarOpen}${spellingOpen}${letter}${spellingClose}${grammarClose}`;
|
||||
}).join('');
|
||||
});
|
||||
// return `${grammarOpen}${spellingOpen}${letter}${spellingClose}${grammarClose}`;
|
||||
// }).join('');
|
||||
// });
|
||||
|
||||
return <div
|
||||
ref={setElement}
|
||||
class={`${css.textarea} ${props.class}`}
|
||||
lang={props.lang}
|
||||
contentEditable
|
||||
dir="auto"
|
||||
lang={props.lang}
|
||||
oninput={onInput}
|
||||
onkeyup={onKeyUp}
|
||||
innerHTML={source.out}
|
||||
data-placeholder={props.placeholder ?? ''}
|
||||
on:keydown={e => e.stopPropagation()}
|
||||
on:pointerdown={e => e.stopPropagation()}
|
||||
contentEditable
|
||||
innerHTML={html()}
|
||||
/>;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue