159 lines
No EOL
4.3 KiB
TypeScript
159 lines
No EOL
4.3 KiB
TypeScript
import { Accessor, createEffect } from "solid-js";
|
|
import { createStore } from "solid-js/store";
|
|
import { unified } from 'unified'
|
|
import { visit } from "unist-util-visit";
|
|
import { decode } from "~/utilities";
|
|
import remarkParse from 'remark-parse'
|
|
import remarkRehype from 'remark-rehype'
|
|
import remarkStringify from 'remark-stringify'
|
|
import rehypeParse from 'rehype-parse'
|
|
import rehypeDomParse from 'rehype-dom-parse'
|
|
import rehypeRemark from 'rehype-remark'
|
|
import rehypeStringify from 'rehype-stringify'
|
|
import type { Text, Root } from 'hast'
|
|
import { isServer } from "solid-js/web";
|
|
|
|
interface SourceStore {
|
|
in: string;
|
|
out: string;
|
|
plain: string;
|
|
query: string;
|
|
metadata: {
|
|
spellingErrors: [number, number][];
|
|
grammarErrors: [number, number][];
|
|
queryResults: [number, number][];
|
|
};
|
|
}
|
|
|
|
export interface Source {
|
|
in: string;
|
|
out: string;
|
|
query: string;
|
|
readonly spellingErrors: [number, number][];
|
|
readonly grammarErrors: [number, number][];
|
|
readonly queryResults: [number, number][];
|
|
}
|
|
|
|
// TODO :: make this configurable, right now we can only do markdown <--> html.
|
|
const inToOutProcessor = unified().use(remarkParse).use(remarkRehype).use(rehypeStringify);
|
|
const outToInProcessor = unified().use(isServer ? rehypeParse : rehypeDomParse).use(rehypeRemark).use(remarkStringify, { bullet: '-' });
|
|
|
|
export function createSource(value: Accessor<string>): Source {
|
|
const [store, setStore] = createStore<SourceStore>({ in: '', out: '', plain: '', query: '', metadata: { spellingErrors: [], grammarErrors: [], queryResults: [] } });
|
|
|
|
const src: Source = {
|
|
get in() {
|
|
return store.in;
|
|
},
|
|
set in(next) {
|
|
const ast = inToOutProcessor.runSync(inToOutProcessor.parse(next));
|
|
|
|
setStore({
|
|
in: next,
|
|
out: String(inToOutProcessor.stringify(ast)),
|
|
plain: String(unified().use(plainTextStringify).stringify(ast)),
|
|
});
|
|
},
|
|
|
|
get out() {
|
|
return store.out;
|
|
},
|
|
set out(next) {
|
|
const ast = outToInProcessor.parse(next);
|
|
|
|
setStore({
|
|
in: String(outToInProcessor.stringify(outToInProcessor.runSync(ast))).trim(),
|
|
out: next,
|
|
plain: String(unified().use(plainTextStringify).stringify(ast)),
|
|
});
|
|
},
|
|
|
|
get query() {
|
|
return store.query;
|
|
},
|
|
set query(next) {
|
|
setStore('query', next)
|
|
},
|
|
|
|
get spellingErrors() {
|
|
return store.metadata.spellingErrors;
|
|
},
|
|
|
|
get grammarErrors() {
|
|
return store.metadata.grammarErrors;
|
|
},
|
|
|
|
get queryResults() {
|
|
return store.metadata.queryResults;
|
|
},
|
|
};
|
|
|
|
createEffect(() => {
|
|
src.in = value();
|
|
});
|
|
src.in = value();
|
|
|
|
createEffect(() => {
|
|
const value = store.plain;
|
|
|
|
setStore('metadata', {
|
|
spellingErrors: spellChecker(value, ''),
|
|
grammarErrors: grammarChecker(value, ''),
|
|
});
|
|
});
|
|
|
|
createEffect(() => {
|
|
setStore('metadata', 'queryResults', findMatches(store.plain, store.query).toArray());
|
|
});
|
|
|
|
return src;
|
|
}
|
|
|
|
function plainTextStringify() {
|
|
this.compiler = function (tree: Root) {
|
|
const nodes: string[] = [];
|
|
|
|
visit(tree, n => n.type === 'text', (n) => {
|
|
nodes.push((n as Text).value);
|
|
});
|
|
|
|
return decode(nodes.join(''));
|
|
};
|
|
}
|
|
|
|
function* findMatches(text: string, query: string): Generator<[number, number], void, unknown> {
|
|
if (query.length < 1) {
|
|
return;
|
|
}
|
|
|
|
let startIndex = 0;
|
|
|
|
while (startIndex < text.length) {
|
|
const index = text.indexOf(query, startIndex);
|
|
|
|
if (index === -1) {
|
|
break;
|
|
}
|
|
|
|
const end = index + query.length;
|
|
|
|
yield [index, end];
|
|
|
|
startIndex = end;
|
|
}
|
|
}
|
|
|
|
const spellChecker = checker(/\w+/gi);
|
|
const grammarChecker = checker(/\w+\s+\w+/gi);
|
|
|
|
function checker(regex: RegExp) {
|
|
return (subject: string, lang: string): [number, number][] => {
|
|
return [];
|
|
|
|
const threshold = .75//.99;
|
|
|
|
return Array.from<RegExpExecArray>(subject.matchAll(regex)).filter(() => Math.random() >= threshold).map(({ 0: match, index }) => {
|
|
return [index, index + match.length] as const;
|
|
});
|
|
}
|
|
} |