Advanced Setup
Make sure you read the core concepts page before setup advance and check if the simple usage is good enough for you.
Setting Up Our Custom Editor Directory
Step 1 - creating the directory
Create a new folder in your project called editor-web
(or any other name) - advance setup will let you full control on what running inside the editor webview, so we need to create a separate folder that will include code that will run inside the webview and will have different build process and tsconfig
Step 2 - adding tsconfig
Create editor-web/tsconfig.json
:
{
"$schema": "https://json.schemastore.org/tsconfig",
"_version": "2.0.0",
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"module": "esnext",
"paths": {
"@10play/tentap-editor": [
"../node_modules/@10play/tentap-editor/lib-web/typescript/webEditorUtils/index.d.ts"
]
},
"moduleResolution": "bundler",
"target": "es2015",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"jsx": "react-jsx",
"noEmit": true,
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
}
}
It's important to configure paths
so on web the web types will be used instead of native types.
Step 3 - updating root tsconfig
Add exclude on the root tsconfig.json: "exclude": ["./editor-web"]
Step 4 - Add index.html AdvancedEditor.tsx and index.tsx
Add the following files
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
/>
<title>RichTextEditor</title>
</head>
<style>
body {
margin: 0;
padding: 0;
}
#root > div:nth-of-type(1) {
overflow: auto;
height: 100%;
position: absolute;
width: 100%;
top: 0;
bottom: 0;
}
#root div .ProseMirror {
height: 100%;
overflow: auto;
}
.ProseMirror:focus {
outline: none;
}
</style>
<body>
<div id="root"></div>
<script type="module" src="/index.tsx"></script>
</body>
</html>
import React from 'react';
import { EditorContent } from '@tiptap/react';
import { useTenTap, CoreBridge } from '@10play/tentap-editor';
import Document from '@tiptap/extension-document';
import Paragraph from '@tiptap/extension-paragraph';
import Text from '@tiptap/extension-text';
/**
* Here we control the web side of our custom editor
*/
export const AdvancedEditor = () => {
const editor = useTenTap({
bridges: [CoreBridge],
tiptapOptions: {
extensions: [Document, Paragraph, Text],
},
});
return <EditorContent editor={editor} />;
};
import React from 'react';
import { createRoot } from 'react-dom/client';
import { AdvancedEditor } from './AdvancedEditor';
/**
* This is the entrypoint for the "web" part of our editor that will be built with vite
*/
declare global {
interface Window {
contentInjected: boolean | undefined;
}
}
/**
* On android - react-native-webview there is a bug where sometimes the content
* is injected after the window is loaded https://github.com/react-native-webview/react-native-webview/pull/2960
* To overcome this we will check if the content is injected before rendering the editor
*/
const contentInjected = () => window.contentInjected;
let interval: NodeJS.Timeout;
interval = setInterval(() => {
if (!contentInjected()) return;
// Once content is injected into the webview, we can render the editor
const container = document.getElementById('root');
const root = createRoot(container!);
root.render(<AdvancedEditor />);
clearInterval(interval);
return;
}, 1);
Step 5 - adding a bundler
We chose to use vite as our bundler so first run
yarn add -D vite @vitejs/plugin-react vite-plugin-singlefile
And now let's create vite.config.ts
and configure it
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { viteSingleFile } from 'vite-plugin-singlefile';
// This config is used to build the web editor into a single file
export default defineConfig({
root: 'src/editor-web', // This should be the directory of your index.html
build: {
outDir: 'build',
},
resolve: {
alias: [
{
find: '@10play/tentap-editor', // On our web bundle we only want to include web related code
replacement: '@10play/tentap-editor/web',
},
// We alias tiptap view and state to use the internal version of tiptap to avoid this error https://github.com/ueberdostiptap/issues/3869#issuecomment-2167931620
{
find: '@tiptap/pm/view',
replacement: '@10play/tentap-editor/web',
},
{
find: '@tiptap/pm/state',
replacement: '@10play/tentap-editor/web',
},
],
},
plugins: [react(), viteSingleFile()],
server: {
port: 3000,
},
});
It's important to have the alias
configuration so vite will not load react-native code
Step 6 - adding build scripts
Add scripts on package.json so it will be easy to run/build editor-web:
"scripts": {
...
"editor:dev": "vite --config ./editor-web/vite.config.ts",
"editor:build": "vite --config ./editor-web/vite.config.ts build && yarn editor:post-build",
"editor:post-build":"node ./node_modules/@10play/tentap-editor/scripts/buildEditor.js ./editor-web/build/index.html",
"reverse-android": "adb reverse tcp:3000 tcp:3000",
}
editor:dev
- Run the editor web in dev mode
editor:build
- Build the editor web, probably need to add to your deploy/build process
editor:post-build
- This will take your built html file, and paste into a a ts and exports a string editorHtml
which we can then later use as the source of our RichText.
reverse-android
- On android need to reverse ports so it will be able to work with DEV / DEV_SERVER_URL props
Step 7 - using our custom editor
Outside
of editor-web update your EditorBridge
to used the html we just built.
import { editorHtml } from './editor-web/build/editorHtml';
const editor = useEditorBridge({
customSource: editorHtml,
...
});
Step 8 - Install react-dom
yarn add react-dom
yarn add -D @types/react-dom
You basically done with the advance setup now you have full control of the editor-web you can write your own bridgeExtensions add additional tiptapExtensions
Alternative Setup
In case you are using Expo you have to use this Alternative Setup for dev mode on your advance setup editor
There is an alternative vite setup that does not require a vite dev server as suggested in #80. This setup does not require we add the DEV
prop
Update vite.config.ts
In vite.config.ts
replace plugins with
const exec = require('child_process').exec;
...
plugins: [
react(),
viteSingleFile(),
{
name: 'postbuild-commands',
closeBundle: async () => {
exec('yarn editor:post-build', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
});
},
},
],
And add emptyOutDir: false
to the build options
build: {
outDir: 'build',
emptyOutDir: false,
},
Update package.json
Update the editor:dev
script to watch the advanced editor instead of running a dev-server, the hot reload will be handled by metro and not vite.
"editor:dev": "vite --config ./editor-web/vite.config.ts -w build"