squa-doc-js v0.5.9
SquaDoc.js
A rich text editor for React, built around Quill's Delta to provide collaborative capabilities.
Inspired by Draft.js, Quill and Slate.
Installation
npm install --save squa-doc-jsQuickstart
import React, { PureComponent } from "react";
import { Value, Editor } from "squa-doc-js";
const { value } = Value.createEmpty()
.change()
.insertText("Hello, World!");
class App extends PureComponent {
state = { value };
render() {
return <Editor value={this.state.value} onChange={this.onChange} />;
}
onChange = ({ value }) => {
this.setState({ value });
};
}Creating a Value from a Delta, and converting a Value to a Delta:
import Delta from "quill-delta";
import { Value } from "squa-doc-js";
const value = Value.fromDelta({
contents: new Delta().insert("Hello, World!\n")
});
const delta = value.toDelta();Creating a Value from JSON, and converting a Value to JSON:
import { Value } from "squa-doc-js";
const value = Value.fromJSON({
contents: [{ insert: "Hello, World!\n" }]
});
const data = value.toJSON();Creating a Value from HTML, and getting the editor's contents as HTML:
import { Value } from "squa-doc-js";
const value = Value.fromHTML({
contents: "<p>Hello, World!</p>"
});
// you can get the HTML contents with document.querySelector
// or with a react reference
const data = document.querySelector(".SquaDocJs-document").innerHTML;API basics
Formatting blocks
import React, { PureComponent } from "react";
import { Value, Editor } from "squa-doc-js";
class App extends PureComponent {
state = { value: Value.createEmpty() };
render() {
return (
<div>
<button value="heading-one" onClick={this.handleBlockTypeClick}>
H1
</button>
<button value="heading-two" onClick={this.handleBlockTypeClick}>
H2
</button>
<Editor value={this.state.value} onChange={this.onChange} />
</div>
);
}
handleBlockTypeClick = event => {
this.onChange(
this.state.value
.change()
.toggleBlockAttribute("type", event.target.value)
.save()
);
};
onChange = ({ value }) => {
this.setState({ value });
};
}The following block formats are supported by default:
typeheading-oneheading-twoheading-threeheading-fourheading-fiveheading-sixunordered-list-itemordered-list-itemparagraphblockquotecode
alignleftrightcenterjustify
indent- Available for the following types:unordered-list-itemordered-list-item
Formatting text and inline embed elements
import React, { PureComponent } from "react";
import { Value, Editor } from "squa-doc-js";
class App extends PureComponent {
state = { value: Value.createEmpty() };
render() {
return (
<div>
<button value="bold" onClick={this.handleInlineFormatClick}>
bold
</button>
<button value="italic" onClick={this.handleInlineFormatClick}>
italic
</button>
<button onClick={this.handleLinkClick}>link</button>
<Editor value={this.state.value} onChange={this.onChange} />
</div>
);
}
handleInlineFormatClick = event => {
this.onChange(
this.state.value
.change()
.toggleInlineAttribute(event.target.value, true)
.save()
);
};
handleLinkClick = event => {
this.onChange(
this.state.value
.change()
.setInlineAttribute("link", window.prompt())
.save()
);
};
onChange = ({ value }) => {
this.setState({ value });
};
}The following inline attributes are supported by default:
anchorlinkcolorsilvergraymaroonredpurplefuchsiagreenlimeoliveyellownavybluetealaqua
bolditalicunderlinestrikethroughcode
Customization
Schema
The editor uses an object called schema to validate embed elements and styles. Therefore, the first step to have custom embed elements or styles is to define your own schema. For example, you can define a custom inline style like this:
import Delta from "quill-delta";
import { NodeType, Value, Editor } from "squa-doc-js";
const schema = {
isInlineMark(name) {
return name === "highlight";
}
};
const value = Value.fromDelta({
schema,
contents: new Delta().insert("foo", { highlight: true })
});Customs schemas have to implement the following interface:
{
isBlockEmbed?: (embedName: string) => boolean;
isInlineEmbed?: (embedName: string) => boolean;
isTableMark?: (markName: string) => boolean;
isRowMark?: (markName: string) => boolean;
isCellMark?: (markName: string) => boolean;
isBlockMark?: (markName: string) => boolean;
isTextMark?: (markName: string) => boolean;
isBlockEmbedMark?: (embedName: string, markName: string) => boolean;
isInlineEmbedMark?: (embedName: string, markName: string) => boolean;
}Table, row, and cell nodes
You can override the default table, row, and cell components using the renderNode property of the Editor component like this:
import React, { PureComponent } from "react";
import { NodeType, Value, Editor } from "squa-doc-js";
const { value } = Value.createEmpty()
.change()
.insertText("Hello, World!");
class App extends PureComponent {
state = { value };
render() {
return (
<Editor
value={this.state.value}
onChange={this.onChange}
renderNode={renderNode}
/>
);
}
onChange = ({ value }) => {
this.setState({ value });
};
}
function renderNode(node) {
if (node.type === NodeType.Table) {
return renderTableNode(node);
}
if (node.type === NodeType.Row) {
return renderRowNode(node);
}
if (node.type === NodeType.Cell) {
return renderCellNode(node);
}
}
function renderTableNode(node) {
return { component: Table };
}
function renderRowNode(node) {
return { component: Row };
}
function renderCellNode(node) {
return { component: Cell };
}
function Table({ children, ...props }) {
return (
<table {...props}>
<tbody>{children}</tbody>
</table>
);
}
function Row({ children, ...props }) {
return <tr {...props}>{children}</tr>;
}
function Cell({ children, ...props }) {
return <tr {...props}>{children}</tr>;
}Block nodes
You can render your custom block nodes using the renderNode property of the Editor component like this:
import Delta from "quill-delta";
import React, { PureComponent } from "react";
import { NodeType, Value, Editor } from "squa-doc-js";
const schema = {
isBlockMark(name) {
return name === "type";
}
};
const value = Value.fromDelta({
schema,
contents: new Delta()
.insert("Heading one")
.insert("\n", { type: "heading-one" })
.insert("Heading two")
.insert("\n", { type: "heading-two" })
});
class App extends PureComponent {
state = { value };
render() {
return (
<Editor
value={this.state.value}
onChange={this.onChange}
renderNode={renderNode}
/>
);
}
onChange = ({ value }) => {
this.setState({ value });
};
}
function renderNode(node) {
if (node.type === NodeType.Block) {
return renderBlockNode(node);
}
}
function renderBlockNode(node) {
const blockType = node.getAttribute("type");
if (blockType === "heading-one") {
return { component: "h1" };
}
if (blockType === "heading-two") {
return { component: "h2" };
}
}Embed nodes
You can render your custom embed nodes using the renderNode property of the Editor component like this:
import Delta from "quill-delta";
import React, { PureComponent } from "react";
import { NodeType, Value, Editor } from "squa-doc-js";
const schema = {
isBlockEmbed(name) {
return name === "block-image";
},
isInlineEmbed(name) {
return name === "inline-image";
}
};
const value = Value.fromDelta({
schema,
contents: new Delta()
.insert({ "block-image": "foo.png" })
.insert({ "inline-image": "bar.png" })
.insert("\n")
});
class App extends PureComponent {
state = { value };
render() {
return (
<Editor
value={this.state.value}
onChange={this.onChange}
renderNode={renderNode}
/>
);
}
onChange = ({ value }) => {
this.setState({ value });
};
}
function renderNode(node) {
if (
node.type === NodeType.BlockEmbed ||
node.type === NodeType.InlineEmbed
) {
return renderEmbedNode(node);
}
}
function renderEmbedNode(node) {
if (node.name === "block-image") {
return { component: BlockImage, props: { node } };
}
if (node.name === "inline-image") {
return { component: InlineImage, props: { node } };
}
}
function BlockImage({ node, ...props }) {
return (
<figure {...props}>
<img src={node.value} />
</figure>
);
}
function InlineImage({ node, ...props }) {
return <img src={node.value} {...props} />;
}Wrapper nodes
You can wrap a group of block nodes using the renderWrapper property of the Editor component. For example, list items are implemented the following way:
import Delta from "quill-delta";
import React, { PureComponent } from "react";
import { NodeType, Value, Editor } from "squa-doc-js";
const schema = {
isBlockMark(name) {
return name === "type";
}
};
const value = Value.fromDelta({
schema,
contents: new Delta()
.insert("Unordered list item 1")
.insert("\n", { type: "unordered-list-item" })
.insert("Unordered list item 2")
.insert("\n", { type: "unordered-list-item" })
.insert("Ordered list item 1")
.insert("\n", { type: "ordered-list-item" })
.insert("Ordered list item 2")
.insert("\n", { type: "ordered-list-item" })
});
class App extends PureComponent {
state = { value };
render() {
return (
<Editor
value={this.state.value}
onChange={this.onChange}
renderWrapper={renderWrapper}
renderNode={renderNode}
/>
);
}
onChange = ({ value }) => {
this.setState({ value });
};
}
function renderWrapper(node) {
if (node.type === NodeType.Block) {
return renderBlockWrapper(node);
}
}
function renderBlockWrapper(node) {
const blockType = node.getAttribute("type");
if (blockType === "unordered-list-item") {
return { component: "ul" };
}
if (blockType === "ordered-list-item") {
return { component: "ol" };
}
}
function renderNode(node) {
if (node.type === NodeType.Block) {
return renderBlockNode(node);
}
}
function renderBlockNode(node) {
const blockType = node.getAttribute("type");
if (
blockType === "unordered-list-item" ||
blockType === "ordered-list-item"
) {
return { component: "li" };
}
}Marks
Attributes of nodes are represented by objects called marks. These marks can be rendered using the renderMark property of the Editor component. Table, row, cell, and block marks can be rendered as classnames, inline marks can be rendered as classnames and components. For example:
import Delta from "quill-delta";
import React, { PureComponent } from "react";
import { NodeType, Value, Editor } from "squa-doc-js";
const schema = {
isBlockMark(name) {
return name === "align";
},
isTextMark(name) {
return name === "link";
}
};
const value = Value.fromDelta({
schema,
contents: new Delta()
.insert("foo", { link: "http://foo.bar" })
.insert("\n", { align: "left" })
});
class App extends PureComponent {
state = { value };
render() {
return (
<Editor
value={this.state.value}
onChange={this.onChange}
renderMark={renderMark}
/>
);
}
onChange = ({ value }) => {
this.setState({ value });
};
}
function renderMark(mark) {
if (mark.name === "align") {
return { className: `align-${mark.value}` };
}
if (mark.name === "link") {
return { component: "a", props: { href: mark.value } };
}
}Licence
GNU LGPLv3
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago