Giter Site home page Giter Site logo

mohamedrejeb / compose-rich-editor Goto Github PK

View Code? Open in Web Editor NEW
802.0 8.0 47.0 1.89 MB

A Rich text editor library for both Jetpack Compose and Compose Multiplatform, fully customizable and supports the common rich text editor features.

Home Page: https://mohamedrejeb.github.io/Compose-Rich-Editor/

License: Apache License 2.0

Kotlin 100.00%
android android-library compose compose-android compose-desktop compose-ios compose-multiplatform compose-ui compose-web kmm

compose-rich-editor's People

Contributors

azimmuradov avatar donovan-fournier avatar ganfra avatar garretyoder avatar janseeger avatar medvedevaelena avatar mohamedrejeb avatar vaibhav2002 avatar wakaztahir avatar wavesonics avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

compose-rich-editor's Issues

Implementing new core foundation

Currently I'm working on a new core foundation for the library based on nodes representation for the rich text.
Here's some benefits of this change:

  • Improve scalability of the library
  • Implement new features more easily
  • Bug fixes
  • Improve HTML output (Shorter)
  • Performance improvements
    ...

This update will bring breaking changes so we are going to move from 0.x to 1.x
The work is in its last stages, it will be ready soon

Toggle selected text style

Is there a way to toggle the style of selected text?

Ex:
I type

foo bar

Then I select the foo and toggle the italic style, I expect it to become

foo bar

Support Immutable Data Structure

The initial importing and exporting using HTML or Markdown is good but its not the one that fits every requirement

Problem

Suppose I have a backend, I can store the output of this editor as HTML or Markdown , Now that goes to my database, I have a website which is built using React which needs to display this rich text in another editor which is built using React or Javascript, If I store it using HTML, well I must be able to parse and display the HTML exactly like this editor does otherwise differences will be introduced. If I store markdown, I will have to make sacrifices on features that markdown doesn't support, An immutable data structure makes it very easy

We make very clean, immutable data classes storing only primitive types like strings, representing the rich text in editor

Solution

A immutable data structure model must be created

Since data classes would be immutable, The structure will be clear so user knows all the types of models our editor has suppose ImmutableParagraph, ImmutableRichText(val isBold, ...)

The data structure would only contain properties that the editor supports and to methods to convert to the mutable data structure that is editable when editor is running

for example

internal data class ImmutableRichTextParagraph(
    val items : List<ImmutableRichItem>
){
    fun toRichTextParagraph() : RichTextParagraph {
        val para = RichTextParagraph()
        para.addSpans(items.map { it.toRichTextSpan() })
        return para
    }
}

In Kotlin, usually immutable classes don't start with immutable word and instead mutable classes have mutable in their names like List and MutableList, since mutable classes are already internal, This change could be made any time when the data structure is mature enough

Pros

  • Standardizes Everything in Immutable Data Classes that Rich Text Editor supports
  • This immutable data structure can be taken out and separated into core library
  • More export and import formats can be supported by just creating libraries that would convert to this immutable data structure
  • Clean code with better organization and structure

Cons

  • Maintain compatibility with previous versions of data structure (if supporting JSON serialization)
  • More code

Thoughts on Exposing Core Data Structure

The data structure must be separated out to another library core, The editor must make implementation dependency on core, so Users cannot access the data structure unless they explicitly specify a implementation dependency on the core library

This would solve

  • No need to have internal on each data class, making it unable to use with a solid use case
  • Hide data structure from users, so library API is not polluted and allow to work on the model until its stable

Where when the user gives HTML , The HTML parser parses the html to this immutable data structure, When markdown is given the parser converts to this immutable data structure

Supporting JSON Serialization

If users were to store the immutable model as json in their databases, We must maintain compatibility backwards

  • Renaming a property, we must use @JsonNames("previous_property_name") annotation from kotlinx.serialization on the new property
  • Removing a property, Either we must make sure user deserializes his json using the option ignoreUnknownKeys in kotlinx.serialization library, or we need to make it nullable and set its default value to null and make the property deprecated
  • Adding a property, No requirement, since if we added a property in new version of the library, user can only generate json with that property using the new version of the library, previous version of the library just cannot deserialize it

Let me know if you need any help, I can contribute

Add mentions @ support

Provide easy ways to support mentions with @ in the Rich Editor:

  • Listener for the text written after the @
  • Highlight the mention text (optional)
  • Delete the mention text at once (optional)

We can create full toolbar like this

It would nice to have support for full toolbar

fun RichTextStyleRow(
    modifier: Modifier = Modifier,
    value: RichTextValue,
    onValueChanged: (RichTextValue) -> Unit,
) {
    LazyRow(
        modifier = modifier
    ) {
        item {
            RichTextStyleButton(
                style = RichTextStyle.Bold,
                value = value,
                onValueChanged = onValueChanged,
                icon = R.drawable.ic_bold,
                tint = <TintColor>
            )
        }

        item {
            RichTextStyleButton(
                style = RichTextStyle.Italic,
                value = value,
                onValueChanged = onValueChanged,
                icon = R.drawable.ic_italic,
                tint = <TintColor>
            )
        }
    }
    LazyRow(
        modifier = modifier
    ) {
        item {
            RichTextStyleButton(
                style = RichTextStyle.H1,
                value = value,
                onValueChanged = onValueChanged,
                icon = R.drawable.ic_heading_1,
                tint = <TintColor>
            )
        }

        item {
            RichTextStyleButton(
                style = RichTextStyle.H2,
                value = value,
                onValueChanged = onValueChanged,
                icon = R.drawable.ic_heading_2,
                tint = <TintColor>
            )
        }

        item {
            RichTextStyleButton(
                style = RichTextStyle.H3,
                value = value,
                onValueChanged = onValueChanged,
                icon = R.drawable.ic_heading_3,
                tint = <TintColor>
            )
        }

        item {
            RichTextStyleButton(
                style = RichTextStyle.H4,
                value = value,
                onValueChanged = onValueChanged,
                icon = R.drawable.ic_heading_4,
                tint = <TintColor>
            )
        }
        item {
            RichTextStyleButton(
                style = RichTextStyle.H5,
                value = value,
                onValueChanged = onValueChanged,
                icon = R.drawable.ic_heading_5,
                tint = <TintColor>
            )
        }
        item {
            RichTextStyleButton(
                style = RichTextStyle.H6,
                value = value,
                onValueChanged = onValueChanged,
                icon = R.drawable.ic_heading_6,
                tint = <TintColor>
            )
        }
    }
}
----------------
@Composable
fun RichTextStyleButton(
    style: RichTextStyle,
    value: RichTextValue,
    onValueChanged: (RichTextValue) -> Unit,
    icon: ImageVector,
    tint: Color? = null
) {
    IconButton(
        modifier = Modifier
            .focusProperties { canFocus = false },
        onClick = {
            onValueChanged(value.toggleStyle(style))
        },
        colors = IconButtonDefaults.iconButtonColors(
            contentColor = if (value.currentStyles.contains(style)) {
                MaterialTheme.colorScheme.onPrimary
            } else {
                MaterialTheme.colorScheme.onBackground
            },
        ),
    ) {
        Icon(
            icon,
            contentDescription = icon.name,
            tint = if (value.currentStyles.contains(style)) tint
                ?: LocalContentColor.current else <DiasabledColor>
        )
    }
}

@Composable
fun RichTextStyleButton(
    style: RichTextStyle,
    value: RichTextValue,
    onValueChanged: (RichTextValue) -> Unit,
    icon: Int,
    tint: Color? = null
) {
    IconButton(
        modifier = Modifier
            .size(30.dp)
            .focusProperties { canFocus = false },
        onClick = {
            onValueChanged(value.toggleStyle(style))
        },
    ) {
        Icon(
            painter = painterResource(id = icon),
            contentDescription = "",
            tint = if (value.currentStyles.contains(style)) tint
                ?: LocalContentColor.current else <DiasabledColor>
        )
    }
}
--------------

Export Android version

Hi there, great library. Thanks.
How do I export only Android version from this project?
Thanks.

Richtext cursor fixed at position 0

I am trying to instantiate the remember delegate with a starting value when in the editing state. It always shows as an empty description state :/ Here's the code and a screenshot below:

var richTextValue by remember { mutableStateOf(RichTextValue.from(htmlStringFromBackend)) }

RichTextEditor(
    enabled = enabled,
    modifier = innerModifier.fillMaxWidth(),
    value = richTextValue,
    onValueChange = {
        richTextValue = it
    },
    colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White),
)

Screenshot 2023-06-12 at 1 08 39 PM

How can I fix this so it will start with loading in the data?

Support Initializing / setting the RichTextState from TextFieldValue / Annotated String

Right now rememberRichTextState has no parameters , I would like to initialize rememberRichTextState via text field value / annotated string

There's also no function setTextFieldValue , setAnnotatedString , but there's setText

This would make it easier to replace OutlinedTextField with OutlinedTextEditor , since we only need to initialize / set the text field value to rich text state and provide rich text state to editor

another java.lang.StringIndexOutOfBoundsException

java.lang.StringIndexOutOfBoundsException: length=0; index=1 at java.lang.String.substring(String.java:2060) at com.mohamedrejeb.richeditor.model.RichTextState.handleAddingCharacters(RichTextState.kt:670) at com.mohamedrejeb.richeditor.model.RichTextState.onTextFieldValueChange$richeditor_compose_release(RichTextState.kt:510) at com.mohamedrejeb.richeditor.ui.BasicRichTextEditorKt$BasicRichTextEditor$12$1.invoke(BasicRichTextEditor.kt:338) at com.mohamedrejeb.richeditor.ui.BasicRichTextEditorKt$BasicRichTextEditor$12$1.invoke(BasicRichTextEditor.kt:335) at androidx.compose.foundation.text.BasicTextFieldKt$BasicTextField$8$1.invoke(BasicTextField.kt:292) at androidx.compose.foundation.text.BasicTextFieldKt$BasicTextField$8$1.invoke(BasicTextField.kt:290) at androidx.compose.foundation.text.TextFieldState$onValueChange$1.invoke(CoreTextField.kt:849) at androidx.compose.foundation.text.TextFieldState$onValueChange$1.invoke(CoreTextField.kt:844) at androidx.compose.foundation.text.TextFieldDelegate$Companion.onEditCommand$foundation_release(TextFieldDelegate.kt:205) at androidx.compose.foundation.text.TextFieldDelegate$Companion$restartInput$1.invoke(TextFieldDelegate.kt:254) at androidx.compose.foundation.text.TextFieldDelegate$Companion$restartInput$1.invoke(TextFieldDelegate.kt:251) at androidx.compose.ui.text.input.TextInputServiceAndroid$createInputConnection$1.onEditCommands(TextInputServiceAndroid.android.kt:128) at androidx.compose.ui.text.input.RecordingInputConnection.endBatchEditInternal(RecordingInputConnection.android.kt:157) at androidx.compose.ui.text.input.RecordingInputConnection.addEditCommandWithBatch(RecordingInputConnection.android.kt:131) at androidx.compose.ui.text.input.RecordingInputConnection.commitText(RecordingInputConnection.android.kt:177) at com.android.internal.view.IInputConnectionWrapper.executeMessage(IInputConnectionWrapper.java:339) at com.android.internal.view.IInputConnectionWrapper$MyHandler.handleMessage(IInputConnectionWrapper.java:89) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7386) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:980)

Found another StringIndexOutOfBoundsException, which occours by adding two linebreaks (enter) in an empty text field.

BTW Thank you for this nice Libary. This makes the developmet for my project much easier.

Feature request: Nested lists

Can you provide some guide to implement nested list elements.
Eg.

  1. First item
  2. Second item
    1. First nested item
    2. Second nested item

Support String Operations on Rich Text while retaining styles

User can perform following operations on the text when its a string

  1. User can remove a text range
  2. User can replace a text range
  3. User can add text at an index

These operations are easier because text is a string , you can perform all these operations easily because String is a perfect representation of the Text user wrote , But not in the case of Rich Text

If you want to remove a range of text , You know the indexes , startIndex & endIndex

The problem is retaining styles

I propose that the following methods be added to RichTextState and be exposed publicly

  1. removeTextRange(startIndex : Int,endIndex : Int) : Boolean
  2. replaceTextRange(startIndex : Int,endIndex : Int,replacement : AnnotatedString) : Boolean
  3. addTextAtIndex(index : Int,text : AnnotatedString) : Boolean

When user calls these methods , text range is removed / replaced , or text is added at an index , Without affecting the styles of other text

Some RichTextStyle(s) are not recognized on creating RichTextValue from HTML String

The RichTextStyle(s) that are not recognized are the styles created using the object : RichTextStyle.
The comparison between RichTextStyle(s) should be improved to be something like comparing SpanStyle(s) and making a SpanStyle value as a member for the RichTextStyle interface.
Also there's some intentions to totally remove the RichTextStyle and simply use the SpanStyle to simplify handling these cases.

java.lang.StringIndexOutOfBoundsException

java.lang.StringIndexOutOfBoundsException: String index out of range: -2 at java.lang.String.substring(String.java:2064) at com.mohamedrejeb.richeditor.model.RichTextState.removeUnorderedList(RichTextState.kt:403) at com.mohamedrejeb.richeditor.model.RichTextState.toggleUnorderedList(RichTextState.kt:366)

Error occours for example if you inserting a list:

  • Point one
  • Point two

And than move the cursor to the very first position and than toggle list again.

Problem with text selection.

When using RichTextEditor and trying to select text there is a lag and issues with the precision of selection.
The issue is especially noticeable when after selecting a single word by long press, I try to extend or contract the selection by dragging selection handles. Sometimes I can't move the handles at all.

Example

Add support for changing styles for texts initialized to RichTextValue

Ex: RichTextValue(text = "This lib is awesome")
This initialized text does not react to style changes

my code

    var body by remember { mutableStateOf(RichTextValue(text = "This lib is awesome")) }

    RichTextEditor(
            modifier = Modifier,
            value = body,
            onValueChange = onValueChange,
            textStyle = textStyle.copy(fontSize = textSize),
            colors = TextFieldDefaults.textFieldColors(
                focusedIndicatorColor = Color.Transparent,
                unfocusedIndicatorColor = Color.Transparent,
                backgroundColor = Color.Transparent
            ),
            maxLines = maxLines ?: Int.MAX_VALUE,
            singleLine = singleLine,
        )
     NotesEditorMenu(
            modifier = Modifier
                .inlineSpacingMedium()
                .bottomSpacing(),
            styleSelected = styleSelected.value,
            onStyleSelected = {
                styleSelected.value = it
            },
            onClickBoldStyle = {
                body = body.toggleStyle(RichTextStyle.Bold)
            },
            onClickUnderlineStyle = {
                body = body.toggleStyle(RichTextStyle.Underline)
            },
            onClickItalicStyle = {
                body = body.toggleStyle(RichTextStyle.Italic)
            },
            onClickMarkStyle = {
                body = body.toggleStyle(RichTextStyle.Mark)
            },
            onClickLeftAlign = {
                textAlign = TextAlign.Left
            },
            onClickRightAlign = {
                textAlign = TextAlign.Right
            },
            onClickCenterAlign = {
                textAlign = TextAlign.Center
            }
        )
bug.mp4

Multiple paragraphs Not working

Here's the code where html

<p style="text-align: left;"><span style="font-weight: 700;">A Story</span></p><p style="text-align: left;"><span style="font-weight: 700;"></span></p><ul><li style="text-align: left;">something something something</li><li style="text-align: left;">Something something something</li><li style="text-align: left;">Something something something</li><li style="text-align: left;">Something something something</li></ul><p style="text-align: left;"></p><p style="text-align: center;">somethingsomething something something</p><p style="text-align: left;">something tomorrow something</p><p style="text-align: left;"><span style="font-weight: 700;">something like this</span></p><p style="text-align: left;"><span style="font-weight: 700;"></span></p><p style="text-align: right;"><span style="font-weight: 700;">something something something </span><span style="font-style: italic;">somethingsomething something something</span></p>

This html was generated for some text I wrote with multiple paragraphs with different alignment

Add HTML import and export support

  • Support creating RichTextValue from a html string
  • Support exporting RichTextValue as html string

This could be helpful specially when dealing with api and you need to share the rich text content between mobile and web so it needs it be saved as html in the database.

RichTextState.setHtml() not working on release build (isDebuggable = false)

There is a problem with the RichTextState.setHtml() method on non-debuggable builds.
No text is visible in RichText or BasicRichTextEditor when I use this method.
setText() is working correctly in the same type of build.

In builds with isDebuggable = true everything is working fine.

Sample code:

val html = "<p><b>Compose Rich Editor</b></p>"
val richTextState = rememberRichTextState()
richTextState.setHtml(html)

RichText(state = richTextState)

Allow storing Editor Value State somewhere else

rememberRichTextState() can only be called inside a compose context

We should be able to do something like this

val value = RichTextEditorValue() // this doesn't need to be remembered
val state = rememberRichTextState(myValue)

I know something like this exists in the previous deprecated API but I am talking about current implementation that supports paragraph , Since idea of separating the core is already there , This could be part of core. Which should be done when the implementation of editor is done

Not able to run for ios

Hi i added a build config for ios project using following config given in screen shot
Screenshot 2023-05-09 at 12 36 10 PM

But i am not able to build, getting following error:
`** BUILD FAILED **

The following build commands failed:
SwiftCompile normal arm64 Compiling\ ComposeViewControllerToSwiftUi.swift /Users/sunil/AndroidStudioProjects/Compose-Rich-Editor/sample/ios/Compose\ Rich\ Editor/ComposeViewControllerToSwiftUi.swift (in target 'Compose Rich Editor' from project 'Compose Rich Editor')
SwiftCompile normal arm64 /Users/sunil/AndroidStudioProjects/Compose-Rich-Editor/sample/ios/Compose\ Rich\ Editor/ComposeViewControllerToSwiftUi.swift (in target 'Compose Rich Editor' from project 'Compose Rich Editor')
(2 failures)`

complete logs file:
logs.docx

Add Copy/Paste support for Rich Text

For now the copy/paste works the same as the normal TextField. It just copies and pastes non styles text.
I'm currently working on adding this feature

Not worked toggleSpanStyle

Not worked toggleSpanStyle if you try set textDecoration like null.

Below my code for changing underline:
fun RichTextState.isUnderline(): Boolean = currentSpanStyle.textDecoration == TextDecoration.Underline
fun RichTextState.toggleUnderlineFont() { toggleSpanStyle( SpanStyle( textDecoration = if (isUnderline()) { null } else { TextDecoration.Underline } ) ) }

If i set textDecoration like Underline this is work, not work if i set null.

Allow implementing editor actions differently

Currently the editor options support addCode, addBold, This is like a toggle which turns on and user keeps going with the styles

However some people prefer editing differently , like myself , for example when I click bold , I want editor to grab the current word and just make it bold , unless I have a selection, This also allows editor to have no state other than selection, Not that previous API shouldn't be the way, but there's no way currently to do the above.

So Editor State of isBold on currently , isItalic on should be separated from the editor and allowed to have a different state

I implemented this initially in my editor which doesn't retain styles

https://github.com/Qawaz/markdown-compose/blob/5293904088ab9ace4d4b1e7e7788437cadd513d6/md-compose-core/src/commonMain/kotlin/com/wakaztahir/markdowncompose/editor/utils/TextFormatter.kt#L121

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.