forked from mirror/cinny
remove markdown editor
This commit is contained in:
parent
a430a6af35
commit
2007d635e6
4 changed files with 9 additions and 342 deletions
|
@ -8,7 +8,7 @@ import React, {
|
|||
useState,
|
||||
} from 'react';
|
||||
import { Box, Scroll, Text } from 'folds';
|
||||
import { Text as SlateText, Descendant, Editor, createEditor, NodeEntry } from 'slate';
|
||||
import { Descendant, Editor, createEditor } from 'slate';
|
||||
import {
|
||||
Slate,
|
||||
Editable,
|
||||
|
@ -19,13 +19,10 @@ import {
|
|||
} from 'slate-react';
|
||||
import { withHistory } from 'slate-history';
|
||||
import { BlockType } from './types';
|
||||
import { RenderElement } from './Elements';
|
||||
import { RenderElement, RenderLeaf } from './Elements';
|
||||
import { CustomElement } from './slate';
|
||||
import { parseBlockMD, parseInlineMD } from '../../plugins/markdown';
|
||||
import type { SingleASTNode } from '@khanacademy/simple-markdown';
|
||||
import '../../molecules/markdown-input/MarkdownInput.scss';
|
||||
import * as css from './Editor.css';
|
||||
// import { toggleKeyboardShortcut } from './keyboard';
|
||||
import { toggleKeyboardShortcut } from './keyboard';
|
||||
|
||||
const initialValue: CustomElement[] = [
|
||||
{
|
||||
|
@ -60,24 +57,6 @@ export const useEditor = (): Editor => {
|
|||
return editor;
|
||||
};
|
||||
|
||||
function Leaf({
|
||||
attributes,
|
||||
children,
|
||||
leaf,
|
||||
}: {
|
||||
attributes: string[];
|
||||
children: string[];
|
||||
leaf: {
|
||||
classes?: string[];
|
||||
};
|
||||
}) {
|
||||
return (
|
||||
<span {...attributes} className={leaf.classes?.join(' ')}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export type EditorChangeHandler = (value: Descendant[]) => void;
|
||||
type CustomEditorProps = {
|
||||
editableName?: string;
|
||||
|
@ -116,10 +95,14 @@ export const CustomEditor = forwardRef<HTMLDivElement, CustomEditorProps>(
|
|||
[]
|
||||
);
|
||||
|
||||
const renderLeaf = useCallback((props: RenderLeafProps) => <Leaf {...props} />, []);
|
||||
const renderLeaf = useCallback((props: RenderLeafProps) => <RenderLeaf {...props} />, []);
|
||||
|
||||
const handleKeydown: KeyboardEventHandler = useCallback(
|
||||
(evt) => onKeyDown?.(evt),
|
||||
(evt) => {
|
||||
onKeyDown?.(evt);
|
||||
const shortcutToggled = toggleKeyboardShortcut(editor, evt);
|
||||
if (shortcutToggled) evt.preventDefault();
|
||||
},
|
||||
[editor, onKeyDown]
|
||||
);
|
||||
|
||||
|
@ -140,86 +123,6 @@ export const CustomEditor = forwardRef<HTMLDivElement, CustomEditorProps>(
|
|||
);
|
||||
}, []);
|
||||
|
||||
const decorate = useCallback(([node, path]: NodeEntry) => {
|
||||
const ranges = [];
|
||||
if (!SlateText.isText(node)) {
|
||||
return ranges;
|
||||
}
|
||||
|
||||
let position = 0;
|
||||
|
||||
// let bold = false;
|
||||
// let italic = false;
|
||||
const enabledStyles = new Set<string>();
|
||||
|
||||
function readSyntax(text: string) {
|
||||
const start = position;
|
||||
// read bytes until the first character of item.content is found
|
||||
let prefix = '';
|
||||
while (position < node.text.length && node.text[position] !== text[0]) {
|
||||
prefix += node.text[position];
|
||||
position += 1;
|
||||
}
|
||||
if (start !== position) {
|
||||
ranges.push({
|
||||
anchor: { path, offset: start },
|
||||
focus: { path, offset: start + prefix.length },
|
||||
classes: ['syntax'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addRanges(content: SingleASTNode[] | string) {
|
||||
if (typeof content === 'string') {
|
||||
const start = position;
|
||||
readSyntax(content);
|
||||
const prefixLength = position - start;
|
||||
position += content.length;
|
||||
if (enabledStyles.size > 0) {
|
||||
ranges.push({
|
||||
anchor: { path, offset: start + prefixLength },
|
||||
focus: { path, offset: position },
|
||||
classes: [...enabledStyles],
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < content.length; i += 1) {
|
||||
const item = content[i];
|
||||
if (item.content) {
|
||||
let style: string | undefined;
|
||||
if (item.type === 'strong') style = 'bold';
|
||||
if (item.type === 'em') style = 'italic';
|
||||
if (item.type === 'inlineCode') style = 'inline-code';
|
||||
if (item.type === 'del') style = 'strikethrough';
|
||||
if (item.type === 'u') style = 'underline';
|
||||
|
||||
if (style) enabledStyles.add(style);
|
||||
addRanges(item.content);
|
||||
if (style) enabledStyles.delete(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// const emojis = getShortcodeToEmoji(this.matrixClient, [room, ...parentRooms]);
|
||||
|
||||
// const content = parseBlockMD(node.text, parseInlineMD);
|
||||
|
||||
// addRanges(content);
|
||||
|
||||
// add final syntax range
|
||||
if (position < node.text.length) {
|
||||
ranges.push({
|
||||
anchor: { path, offset: position },
|
||||
focus: { path, offset: node.text.length },
|
||||
classes: ['syntax'],
|
||||
});
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={css.Editor} ref={ref}>
|
||||
<Slate editor={editor} initialValue={initialValue} onChange={onChange}>
|
||||
|
@ -239,7 +142,6 @@ export const CustomEditor = forwardRef<HTMLDivElement, CustomEditorProps>(
|
|||
hideTrack
|
||||
>
|
||||
<Editable
|
||||
decorate={decorate}
|
||||
data-editable-name={editableName}
|
||||
className={css.EditorTextarea}
|
||||
placeholder={placeholder}
|
||||
|
|
|
@ -320,7 +320,6 @@ const useTimelinePagination = (
|
|||
|
||||
return async (backwards: boolean) => {
|
||||
if (fetching) return;
|
||||
const targetTimeline = timelineRef.current;
|
||||
const { linkedTimelines: lTimelines } = timelineRef.current;
|
||||
const timelinesEventsCount = lTimelines.map(timelineToEventsCount);
|
||||
|
||||
|
@ -360,7 +359,6 @@ const useTimelinePagination = (
|
|||
}
|
||||
|
||||
fetching = false;
|
||||
if (targetTimeline !== timelineRef.current) return;
|
||||
if (alive()) {
|
||||
recalibratePagination(lTimelines, timelinesEventsCount, backwards);
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
.markdown-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 0px;
|
||||
height: 100%;
|
||||
font-size: var(--fs-b1);
|
||||
|
||||
background-color: var(--bg-surface-low);
|
||||
color: var(--tc-surface-normal);
|
||||
|
||||
border-radius: var(--bo-radius);
|
||||
border: 1px solid var(--bg-surface-border);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.markdown-input__editable {
|
||||
padding: var(--sp-ultra-tight) calc(var(--sp-tight) - 2px);
|
||||
width: 100%;
|
||||
// the outline adds a blue border on firefox when selected which looks ugly
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.inline-code {
|
||||
font-family: monospace;
|
||||
color: var(--tc-code) !important;
|
||||
}
|
||||
|
||||
.syntax {
|
||||
color: var(--tc-surface-low);
|
||||
}
|
||||
|
||||
.read-only {
|
||||
cursor: not-allowed;
|
||||
}
|
|
@ -1,185 +0,0 @@
|
|||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { Editable, ReactEditor, Slate, withReact } from 'slate-react';
|
||||
import { withHistory } from 'slate-history';
|
||||
import { Text, Descendant, createEditor } from 'slate';
|
||||
import './MarkdownInput.scss';
|
||||
import { SingleASTNode } from '@khanacademy/simple-markdown';
|
||||
import PropTypes from 'prop-types';
|
||||
import { mdParser } from '../../../util/markdown';
|
||||
import { getShortcodeToEmoji } from '../../organisms/emoji-board/custom-emoji';
|
||||
|
||||
function Leaf({
|
||||
attributes,
|
||||
children,
|
||||
leaf,
|
||||
}: {
|
||||
attributes: string[];
|
||||
children: string[];
|
||||
leaf: {
|
||||
classes?: string[];
|
||||
};
|
||||
}) {
|
||||
return (
|
||||
<span {...attributes} className={leaf.classes?.join(' ')}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
Leaf.propTypes = {
|
||||
attributes: PropTypes.arrayOf(PropTypes.string),
|
||||
children: PropTypes.arrayOf(PropTypes.string),
|
||||
leaf: PropTypes.shape({
|
||||
classes: PropTypes.arrayOf(PropTypes.string),
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Flatten Slate nodes into a single string.
|
||||
* @param nodes Slate nodes, you can get this from ReactEditor.children
|
||||
* @returns The flattened string
|
||||
*/
|
||||
export function flattenNodes(nodes: Descendant[]): string {
|
||||
const flat = nodes
|
||||
.map((node) => {
|
||||
if (Text.isText(node)) {
|
||||
return node.text;
|
||||
}
|
||||
return flattenNodes(node.children);
|
||||
})
|
||||
.join('\n');
|
||||
return flat;
|
||||
}
|
||||
|
||||
export function MarkdownInput({
|
||||
onChange,
|
||||
onPaste,
|
||||
onKeyDown,
|
||||
placeholder,
|
||||
onCreateEditor,
|
||||
readOnly,
|
||||
}: {
|
||||
onChange: (value: Descendant[]) => void;
|
||||
onPaste: (event: React.ClipboardEvent<HTMLDivElement>) => void;
|
||||
onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
placeholder: string;
|
||||
onCreateEditor: (editor: ReactEditor) => void;
|
||||
readOnly?: boolean;
|
||||
}) {
|
||||
const renderLeaf = useCallback((props) => <Leaf {...props} />, []);
|
||||
const editor = useMemo(() => withHistory(withReact(createEditor())), []);
|
||||
onCreateEditor(editor);
|
||||
|
||||
const decorate = useCallback(([node, path]) => {
|
||||
const ranges = [];
|
||||
if (!Text.isText(node)) {
|
||||
return ranges;
|
||||
}
|
||||
|
||||
let position = 0;
|
||||
|
||||
// let bold = false;
|
||||
// let italic = false;
|
||||
const enabledStyles = new Set<string>();
|
||||
|
||||
function readSyntax(text: string) {
|
||||
const start = position;
|
||||
// read bytes until the first character of item.content is found
|
||||
let prefix = '';
|
||||
while (position < node.text.length && node.text[position] !== text[0]) {
|
||||
prefix += node.text[position];
|
||||
position += 1;
|
||||
}
|
||||
if (start !== position) {
|
||||
ranges.push({
|
||||
anchor: { path, offset: start },
|
||||
focus: { path, offset: start + prefix.length },
|
||||
classes: ['syntax'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addRanges(content: SingleASTNode[] | string) {
|
||||
if (typeof content === 'string') {
|
||||
const start = position;
|
||||
readSyntax(content);
|
||||
const prefixLength = position - start;
|
||||
position += content.length;
|
||||
if (enabledStyles.size > 0) {
|
||||
ranges.push({
|
||||
anchor: { path, offset: start + prefixLength },
|
||||
focus: { path, offset: position },
|
||||
classes: [...enabledStyles],
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < content.length; i += 1) {
|
||||
const item = content[i];
|
||||
if (item.content) {
|
||||
let style: string | undefined;
|
||||
if (item.type === 'strong') style = 'bold';
|
||||
if (item.type === 'em') style = 'italic';
|
||||
if (item.type === 'inlineCode') style = 'inline-code';
|
||||
if (item.type === 'del') style = 'strikethrough';
|
||||
if (item.type === 'u') style = 'underline';
|
||||
|
||||
if (style) enabledStyles.add(style);
|
||||
addRanges(item.content);
|
||||
if (style) enabledStyles.delete(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// const emojis = getShortcodeToEmoji(this.matrixClient, [room, ...parentRooms]);
|
||||
|
||||
const content = mdParser(node.text, {
|
||||
userNames: [],
|
||||
emojis: {},
|
||||
});
|
||||
|
||||
addRanges(content);
|
||||
|
||||
// add final syntax range
|
||||
if (position < node.text.length) {
|
||||
ranges.push({
|
||||
anchor: { path, offset: position },
|
||||
focus: { path, offset: node.text.length },
|
||||
classes: ['syntax'],
|
||||
});
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}, []);
|
||||
|
||||
const initialValue: Descendant[] = [
|
||||
{
|
||||
children: [{ text: '' }],
|
||||
},
|
||||
];
|
||||
|
||||
const [isEmpty, setIsEmpty] = useState(true);
|
||||
|
||||
const onChangeInternal = (value: Descendant[]) => {
|
||||
const text = flattenNodes(value);
|
||||
setIsEmpty(text.length === 0);
|
||||
if (onChange) onChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`markdown-input${readOnly ? ' read-only' : ''}`}>
|
||||
<Slate editor={editor} initialValue={initialValue} onChange={onChangeInternal}>
|
||||
<Editable
|
||||
decorate={decorate}
|
||||
renderLeaf={renderLeaf}
|
||||
placeholder={placeholder}
|
||||
onKeyDown={onKeyDown}
|
||||
onPaste={onPaste}
|
||||
readOnly={readOnly}
|
||||
className={`markdown-input__editable${isEmpty ? ' empty' : ''}`}
|
||||
/>
|
||||
</Slate>
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue