Skip to content

Commit

Permalink
Fix markup semantics (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
abhisaha1 committed Apr 16, 2019
1 parent bf3bfb2 commit 7772c53
Show file tree
Hide file tree
Showing 54 changed files with 1,962 additions and 1,121 deletions.
Binary file added .DS_Store
Binary file not shown.
3 changes: 3 additions & 0 deletions examples/demo-1/index.tsx
Expand Up @@ -37,6 +37,9 @@ class Demo extends Component {
onButtonClick={this.onButtonClick}
onBeforeRender={this.onBeforeRender}
spellCheck={false}
onChange={html => {
console.log(html);
}}
getCharCount={count => {
// count is available.
if (count) {
Expand Down
32 changes: 26 additions & 6 deletions index.html
Expand Up @@ -4,19 +4,15 @@
<meta charset=”UTF-8"> <meta name=”viewport” content=”width=device-width,
initial-scale=1.0"> <meta http-equiv=”X-UA-Compatible” content=”ie=edge”>
<title>Letterpad Editor Demo</title>
<link
<!-- <link
href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet"
/>
/> -->
<link
href="https://fonts.googleapis.com/css?family=Libre+Baskerville:400,400i,700|Libre+Franklin:200,200i,400,400i,700,700i"
rel="stylesheet"
/>

<link
href="https://cdn.jsdelivr.net/npm/prism-themes@1.0.1/themes/prism-atom-dark.css"
rel="stylesheet"
/>
<style type="text/css">
* {
box-sizing: border-box;
Expand All @@ -29,6 +25,30 @@
html {
font-size: 100%;
}
@font-face {
font-family: "Material Icons";
font-style: normal;
font-weight: 400;
src: local("Material Icons"), local("MaterialIcons-Regular"),
url(public/fonts/2fcrYFNaTjcS6g4U3t-Y5ZjZjT5FdEJ140U2DJYC3mY.woff2)
format("woff2");
}

.material-icons {
font-family: "Material Icons";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-moz-font-feature-settings: "liga";
-moz-osx-font-smoothing: grayscale;
}
</style>
</head>
<body>
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -18,6 +18,7 @@
"prismjs": "^1.15.0",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"showdown": "^1.9.0",
"slate": "^0.44.10",
"slate-auto-replace": "^0.12.1",
"slate-edit-code": "^0.15.5",
Expand All @@ -37,7 +38,7 @@
"@types/react": "^16.8.4",
"@types/react-dom": "^16.8.2",
"@types/react-test-renderer": "^16.8.1",
"@types/slate": "^0.43.9",
"@types/slate": "^0.44.3",
"@types/slate-html-serializer": "^0.6.3",
"@types/slate-react": "^0.20.3",
"css-loader": "^2.1.0",
Expand Down
Binary file added public/.DS_Store
Binary file not shown.
Binary file not shown.
30 changes: 25 additions & 5 deletions src/components/Button.tsx
@@ -1,20 +1,40 @@
import React, { SFC, MouseEventHandler } from "react";
import classnames from "classnames";
import styled from "styled-components";

const Wrapper = styled.span`
${(p: any) => p.styleString}
`;
interface ButtonProps {
onMouseDown: MouseEventHandler;
icon?: string;
icon: string;
isActive?: boolean;
styleString?: string;
}

const Button: SFC<ButtonProps> = ({ onMouseDown, isActive, icon }) => {
const Button: SFC<ButtonProps> = ({
onMouseDown,
isActive,
icon,
styleString
}) => {
const classes = classnames("button", {
active: isActive
});
const isCustomIcon = icon.indexOf(".") > 0;
return (
<span className={classes} onMouseDown={onMouseDown}>
<span className="material-icons">{icon}</span>
</span>
<Wrapper
styleString={styleString}
className={classes}
onMouseDown={onMouseDown}
icon={icon}
>
{isCustomIcon ? (
<span className="custom-icons" />
) : (
<span className="material-icons">{icon}</span>
)}
</Wrapper>
);
};

Expand Down
10 changes: 6 additions & 4 deletions src/components/Toolbar.tsx
Expand Up @@ -147,11 +147,13 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
}
});
if (menu.current) {
menu.current.querySelectorAll(".material-icons").forEach(node => {
node.addEventListener("mousedown", () => {
menuActive && setMenuActive(false);
menu.current
.querySelectorAll(".material-icons, .custom-icons")
.forEach(node => {
node.addEventListener("mousedown", () => {
menuActive && setMenuActive(false);
});
});
});
}

function showPlaceholder(component: ComponentType) {
Expand Down
4 changes: 3 additions & 1 deletion src/components/__tests__/Button.test.tsx
Expand Up @@ -6,7 +6,9 @@ describe("components", () => {
describe("Button", () => {
test("snapshot", () => {
const onMouseDown = jest.fn();
const component = renderer.create(<Button onMouseDown={onMouseDown} />);
const component = renderer.create(
<Button icon="test" onMouseDown={onMouseDown} />
);

const tree: any = component.toJSON();
expect(tree).toMatchSnapshot();
Expand Down
39 changes: 29 additions & 10 deletions src/editor.tsx
@@ -1,12 +1,11 @@
import React from "react";
import React, { Component } from "react";
import { Value, Editor } from "slate";
import {
Editor as SlateReactEditor,
EventHook,
getEventTransfer,
findDOMNode
} from "slate-react";
import { Component } from "react";
import AutoReplace from "slate-auto-replace";
import { Plugin as SlateReactPlugin } from "slate-react";
import {
Expand All @@ -26,6 +25,9 @@ import { showMenu } from "./helper/showMenu";
import { getRules } from "./helper/rules";
import Toolbar from "./components/Toolbar";
import { Theme } from "./theme.css";
import showdown from "showdown";

const converter = new showdown.Converter();

export interface LetterpadEditorProps {
onButtonClick(
Expand All @@ -35,6 +37,7 @@ export interface LetterpadEditorProps {
): void;
onBeforeRender(props: { type: string }): void;
getCharCount?(count: number): void;
onChange?(html: string, value?: Value): void;
width?: number;
theme?: string;
spellCheck?: boolean;
Expand All @@ -52,6 +55,7 @@ interface LetterpadEditorState {
slateReactPlugins: SlateReactPlugin[];
pluginsMap: PluginsMap;
value: Value;
html: string;
}

/**
Expand Down Expand Up @@ -106,6 +110,7 @@ function getInitialState(pluginConfigs: PluginConfig[]): LetterpadEditorState {
slateReactPlugins,
pluginsMap,
value: Value.fromJSON(initialValue),
html: "",
toolbarActive: false,
toolbarPosition: {
top: 0,
Expand All @@ -128,8 +133,11 @@ export class LetterpadEditor extends Component<
state = getInitialState(pluginConfigs);

onChange = ({ value }: { value: Value }) => {
this.setState({ value });
const html = this.html.serialize(value);
this.setState({ value, html });

// Everytime there is a change in the editor, we have to check if the cursor is
// inside an empty block. If so, then we display an additional toolbar
if (
!value.focusBlock ||
value.focusBlock.text ||
Expand All @@ -145,12 +153,15 @@ export class LetterpadEditor extends Component<
}
});
} else {
// else check if any text has been selected. If so, get the node of the cursor
let cursorNode;
try {
cursorNode = findDOMNode(this.editor!.value.focusBlock);
} catch (e) {}
if (cursorNode) {
// ge the position of the cursor
const { top, left, width } = cursorNode.getBoundingClientRect();
// display the menubar
this.setState({
toolbarActive: true,
toolbarPosition: {
Expand All @@ -166,7 +177,7 @@ export class LetterpadEditor extends Component<
const node = value.fragment.nodes.first();
const plugin = this.state.pluginsMap.node[node.type];
if (plugin) {
if (plugin.plugin.allowChildTransform === false) {
if (plugin.plugin.allowChildTransforms === false) {
if (this.menuRef && this.menuRef.current) {
this.menuRef.current.removeAttribute("style");
return;
Expand All @@ -178,7 +189,7 @@ export class LetterpadEditor extends Component<
const mark = value.activeMarks.first();
const plugin = this.state.pluginsMap.mark[mark.type];
if (plugin) {
if (plugin.plugin.allowChildTransform === false) {
if (plugin.plugin.allowChildTransforms === false) {
if (this.menuRef && this.menuRef.current) {
this.menuRef.current.removeAttribute("style");
return;
Expand All @@ -188,10 +199,15 @@ export class LetterpadEditor extends Component<
}
};

onPaste: EventHook = (event, editor, next) => {
onPaste: EventHook = (event, editor) => {
scrollToCursor();
const transfer = getEventTransfer(event);
if (transfer.type != "html") return next();
if (transfer.type != "html") {
// convert markdown to html
let html = converter.makeHtml((transfer as any).text);
const { document } = this.html.deserialize(html);
return editor.insertFragment(document);
}

const parentTag = editor.value.blocks.first().type; // p, pre, etc
for (let i = 0; i < pluginConfigs.length; i++) {
Expand All @@ -204,8 +220,7 @@ export class LetterpadEditor extends Component<
}
// remove style attr
const REMOVE_STYLE_ATTR = /style="[^\"]*"/gi;
const html = (transfer as any).html.replace(REMOVE_STYLE_ATTR, "");

let html = (transfer as any).html.replace(REMOVE_STYLE_ATTR, "");
// TODO: fix the transfer as any
const { document } = this.html.deserialize(html);
editor.insertFragment(document);
Expand Down Expand Up @@ -235,8 +250,12 @@ export class LetterpadEditor extends Component<
document.removeEventListener("keyup", this.hideMenu);
}

componentDidUpdate = () => {
componentDidUpdate = (_: any, prevState: any) => {
this.updateMenu();
const { html } = this.state;
if (typeof this.props.onChange === "function" && prevState.html !== html) {
this.props.onChange(html);
}
};

toggleToolbarClass = () => {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/audio/AudioButton.tsx
Expand Up @@ -10,7 +10,7 @@ const AudioButton: EditorButtonComponent = ({ editor, callbacks }) => {

return (
<Button
isActive={hasBlock(editor.value, type)}
isActive={hasBlock((editor as any).value, type)}
icon="queue_music"
onMouseDown={() => {
if (callbacks.showPlaceholder) {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/audio/AudioInput.tsx
Expand Up @@ -41,7 +41,7 @@ const AudioInput: FunctionComponent<any> = React.forwardRef(
onComplete();
// if the url is not empty
if (url) {
const isActive = hasBlock(editor.value, type);
const isActive = hasBlock((editor as any).value, type);
applyAudio(editor, isActive ? "p" : type, url);
} else {
editor.focus();
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/audio/AudioNode.tsx
Expand Up @@ -11,7 +11,7 @@ const AudioNode: SFC<{
}> = ({ attributes, node, children }) => {
const attrs = getAttributesFromNode(node);
return (
<audio {...attributes} controls {...attrs}>
<audio {...attributes} controls {...attrs} id="plugin-audio">
{children}
</audio>
);
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/audio/index.tsx
Expand Up @@ -12,7 +12,7 @@ const AudioPlugin: PluginConfig["slatePlugin"] = () => ({
onKeyDown(event, editor, next) {
const type = "audio";
if (isKeyboardEvent(event) && event.key === "Enter") {
const isActive = hasBlock(editor.value, type);
const isActive = hasBlock((editor as any).value, type);
if (isActive) {
event.preventDefault();
return editor.splitBlock(1).setBlocks("p");
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/blockquote/BlockquoteButton.tsx
Expand Up @@ -11,10 +11,10 @@ const BlockquoteButton: EditorButtonComponent = ({ editor }) => {

return (
<Button
isActive={hasBlock(editor.value, type)}
isActive={hasBlock((editor as any).value, type)}
icon="format_quote"
onMouseDown={() => {
const isActive = hasBlock(editor.value, type);
const isActive = hasBlock((editor as any).value, type);
return applyBlockquote(editor, isActive ? "p" : type);
}}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/blockquote/BlockquoteNode.tsx
Expand Up @@ -23,7 +23,7 @@ const BlockquoteNode: SFC<{
}> = ({ attributes, children, node }) => {
const attrs = getAttributesFromNode(node);
return (
<Wrapper {...attributes} {...attrs}>
<Wrapper {...attributes} {...attrs} data-id="plugin-blockquote">
{children}
</Wrapper>
);
Expand Down
5 changes: 3 additions & 2 deletions src/plugins/blockquote/index.tsx
Expand Up @@ -16,10 +16,10 @@ const BlockquotePlugin: PluginConfig["slatePlugin"] = () => ({
const type = "blockquote";
if (!isKeyboardEvent(event)) return;
if (isMod(event) && event.key === "/") {
const isActive = hasBlock(editor.value, type);
const isActive = hasBlock((editor as any).value, type);
return applyBlockquote(editor, isActive ? "p" : type);
} else if (event.key === "Enter") {
const isActive = hasBlock(editor.value, type);
const isActive = hasBlock((editor as any).value, type);
if (isActive) {
event.preventDefault();
return editor.splitBlock(1).setBlocks("p");
Expand All @@ -31,6 +31,7 @@ const BlockquotePlugin: PluginConfig["slatePlugin"] = () => ({

const blockquotePluginConfig: PluginConfig[] = [
{
name: "plugin-blockquote",
renderType: "node",
menuButtons: [
{
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/bold/BoldMark.tsx
@@ -1,6 +1,8 @@
import React, { SFC } from "react";

/* eslint-disable react/prop-types */
const BoldMark: SFC = ({ children }) => <strong>{children}</strong>;
const BoldMark: SFC = ({ children }) => (
<strong data-id="plugin-bold">{children}</strong>
);

export default BoldMark;
2 changes: 1 addition & 1 deletion src/plugins/codeblock/CodeblockButton.tsx
Expand Up @@ -8,7 +8,7 @@ import { EditorButtonComponent } from "..";
/* eslint-disable no-unused-vars */
const codeblockButton: EditorButtonComponent = ({ editor }) => (
<Button
isActive={hasBlock(editor.value, "u")}
isActive={hasBlock((editor as any).value, "u")}
icon="code"
onMouseDown={() => {
return applyCodeblock(editor);
Expand Down

0 comments on commit 7772c53

Please sign in to comment.