Skip to content

Instantly share code, notes, and snippets.

@twavv
Last active August 16, 2019 18:51
Show Gist options
  • Save twavv/ae63368d8c646f6fadbb1cea53227884 to your computer and use it in GitHub Desktop.
Save twavv/ae63368d8c646f6fadbb1cea53227884 to your computer and use it in GitHub Desktop.
Slate Inline Focus Hack
import {
LATEX_INLINE_NODE_TYPE,
} from "../schema";
export const LatexInlineBackspacePlugin = () => ({
/**
* Handle key down when we're on the right edge of a `latexinline`.
* @param event
* @param editor
* @param next
* @returns {*}
*/
onKeyDown(event, editor, next) {
if (
event.key === "Backspace"
&& editor.value.selection.isCollapsed
&& editor.value.selection.focus.offset === 0
) {
const previousText = editor.value.previousText;
const previousInline = previousText && editor.value.document.getClosestInline(previousText.key);
if (previousInline && previousInline.type === LATEX_INLINE_NODE_TYPE) {
const point = {key: previousInline.key, offset: previousInline.text.length - 1};
event.preventDefault();
return (
editor
.select({
focus: point,
anchor: point,
})
);
}
}
return next();
},
});
import {Text} from "slate";
import LatexInline from "../components/LatexInline";
import {NODE_TEXT_INVALID} from "./slate-error-codes";
export const LATEX_INLINE_NODE_TYPE = "latexinline";
/*
* CURRENT BUG:
* Deleting on a latexinline doesn't work (it deletes the trailing space and
* then the normalization rule immediately adds it back). We could just have
* this condition be detected and make it equivalent to removing a character
* (this is **VERY MUCH** a hack), but this also puts the burden on other
* code to ensure they're always inserting LaTeX correctly (via a
* createInlineLatex function à la newParagraph).
*/
export const LATEX_INLINE_SCHEMA = {
/**
* Only allow a single text node as a child.
*/
nodes: [
{
match: {object: "text"},
min: 1,
max: 1,
},
],
/**
* Check validity of text. Ensures that text has a trailing space.
* @param text {string}
*/
text(text) {
return (
text.slice(text.length - 1, text.length).trim() === ""
);
},
normalize(editor, error) {
const {node} = error;
if (error.code === NODE_TEXT_INVALID) {
// The text node gets merged into the previous one by Slate's sanity processes,
// so the ultimate effect of this is to add a space to the end of the existing
// text node.
return editor.insertNodeByKey(node.key, node.nodes.size, Text.create({text: " "}));
}
},
};
export const LATEX_INLINE_COMPONENT = LatexInline;
import * as React from "react";
import {css} from "react-emotion";
import InlineLatex from "@/components/latex/InlineLatex";
// We remove 1ch (1 character width) from the right of the latexinline when it's
// in the "editor" mode to attempt to hide the hack where we require there to be
// an extra space at the end of the node.
const UNHIDDEN_SPAN_STYLE = css`
font-size: 110%;
margin-right: -1ch;
`;
const HIDDEN_SPAN_STYLE = css`
display: inline-block;
height: 0;
width: 0;
overflow: hidden;
color: transparent;
`;
const LatexInline = (props) => {
const {attributes, children, editor, node, isSelected} = props;
const onLatexClick = React.useCallback((event) => {
event.preventDefault();
// Points should always reference Text nodes.
const {key} = node.getFirstText();
editor
.select({
anchor: {key, offset: 0},
focus: {key, offset: 0},
})
.focus();
}, [editor, node]);
return (
<span
{...attributes}
>
<code
className={isSelected ? UNHIDDEN_SPAN_STYLE : HIDDEN_SPAN_STYLE}
>
{children}
</code>
{
isSelected
? null
: (
<InlineLatex
contentEditable={false}
onClick={onLatexClick}
>
{node.text}
</InlineLatex>
)
}
</span>
);
};
export default React.memo(LatexInline);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment