unified-doc-react
react
wrapper for unified-doc.
Install
npm install unified-doc-react
Use
DocComponent
For quick and simple rendering of a document, use the React component:
import React from 'react';
import { DocComponent } from 'unified-doc-react';
const options = {
content: '> some **strong** content',
filename: 'doc.md',
marks: [
{ id: 'a', start: 0, end: 5 },
{ id: 'a', start: 10, end: 12 },
],
};
function MyDoc() {
return (
<DocComponent
className="my-doc"
options={options}
/>
);
}
DocProvider
and useDoc
For building advanced and interactive document applications, wrap your component with a DocProvider
. Components under the DocProvider
have access to the doc
instance via the useDoc
hook.
// App.js
import React from 'react';
import { DocProvider } from 'unified-doc-react';
import MyDoc from './MyDoc';
const options = {
content: '> some **strong** content',
filename: 'doc.md',
};
function MyApp() {
return (
<DocProvider options={options}>
<MyDoc />
</DocProvider>
);
}
// MyDoc.js
import React, { useState } from 'react';
import { saveFile } from 'unified-doc-dom';
import { useDoc } from 'unified-doc-react';
function MyDoc() {
const doc = useDoc();
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
function clearSearch() {
setQuery('');
setResults([]);
}
function handleSearch(e) {
const query = e.target.value;
const results = doc.search(query);
setQuery(query);
setResults(results);
}
return (
<div>
<h1>{doc.file().name}</h1>
<h2>Search</h2>
<input value={query} onChange={handleSearch} />
{results.map((result, i) => {
const [left, matched, right] = result.snippet;
return (
<div key={i}>
{left}
<strong>{matched}</strong>
{right}
</div>
);
})}
<button onClick={clearSearch}>
Clear search
</button>
<h2>Contents</h2>
<div>{doc.compile().result}</div>
<button onClick={() => saveFile(doc.file())}>
Download original
</button>
<button onClick={() => saveFile(doc.file('.html'))}>
Download HTML
</button>
<button onClick={() => saveFile(doc.file('.txt'))}>
Download text
</button>
<h2>Text Contents</h2>
<pre>{doc.textContent()}</pre>
</div>
);
}
Use with unified-doc-dom
unified-doc-react
can be used seamlessly with methods in unified-doc-dom
to build interactive document applications.
import React, { useEffect, useRef, useState } from 'react';
import { fromFile, highlight, registerMarks, selectText } from 'unified-doc-dom';
import { DocComponent } from 'unified-doc-react';
import { v4 as uuidv4 } from 'uuid';
// import optional highlight styles
import 'unified-doc-dom/css/highlight.css';
function MyDoc() {
const docRef = useRef();
const [fileData, setFileData] = useState();
const [marks, setMarks] = useState([]);
function addMark(newMark) {
setMarks(oldMarks => [...oldMarks, { ...newMark, id: uuidv4() }]);
}
// enable and capture selected text as marks
useEffect(() => {
// cleanup function conveniently returned
return selectText(docRef.current, { callback: addMark });
}, []);
// register marks with callbacks
useEffect(() => {
const callbacks = {
onClick: (event, mark) => console.log('clicked', event, mark),
onMouseEnter: (event, mark) => console.log('mouseenter', event, mark),
onMouseOut: (event, mark) => console.log('mouseout', event, mark),
}
// cleanup function conveniently returned
return registerMarks(docRef.current, marks, callbacks);
}, [marks]);
// highlight applied marks given its ID
function highlightLastMark() {
highlight(docRef.current, marks[marks.length - 1].id);
}
// read file data from a JS file
async function uploadFile(e) {
const fileData = await fromFile(e.target.files[0]);
setFileData(fileData);
}
let docContent;
if (!fileData) {
docContent = <input type="file" onChange={uploadFile}></input>;
} else {
const options = {
content: fileData.content,
filename: fileData.name,
marks,
};
docContent = <DocComponent options={options} />;
}
return (
<div>
<button onClick={highlightLastMark}>
Highlight last mark
</button>
<div ref={docRef}>
{docContent}
</div>
</div>
);
}
API
The term doc
used below refers to a unified-doc
instance. Please refer to unified-doc for detailed documentation of doc
API methods.
DocComponent(props)
Interface
function DocComponent(props: Props): React.ReactElement;
A simple React component that wraps around a doc
instance.
Related interfaces
interface Props {
/** options for `doc` instance */
options: Options;
/** optional `className` to attach to rendered `docElement` */
className?: string;
/** a reference to the rendered `docElement` */
ref?: React.Ref<HTMLDivElement>;
}
DocProvider(props)
Interface
function DocProvider(props: ProviderProps): React.ReactElement;
Use the DocProvider
to expose the doc
instance in a React context. Components under DocProvider
can access the doc
instance via the useDoc
hook.
Related interfaces
interface ProviderProps {
/** any React node */
children: React.ReactNode;
/** options for `doc` instance */
options: Options;
}
useDoc()
Interface
export function useDoc(): DocInstance;
Access the doc
instance in any components under the DocProvider
.
options
Provide options
to configure unified-doc
.
Please refer to unified-doc for detailed documentation of options
.
Development
This project is:
- implemented with the
unified-doc
interface. - linted with
xo
+prettier
+tsc
. - developed and built with
microbundle
. - tested with
jest
. - softly-typed with
typescript
withcheckJs
(only public APIs are typed).
# install dependencies
npm run bootstrap
# build package with microbundle
npm run build
# clean package (rm dist + node_modules)
npm run clean
# watch/rebuild package with microbundle
npm run dev
# lint package with xo + prettier + tsc
npm run lint
# update semantic version with changelog
npm run release
# test package with jest in --watch mode (make sure to run the 'dev' script)
npm run test
# test package in a single run
npm run test:run