Git Product home page Git Product logo

10tap-editor's Introduction

10tap-editor

cover

MIT License npm

TenTap is a typed, easy to use, customizable, and extendable Rich Text editor for React-Native based on Tiptap and Prosemirror. It offers a "plug and play" experience and comes with many essential features out of the box that can be incorporated into your apps quickly. Additionally, TenTap allows you the developers to tailor the editor to your applications specific needs.

Features

  • 💁 Based on tiptap
  • ➕ Extendable
  • 🎹 Custom keyboards
  • ⚙️ Support dynamic scheme
  • 🛠️ Native toolbar
  • 💅 Customizable styles
  • 🌒 Darkmode and custom theme support
  • 🏗️ supports new architecture*

* new arch supported on react-native version 0.73.5 and above

Why?

After years of developing rich text editors for mobile, we realized that there is an empty void for open source RichText editors on mobile especially for ReactNative. So we have decided to create this package that incorporates all that we have learned, and that provides the best possible ux. Tentap is designed for getting the best experience of editing rich-text on mobile inspired by state of the art mobile editors like: gdocs, notion, dropbox paper.

Docs and Examples

Click Here For Full Documentation

Installation

React Native

  1. yarn add @10play/tentap-editor react-native-webview
  2. cd ios && pod install

Expo

npx expo install @10play/tentap-editor react-native-webview
Only basic usage without custom keyboard is supported by Expo Go (see basic example).
Otherwise you will need to setup Expo Dev Client.

Now you ready to add tentap to your app!

Usage

export const Basic = () => {
  const editor = useEditorBridge({
    autofocus: true,
    avoidIosKeyboard: true,
    initialContent: 'Start editing!',
  });

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <RichText editor={editor} />
      <KeyboardAvoidingView
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        style={{
          position: 'absolute',
          width: '100%',
          bottom: 0,
        }}
      >
        <Toolbar editor={editor} />
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
};

Contributing

See the contributing guide to learn how to contribute to the repository and the development workflow.

License

MIT


Made with create-react-native-library

10tap-editor's People

Contributors

17amir17 avatar guyserfaty avatar ororsatti avatar

Stargazers

Sreehari Jayaraj avatar Anh Quoc Tran avatar  avatar Ahmed Khalil avatar David Parrelli avatar Michael Ritchie avatar Kelvin Ng'eno Ndumia avatar Matthew Ha avatar Héctor avatar Bohdan D. avatar Subhash Dhandhukiya avatar Junnosuke Kado avatar Joshua Moreno avatar Li Ding avatar Mayank pandav avatar Syntax avatar  avatar WAQAS KHAN ROGHANI avatar  avatar Bruno Wego avatar Arth Tyagi avatar  avatar Brian Juhl avatar Saravanakumar avatar Cem Turan avatar Cuervolu avatar nakapon avatar Frank Leng avatar wcyat avatar  avatar  avatar  avatar João Pedro Macedo Oliveira avatar Michel Haddad avatar Daniel Di Venere avatar  avatar Anik Das avatar Lực Nguyễn avatar Agney Menon avatar Drew Lyton avatar Manish avatar  avatar sidali hallak avatar Andrey Bondarenko avatar Cezar Cretu avatar  avatar Michael Demarais avatar  avatar Gustavo Aurélio avatar weining avatar B. Cedric Cogell avatar Jean-Baptiste Terrazzoni avatar fj avatar laogui avatar Allen Lin avatar Frederik Eychenié avatar KHELIFI Ahmed Aziz avatar immortal avatar  avatar Carter McKenzie avatar Rafael T. Ballestiero avatar Charles Kornoelje avatar Joshua avatar Lucky avatar  avatar Ameer avatar Kieran Czerwinski avatar Shekhar K. Sharma avatar  avatar Amir Panahi avatar Lorenzo Gonnelli avatar Oliver Molnar avatar Eric L. avatar Emerenini Cynthia Ngozi avatar Deri Kurniawan avatar Thiago Sciotta avatar ib avatar Viraj Patel avatar Ben Williams avatar  avatar  avatar Alexandr Zahatski avatar olya avatar Nikita avatar  avatar  avatar Julian Weiss avatar Jason Shellen avatar BYIRINGIRO Emmanuel avatar Mia Loha.dev avatar baraa avatar  avatar Hoang Hai avatar OhmKhur avatar Konstantin L avatar Jesko Iwanovski avatar  avatar Ross Waycaster avatar  avatar João Vitor Wenceslau Campagnin avatar

Watchers

Patrick O'Sullivan  avatar Mia Loha.dev avatar Matías Andrade Guzmán avatar  avatar  avatar

10tap-editor's Issues

Change icon tint color when active

Is it possible to change the color of the toolbar icon when it's active? I tried with this code below but it didn't work.

theme: { toolbar: { icon: { tintColor: "#bafcca", }, iconActive: { tintColor: "#43bc32", }, }, }

Doesn't render <br> tags

I've been using the Basic example of using the editor but it doesn't seem to respect <br> tags.

To test, I create a new expo project, then replace the HomeScreen component with this


import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import React from 'react';
import {
  SafeAreaView,
  KeyboardAvoidingView,
  Platform,
  StyleSheet,
} from 'react-native';
import { RichText, Toolbar, useEditorBridge } from '@10play/tentap-editor';

export default function HomeScreen() {
  const editor = useEditorBridge({
    autofocus: true,
    avoidIosKeyboard: true,
    initialContent,
  });

  return (
    <SafeAreaView style={exampleStyles.fullScreen}>
      <RichText editor={editor} />
      <KeyboardAvoidingView
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        style={exampleStyles.keyboardAvoidingView}
      >
        <Toolbar editor={editor} />
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  titleContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8,
  },
  stepContainer: {
    gap: 8,
    marginBottom: 8,
  },
  reactLogo: {
    height: 178,
    width: 290,
    bottom: 0,
    left: 0,
    position: 'absolute',
  },
});

const exampleStyles = StyleSheet.create({
  fullScreen: {
    flex: 1,
  },
  keyboardAvoidingView: {
    position: 'absolute',
    width: '100%',
    bottom: 0,
  },
});

const initialContent = `<p>This <br/><br/>is a <br/><strong>basic example!</strong></p>`;

What I see in my simulator looks like this
Simulator Screenshot - iPhone 12 - 2024-05-07 at 17 34 24

Invariant Violation: requireNativeComponent: "TenTapView" was not found in the UIManager.

I have this error : Invariant Violation: requireNativeComponent: "TenTapView" was not found in the UIManager.


import PenIcon from "@/assets/icons/PenIcon";
import { useKeyboard, useBridgeState, Toolbar, ColorKeyboard } from "@10play/tentap-editor";

const ToolbarWithColor = ({
  editor,
  activeKeyboard,
  setActiveKeyboard
}) => {
  // Get updates of editor state
  const editorState = useBridgeState(editor);

  const { isKeyboardUp: isNativeKeyboardUp } = useKeyboard();
  const customKeyboardOpen = activeKeyboard !== undefined;
  const isKeyboardUp = isNativeKeyboardUp || customKeyboardOpen;

  // Here we make sure not to hide the keyboard if our custom keyboard is visible
  const hideToolbar =
    !isKeyboardUp || (!editorState.isFocused && !customKeyboardOpen);

  return (
    <Toolbar
      editor={editor}
      hidden={hideToolbar}
      items={[
        {
          onPress: () => () => {
            const isActive = activeKeyboard == ColorKeyboard.id;
            if (isActive) editor.focus();
            setActiveKeyboard(isActive ? undefined : ColorKeyboard.id);
          },
          active: () => activeKeyboard === ColorKeyboard.id,
          disabled: () => false,
          image: () => PenIcon,
        },
      ]}
    />
  )
}

export default ToolbarWithColor


import { SafeAreaView, KeyboardAvoidingView, Platform, StyleSheet, View } from 'react-native';
import { useEditorBridge, RichText, PlaceholderBridge, useEditorContent, CoreBridge, TenTapStartKit, ColorBridge, HighlightBridge, ImageBridge, CustomKeyboard } from '@10play/tentap-editor';
import { useEffect, useRef, useState } from 'react';
import FigtreeRegular64 from "@/assets/fonts/figtree/Figtree-Regular_base64"
import { SIZE } from '@/assets/themes/theme';
import ToolbarWithColor from './ToolbarWithColor';

const Wysiwyg = ({
    displayToolbar,
    placeholder,
    onChange,
    onEditorReady
}) => {

    const customFont = `
        @font-face {
            font-family: 'Figtree';
            src: url('${FigtreeRegular64}');
            font-weight: normal;
            font-style: normal;
            font-display: swap;
        }

        * {
            font-family: 'Figtree';
            word-break: break-word;
        }
    `
    
    const rootRef = useRef(null)

    const [activeKeyboard, setActiveKeyboard] = useState()

    const editor = useEditorBridge({
        avoidIosKeyboard: true,
        bridgeExtensions: [
            ...TenTapStartKit,
            PlaceholderBridge.configureExtension({
                placeholder: placeholder,
            }),
            CoreBridge.configureCSS(customFont),
            ColorBridge.configureExtension({
                types: ['textStyle'],
            }),
            HighlightBridge,
            ImageBridge.configureExtension({
                inline: true,
            })
        ],
    })

    const content = useEditorContent(editor, { type: 'html' })

    useEffect(() => {
        onChange(content)
    }, [content])

    useEffect(() => {
        onEditorReady(editor)
    }, [editor])

    return (
        <SafeAreaView style={{ flex: 1 }} ref={rootRef}>
            <View style={{ flex: 1, paddingHorizontal: SIZE.padding }}>
                <RichText editor={editor} />
            </View>
            {displayToolbar && (
                <KeyboardAvoidingView
                    behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
                    style={styles.keyboardAvoidingView}
                >
                    <ToolbarWithColor
                        editor={editor}
                        activeKeyboard={activeKeyboard}
                        setActiveKeyboard={setActiveKeyboard}
                        ColorKeyboard={ColorKeyboard}
                    />
                    <CustomKeyboard
                        rootRef={rootRef}
                        activeKeyboardID={activeKeyboard}
                        setActiveKeyboardID={setActiveKeyboard}
                        keyboards={[ColorKeyboard]}
                        editor={editor}
                    />
                </KeyboardAvoidingView>
            )}
        </SafeAreaView>
    )
}

export default Wysiwyg

const styles = StyleSheet.create({
    keyboardAvoidingView: {
        position: 'absolute',
        width: '100%',
        bottom: 0,
    },
})

there is anyway to control link open? onLinkOpen maybe?

On the editor I was able to add a link but was not able to open an link inside the editor, when click on the link, the editor controls of course take preference, so when click on it just select the work that contains the link, there is any way to open the link? and is yes, there is any way to control this action?

Needed to add / commands in ReactNative App.

Hi,
I want to create notion like editor using tiptap in react Native
i want to implement the / commands in editor to use Commands

1_223aTv2WEZW8Tc78eY0J-A

This is in React, but i want it to be in React Native.
I'm new here, Any help will be appricated.
Thanks.

Issue with @10play/tentap-editor on Physical Android Devices

I am encountering an issue with the "@10play/tentap-editor" package when running my React Native application on physical Android device. Despite functioning correctly on Android emulators, the text editor component fails to display on physical Android phone.

Upon investigation, I discovered that certain aspects of my code appear to be functioning only on Android emulator, However, before making any changes to my code, I wanted to reach out and inquire if there's something iam missing of the "@10play/tentap-editor" package im not aware of.. of any known issues or compatibility limitations specific to Android devices.

I have thoroughly tested the application on multiple physical Android devices to ensure that the issue is not device-specific. Despite these efforts, the problem persists across different models of Android phones.

Below is the relevant section of my code for reference:

import { SafeAreaView, KeyboardAvoidingView, Platform, TouchableOpacity, StyleSheet } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import { useNavigation } from '@react-navigation/native';
import { RichText, Toolbar, useEditorBridge } from '@10play/tentap-editor';

const AddToDoScreen = ({ route }) => {
  const [text, setText] = useState('');
  const { todo } = route.params || {};
  const navigation = useNavigation();

  // Initialize the 10tap editor
  const editor = useEditorBridge({
    autofocus: true,
    avoidIosKeyboard: true,
    initialContent: todo ? todo.text : '',
  });

  //console.log('Editor initialized:', editor);

 
 
  useEffect(() => {
    if (todo) {
      // Set initial content of the editor to the text of the todo
      console.log("Todo Text:", todo.text);
      editor.setContent(todo.text);
    }
  }, [todo]);

  const addTodo = () => {
    editor.getText().then((content) => {
      if (content.trim() !== '') {
        if (todo) {
          const updatedTodo = { ...todo, text: content };
          navigation.navigate('TodoScreen', { newTodo: updatedTodo });
        } else {
          const newTodo = { id: Date.now().toString(), text: content, createdAt: new Date().toISOString() };
          navigation.navigate('TodoScreen', { newTodo}); 
        }
        setText('');
      }
    });
  };

  return (
    <SafeAreaView style={styles.fullScreen}>
      <RichText editor={editor} />
      <KeyboardAvoidingView
        behavior={Platform.OS === 'android' ? 'padding' : 'height'}
        style={styles.keyboardAvoidingView}
      >
      <Toolbar editor={editor} />
      </KeyboardAvoidingView>
      <TouchableOpacity style={styles.addButton} onPress={addTodo}>
          <Icon name="plus" size={20} color="white" />
        </TouchableOpacity>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  fullScreen: {
    flex: 1,
  },
  keyboardAvoidingView: {
    position: 'absolute',
    width: '100%',
    bottom: 0,
  },
  addButton: {
    position: 'absolute',
    bottom: 20,
    right: 20,
    backgroundColor: 'blue',
    width: 40,
    height: 40,
    borderRadius: 20,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default AddToDoScreen; ```

Custom Keyboard Not Reactive

What is Expected

The custom keyboard component (any keyboard created with CustomKeyboardExtension), should be reactive and should unmount when the keyboard is closed.

What is Happening

Currently we get reactive data in the custom keyboard from the useRemoteEditorBridge hook which basically listens for changes on the editor and stops listening when unmounted. When the the keyboard is closed the keyboard's root view is removed from the view on the native side, but is not unmounted on the react side. So all of the JS is still alive there and it still listens for changes on the editor.

Support for DropCursor extension?

First off, this library seems amazing. I've struggled in the past with RN WYSIWYGs, so thank you for your work in bringing a dedicated & robust experience to the framework.

I'm working on a new app, and 10tap seems very promising after only playing with it for 10 minutes. The TaskList support is a deal-maker for us!

Do you plan to add support for the DropCursor extension? That would help a lot in making this thing feel like Notion et al.

Build failed RN 0.73.5 with New Architecture (IOS)

I'm getting this error when I try to build my app with the new architecture enabled on IOS

Screenshot 2024-03-07 at 16 41 42

My podfile:

# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
  'require.resolve(
    "react-native/scripts/react_native_pods.rb",
    {paths: [process.argv[1]]},
  )', __dir__]).strip

platform :ios, min_ios_version_supported
prepare_react_native_project!

# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
#
# To fix this you can also exclude `react-native-flipper` using a `react-native.config.js`
# ```js
# module.exports = {
#   dependencies: {
#     ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
# ```
flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled

linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
  Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
  use_frameworks! :linkage => linkage.to_sym
end

target 'pellRichWithWebView' do
  config = use_native_modules!

  use_react_native!(
    :path => config[:reactNativePath],
    # Enables Flipper.
    #
    # Note that if you have use_frameworks! enabled, Flipper will not work and
    # you should disable the next line.
    :flipper_configuration => flipper_config,
    # An absolute path to your application root.
    :app_path => "#{Pod::Config.instance.installation_root}/.."
  )

  target 'pellRichWithWebViewTests' do
    inherit! :complete
    # Pods for testing
  end

  post_install do |installer|
    # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
    react_native_post_install(
      installer,
      config[:reactNativePath],
      :mac_catalyst_enabled => false
    )
  end
end

Steps to reproduce

  1. npx react-native init newProject
  2. yarn add @10play/tentap-editor react-native-webview
  3. bundle install && cd ios && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install

Libraries version

  • React Native: 0.73.5
  • Tentap: 0.4.52

IOS keyboard not disappears.

Hi, im unable to close the keyboard on IOS device its working great on Android.
Also Return On Keybaord is not closing the Keyboard.

RPReplay_Final1715345403.MP4

<SafeAreaView style={styles.fullScreen}> <RichText startInLoadingState={true} onLoad={() => { const json = convertSchemaToJSON(params.content); editor.setContent(JSON.parse(json)); }} editor={editor} /> <KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : "height"} style={styles.keyboardAvoidingView} > <Toolbar editor={editor} /> </KeyboardAvoidingView> </SafeAreaView>

RichText overflow after line break

Hello, I have a problem when my RichText takes up a large part of my screen, when I have no content inside I can scroll inside. When I add content and skip a line, the first line is hidden at the top and I have to scroll to see it. How can I fix this?

Basically, the question is: how can I modify the css of my RichText container?

import { SafeAreaView, KeyboardAvoidingView, Platform, StyleSheet, View } from 'react-native';
import { useEditorBridge, RichText, Toolbar, PlaceholderBridge, useEditorContent, CoreBridge, TenTapStartKit, ColorBridge, HighlightBridge, ImageBridge } from '@10play/tentap-editor';
import { useEffect } from 'react';
import FigtreeRegular64 from "@/assets/fonts/figtree/Figtree-Regular_base64"
import { SIZE } from '@/assets/themes/theme';

const Wysiwyg = ({
    displayToolbar,
    placeholder,
    onChange,
    onEditorReady
}) => {

    const customFont = `
        @font-face {
            font-family: 'Figtree';
            src: url('${FigtreeRegular64}');
            font-weight: normal;
            font-style: normal;
            font-display: swap;
        }

        * {
            font-family: 'Figtree';
            word-break: break-word;
        }
    `

    const editor = useEditorBridge({
        avoidIosKeyboard: true,
        bridgeExtensions: [
            ...TenTapStartKit,
            PlaceholderBridge.configureExtension({
                placeholder: placeholder,
            }),
            CoreBridge.configureCSS(customFont),
            ColorBridge.configureExtension({
                types: ['textStyle'],
            }),
            HighlightBridge,
            ImageBridge.configureExtension({
                inline: true,
            })
        ],
    })

    const content = useEditorContent(editor, { type: 'html' })

    useEffect(() => {
        onChange(content)
    }, [content])

    useEffect(() => {
        onEditorReady(editor)
    }, [editor])

    return (
        <SafeAreaView style={{ flex: 1 }}>
            <View style={{ flex: 1, paddingHorizontal: SIZE.padding }}>
                <RichText editor={editor} style={{ overflow: 'hidden' }} />
            </View>
            {displayToolbar && (
                <KeyboardAvoidingView
                    behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
                    style={styles.keyboardAvoidingView}
                >
                    <Toolbar editor={editor} />
                </KeyboardAvoidingView>
            )}
        </SafeAreaView>
    )
}

export default Wysiwyg;

const styles = StyleSheet.create({
    keyboardAvoidingView: {
        position: 'absolute',
        width: '100%',
        bottom: 0,
    },
})

do we have onHeightChange?

do we have onHeightChange? - I mean the way to detect line break (how can I detect it's 5 lines [screenshot])
Screenshot 2024-05-23 at 14 34 42

Thankss

Dismissing the keyboard

I was having issues dismissing the keyboard, managed to solve it.

Just for future reference, use the editor.blur() function cause calling Keyboard.dismiss() wont do it. We need to communicate the change to the webview, which the blur fn does.

Better dev command?

I use advanced mode as of now and I've been having some issues. Since what I see on dev mode is only half of what will get rendered on RN I don't see much value developing the editor on localhost with a Vite dev server.

That's why i've changed yarn editor:dev to be vite --config ./editor-web/vite.config.mts -w build. This will not run the vite dev server on port 3000 but instead will rebuild the single file html every time there's a change on the watched files.

The next problem then is that we need to run the post-build command for 10tap to build the custom editor. For this, I added the following custom plugin to my vite.config.ts

 plugins: [
    react(),
    viteSingleFile(),
    {
      name: 'postbuild-commands', // the name of your custom plugin. Could be anything.
      closeBundle: async () => {
        exec('npm run editor:post-build', (error, stdout, stderr) => {
          if (error) {
            console.error(`exec error: ${error}`);
            return;
          }
        });
      },
    },
  ],

And voilá! If i change the advanced editor source I immediately see the change on RN side.

The only bad thing is that I do see a small flash of a Metro error. It seems Vite deleted the build folder when starting a new build. I tried setting build.emptyOutDir: false on my vite config but it didn't work. So far it's not that big of an issue, can probably be solved with some investigation.

Let me know what you think

Mentions and Suggestion

Hey there, I'm just starting to use 10tap on a project and I was wondering if there's any compatibility with the suggestions and mentions plugin from tiptap. This is a must-have for me.

If this feature does not exist yet, I would be happy to make a contribution if you could share some general steps that I could follow to implement this.

Thanks in advance!

Problem of integration of ColorBridge, HighlightBridge and ImageBridge

I'd like to know why I don't have the tools to add an image with the ImageBridge, a color to my text with the ColorBridge and a colored background with the HIghlightBridge. Here's my code.

Does anyone know where this is coming from? That's my dependencies :

"@10play/tentap-editor@^0.5.0":
  version "0.5.0"
  resolved "https://registry.yarnpkg.com/@10play/tentap-editor/-/tentap-editor-0.5.0.tgz#c8be670c3dd702620d126e99210a3e8869da5cc5"
  integrity sha512-TaIfUPO6gcFF1ID4pb2on6IUpWLDb5iXUCt3hNRXQz/sHpxuaTfuCFWTc2uW5n3QMeFAPmGEH7ZMdxEGT4SlqA==
  dependencies:
    "@tiptap/extension-blockquote" "^2.2.1"
    "@tiptap/extension-bold" "^2.2.1"
    "@tiptap/extension-bullet-list" "^2.2.1"
    "@tiptap/extension-code" "^2.2.1"
    "@tiptap/extension-code-block" "^2.2.1"
    "@tiptap/extension-color" "^2.1.16"
    "@tiptap/extension-document" "^2.2.1"
    "@tiptap/extension-dropcursor" "^2.2.4"
    "@tiptap/extension-hard-break" "^2.3.1"
    "@tiptap/extension-heading" "^2.2.1"
    "@tiptap/extension-highlight" "^2.1.16"
    "@tiptap/extension-history" "^2.2.1"
    "@tiptap/extension-horizontal-rule" "^2.2.1"
    "@tiptap/extension-image" "^2.2.1"
    "@tiptap/extension-italic" "^2.2.1"
    "@tiptap/extension-link" "^2.1.16"
    "@tiptap/extension-list-item" "^2.2.1"
    "@tiptap/extension-ordered-list" "^2.2.1"
    "@tiptap/extension-placeholder" "^2.2.1"
    "@tiptap/extension-strike" "^2.2.1"
    "@tiptap/extension-task-item" "^2.1.16"
    "@tiptap/extension-task-list" "^2.1.16"
    "@tiptap/extension-text-style" "^2.1.16"
    "@tiptap/extension-underline" "^2.1.16"
    "@tiptap/pm" "^2.1.16"
    "@tiptap/react" "^2.1.16"
    "@tiptap/starter-kit" "^2.1.16"
    lodash "^4.17.21"
    react-dom "^18.2.0"

import { SafeAreaView, KeyboardAvoidingView, Platform, StyleSheet, View } from 'react-native';
import { useEditorBridge, RichText, Toolbar, PlaceholderBridge, useEditorContent, CoreBridge, TenTapStartKit, ColorBridge, HighlightBridge, ImageBridge } from '@10play/tentap-editor';
import { useEffect } from 'react';
import FigtreeRegular64 from "@/assets/fonts/figtree/Figtree-Regular_base64"
import { SIZE } from '@/assets/themes/theme';

const Wysiwyg = ({
    displayToolbar,
    placeholder,
    onChange,
    onEditorReady
}) => {

    const customFont = `
        @font-face {
            font-family: 'Figtree';
            src: url('${FigtreeRegular64}');
            font-weight: normal;
            font-style: normal;
            font-display: swap;
        }

        * {
            font-family: 'Figtree';
            word-break: break-word
        }
    `

    const editor = useEditorBridge({
        bridgeExtensions: [
            ...TenTapStartKit,
            PlaceholderBridge.configureExtension({
                placeholder: placeholder,
            }),
            CoreBridge.configureCSS(customFont)
        ],
    })

    const content = useEditorContent(editor, { type: 'html' })

    useEffect(() => {
        onChange(content)
    }, [content])

    useEffect(() => {
      onEditorReady(editor)
    }, [editor])

    return (
        <SafeAreaView style={{ flex: 1, paddingHorizontal: SIZE.padding }}>
            <RichText editor={editor} />
            {displayToolbar && (
                <KeyboardAvoidingView
                    behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
                    style={styles.keyboardAvoidingView}
                >
                    <Toolbar editor={editor} />
                </KeyboardAvoidingView>
            )}
        </SafeAreaView>
    )
}

export default Wysiwyg

const styles = StyleSheet.create({
    keyboardAvoidingView: {
        position: 'absolute',
        width: '100%',
        bottom: 0,
    },
})

Faster Way To Inject Font

Inject the Base64 content of a font is expensive on load time, we need a better way of doing this.

Dynamic css.

Would be cool if there was a way to update the css for the editor.
CoreBridge.configureCSS(customCss),

how to render a sample html message using read only mode

Questions:

  • Is the way mentioned below how I'm supposed to build a sample renderer? I am unsure how to set the content using the useEditorBridge hook, I used initialContent as the suited prop. But is this how I am supposed to implement or is there something I am missing?
  • Moreover I am mapping RichTextEditor for say a list of messages, this is currently making every message scrollable individually, is there any way to stop this?

Code Sample Screenshot:

image

Output Screenshot:

image

Read only mode

Hey! I'd like to implement a bridge (or add options to an existing one) to be able to turn on/off the readonly mode of the editor. Is this something you have a preference on how to implement?

Right now I am currently rendering the html on a webview with all my css manually copied in it. So it'd be cool to hand a prop on the react native side to the editor to enable/disable it.

Editor's initialContent support for JSON missing

I noticed that initialContent can't accept JSON when tiptap editor does. I have a simple change that should work and we tested it too. With this change we should be able to use the code below:

const editor = useEditorBridge({
    autofocus: true,
    avoidIosKeyboard: true,
    initialContent: {
      type: "doc",
      content: [
        {
          type: "paragraph",
          content: [
            {
              type: "text",
              text: "Leave a note ",
            },
            {
              type: "text",
              marks: [
                {
                  type: "bold",
                },
              ],
              text: "—text in bold",
            },
          ],
        },
      ],
    },
    // bridgeExtensions: [...TenTapStartKit, CodeBridge.configureCSS(editorCustomCSS)],
    // theme: customTheme,
  })

TypeError: _extensionLink.default.configure is not a function (it is undefined), js engine: hermes

When trying to check the operation with a minimal implementation, a TypeError is output and cannot be checked.
I was struggling with the implementation of the editor and would love to use this library.

Thank you for your contribution to React Native!

dependencies

"@10play/tentap-editor": "^0.5.0",
"expo": "~50.0.17",
"react-native": "0.73.6",
"react-native-web": "~0.19.10",
"react-native-webview": "13.6.4",

Minimal implementation

import React from 'react';
import { KeyboardAvoidingView, Platform, StyleSheet } from 'react-native';
import { RichText, Toolbar, useEditorBridge } from '@10play/tentap-editor';
import { AppView } from '@/components/Themed';

export const ExampleScreen = () => {
  const editor = useEditorBridge();

  return (
    <AppView style={exampleStyles.fullScreen}>
      <RichText editor={editor} />
      <KeyboardAvoidingView
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        style={exampleStyles.keyboardAvoidingView}
      >
        <Toolbar editor={editor} />
      </KeyboardAvoidingView>
    </AppView>
  );
};

const exampleStyles = StyleSheet.create({
  fullScreen: {
    flex: 1,
  },
  keyboardAvoidingView: {
    position: 'absolute',
    width: '100%',
    bottom: 0,
  },
});

Error

 ERROR  TypeError: _extensionLink.default.configure is not a function (it is undefined), js engine: hermes
 ERROR  Invariant Violation: "main" has not been registered. This can happen if:
* Metro (the local dev server) is run from the wrong folder. Check if Metro is running, stop it and restart it in the current project.
* A module failed to load due to an error and `AppRegistry.registerComponent` wasn't called., js engine: hermes

New Arch Support

With RN .75 new arch comes out of the box, which we do not currently support

DropCursorBridge is not exported

Hey there! tried open a small PR for this but don't have write access.

As the title says, the DropCursor bridge is there and all but is not exported on src/index.tsx. Super small fix

Toolbar not display on IOS

IMG_00F92A1553F1-1

As shown in the above picture, when i start input some text, but toolbar not display as expect, I use the demo code.

How can we set background color as transparent?

Screenshot 2024-05-28 at 11 22 56

I set background as image -> it works, but when I set background as transparent, editor show white background,
how can we set background color as transparent ?

  • For more information, I already set background: transparent for *, .ProseMirror, body, but it didnt work

thank you a lot

Reduce Dependencies

Currently tiptap and all of it's extension are deps, but I might not have to be this way since it is bundled anyways into a ts file. It is however imported inside each bridge, which might cause problems

Allow setting max characters limit

Hello, is there a way to set a maximum character limit for the RichText component? I couldn’t find any information in the documentation, nor could I find details on making the element controlled to implement my own logic.

Thanks!

Schema Format.

Hi, i have a editor Slate.js
running in production, it gives me only output as JSON.

And 10Tap accepts json but both of them have different json schema .

Is there a way to customize the JSON Schema format according my needs.

Type error when using basic example and adding any bridgeExtension

Versions:
"@10play/tentap-editor": "^0.4.55",
"expo": "~50.0.14",
I'm using the iOS simulator with expo go, but I also tried eas build and running it on the dev client and I got the same error.

I just have just made a simple expo project and used nothing but the example code for 10tap in the docs. It works fine but when I add the bridgeExtensions field, I get this error:

ERROR  TypeError: editor.updateScrollThresholdAndMargin is not a function (it is undefined)

This error is located at:
    in RichText (at App.js:24)
    in RCTSafeAreaView (at App.js:23)
    in Basic (at withDevTools.ios.js:25)
    in withDevTools(Basic) (at renderApplication.js:57)
    in RCTView (at View.js:116)
    in View (at AppContainer.js:127)
    in RCTView (at View.js:116)
    in View (at AppContainer.js:155)
    in AppContainer (at renderApplication.js:50)
    in main(RootComponent) (at renderApplication.js:67), js engine: hermes

Interestingly when I make it "avoidIosKeyboard: false" then the error disappears but the editor doesn't work.

Here's my code:

import React, { useEffect } from "react";
import {
    SafeAreaView,
    View,
    KeyboardAvoidingView,
    Platform,
    StyleSheet,
} from "react-native";
import {
    LinkBridge,
    RichText,
    Toolbar,
    useEditorBridge,
} from "@10play/tentap-editor";

export const Basic = () => {
    const editor = useEditorBridge({
        autofocus: true,
        avoidIosKeyboard: true,
        bridgeExtensions: [LinkBridge.configureExtension({ openOnClick: false })],
        initialContent: initialContent,
    });
    return (
        <SafeAreaView style={exampleStyles.fullScreen}>
            <RichText editor={editor} />
            <KeyboardAvoidingView
                behavior={Platform.OS === "ios" ? "padding" : "height"}
                style={exampleStyles.keyboardAvoidingView}
            >
                <Toolbar editor={editor} />
            </KeyboardAvoidingView>
        </SafeAreaView>
    );
};

Add API Similar to `onChange`

Currently there is no easy way to get changed content when it is changed. This is on purpose because sending the doc's content each change with messages between the webview and native part can be expensive.
We should expose some api to make this performant.

Custom Keyboard Jitter when Opened [Android]

What is expected

The custom keyboard should open smoothly like on IOS

What is happening

When opening the CustomKeyboard on Android the soft keyboard is hidden and the softInputMode on the window is changes, this causes a jitter for a frame when opening the custom keyboard.

Screen.Recording.2024-02-18.at.12.58.51.mov

Advanced setup questions

Hello! First of all, thank you so much for making such a nice rich text library available for react native 👍

I'm applying the advanced setup to implement 'readonly mode / multiple placeholder' and I'm not quite sure how the advanced setup implements its own bridgeExtension.

For example, if I implemented a custom bridgeExtension called PlaceholderWithTitleBridge, would this need to be added to both advancedEditor (Web: useTenTap-bridges) and advancedRichText (RN: useEditorBridge-bridgeExtensions)? I'm trying to understand the examples and documentation + source code on GitHub, but I'm not sure due to my lack of knowledge

And I'm using i18next's useTranslation hook for internalization support. With the useTranslation hook, I get 2 placeholder values (title, content) from the translate file. I functionized PlaceholderWithTitleBridge to use them in PlaceholderWithTitleBridge. However, this way there seems to be no way to pass the placeholder values from advancedEditor(web). This seems to be wrong.

Can anyone help me with this gap in my knowledge? If there is a clear path, I'd love to know.

I've attached the source code for comment. thank you!

// PlaceholderWithTitleBridge.ts (Custom BridgeExtension)

import Placeholder from "@tiptap/extension-placeholder";
import { BridgeExtension } from "@10play/tentap-editor";

type PlaceholderEditorWithTitleState = {};

type PlaceholderEditorWithTitleInstance = {};

declare module "@10play/tentap-editor" {
  interface BridgeState extends PlaceholderEditorWithTitleState {}
  interface EditorBridge extends PlaceholderEditorWithTitleInstance {}
}

export const PlaceholderWithTitleBridge = ({
  titlePlaceholder,
  contentPlaceholder,
}: {
  titlePlaceholder: string | undefined;
  contentPlaceholder: string | undefined;
}) =>
  new BridgeExtension<
    PlaceholderEditorWithTitleState,
    PlaceholderEditorWithTitleInstance
  >({
    tiptapExtension: Placeholder.configure({
      showOnlyCurrent: false,
      placeholder: ({ node }) => {
        if (node.type.name === "title") {
          return titlePlaceholder ?? ''; // need to fix
        } else {
          return contentPlaceholder ?? ''; // need to fix
        }
      },
    }),
    extendCSS: `
    h1.is-empty:nth-child(1)::before,
    p.is-empty:nth-child(2):last-child::before {
        color: #adb5bd;
        content: attr(data-placeholder);
        float: left;
        height: 0;
        pointer-events: none;
    }
  `,
  });
// AdvancedRichText.tsx (in RN component)

export default function AdvancedRichText() {
  const { t } = useTranslation(); // i18next
  const editor = useEditorBridge({
    customSource: editorHtml,
    bridgeExtensions: [
      CoreBridge,
      ListItemBridge,
      BulletListBridge,
      OrderedListBridge,
      TaskListBridge,
      PlaceholderWithTitleBridge({ titlePlaceholder: t("Editor:title_placeholder"), contentPlaceholder: t("Editor:content_placeholder") }),
    ],
    avoidIosKeyboard: true,
  });

  const content = useEditorContent(editor, { type: "json" });

  useEffect(() => {
    setTimeout(() => {
      editor.focus("start");
    }, 200); // autofocus:true not working
  }, []);

  useEffect(() => {
    StatusBar.setBarStyle("light-content"); // need to fix
  }, [content]);

  return (
    <BottomSheetModalProvider>
      <StatusBar barStyle={"light-content"} hidden={false} />
      <Container>
        <RichText editor={editor} />
      </Container>
    </BottomSheetModalProvider>
  );
}

// AdvancedEditor.tsx (in editor-web)

const DocumentWithTitle = Document.extend({
  content: "title block*",
});

const Title = Heading.extend({
  name: "title",
  group: "title",
  parseHTML: () => [{ tag: "h1:first-child" }],
}).configure({ levels: [1] });

export const AdvancedEditor = () => {
  const editor = useTenTap({
    bridges: [
      CoreBridge,
      ListItemBridge,
      BulletListBridge,
      OrderedListBridge,
      TaskListBridge,
      // PlaceholderWithTitleBridge({
      //   titlePlaceholder: ??,
      //   contentPlaceholder: ??,
      // }),
    ],
    tiptapOptions: {
      extensions: [DocumentWithTitle, Paragraph, Text, Title],
    },
  });

  return (
    <div style={{ backgroundColor: #FFF }}>
      <EditorContent editor={editor} />
    </div>
  );
};

Crash on initializaiton

This library has crash on useEditorBridge hook this line const webviewRef = useRef<WebView>(null);.
Sometimes this ref is null and it is not going to work every time.
We are having trouble with this because most of the time it won't work.
We are using "react-native": "0.73.2" version.
Any solutions?

Place inside a ScrollView

In the CustomAndStaticToolbar example there's a component above the RTE, but when the content from the RTE does not fit on the screen, the top view remains static.

What's the best approach for have the top component & RTE scroll together?

Is there any form of web support via Expo?

Really enjoying the iOS and Android experience! But I have a bare react native app that uses Expo to also ship a web version of the app. Would I be able to utilize this library for that use case? It doesn't seem so with the two platform iOS and Android specific code but wanted to see if it is possible.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.