Giter Site home page Giter Site logo

roosterjs's Introduction

Build Status

Rooster

Rooster is a framework-independent JavaScript rich-text editor neatly nested inside one HTML <div> element. Editing operations performed by end users are handled in simple ways to generate the final HTML.

Rooster is working on top of a middle layer data structure called "Content Model". All format API and editing operation are using this Content Model layer as content format, and finally convert to HTML and show it in editor.

To view the demo site, please click the link below:

RoosterJs Demo Site.

Upgrade from RoosterJs 8.*

Please see here.

Features

Packages

Rooster contains 6 basic packages.

  1. roosterjs: A facade of all Rooster code for those who want a quick start. Use the createEditor() function in roosterjs to create an editor with default configurations.

  2. roosterjs-content-model-core: Defines the core editor and plugin infrastructure. Use roosterjs-content-model-core instead of roosterjs to build and customize your own editor.

  3. roosterjs-content-model-api: Defines APIs for editor operations. Use these APIs to modify content and formatting in the editor you built using roosterjs-content-model-core.

  4. roosterjs-content-model-dom: Defines APIs for Content Model and DOM operations. This package do conversion between DOM tree and roosterjs Content Model.

  5. roosterjs-content-model-plugins: Defines basic plugins for common features.

  6. roosterjs-content-model-types: Defines public interfaces and enumerations, including Content Model types, API parameters and other types.

There are also some extension packages to provide additional functionalities.

  1. roosterjs-color-utils: Provide color transformation utility to make editor work under dark mode.

  2. roosterjs-react: Provide a React wrapper of roosterjs so it can be easily used with React.

To be compatible with old (8.*) versions, you can use EditorAdapter class from the following package which can act as a 8.* Editor:

  1. roosterjs-editor-adapter: Provide a adapter class EditorAdapter to work with Editor (9.*) and legacy plugins (via EditorAdapterOptions.legacyPlugins)

And the following packages are for old (8.*) compatibility:

  1. roosterjs-editor-core:
  2. roosterjs-editor-api:
  3. roosterjs-editor-dom:
  4. roosterjs-editor-plugins:
  5. roosterjs-editor-types:
  6. roosterjs-editor-types-compatible:

APIs

Rooster provides Content Model level APIs (in roosterjs-content-model-dom), core APIs (in roosterjs-content-model-core), and formatting APIs (in roosterjs-content-modelapi) to perform editing operations.

roosterjs-content-model-dom provides several levels of Content Model operations:

  • Create Content Model elements
  • Convert DOM tree to Content Model
  • Convert Content Model to DOM tree
  • Format handlers
  • A few DOM level API

roosterjs-content-model-core provides APIs for editor core. Editor class will call such APIs to perform basic editor operations. These APIs can be overridden by specifying API overrides in Editor options when creating the editor.

roosterjs-content-model-api provides APIs for scenario-based operations triggered by user interaction.

Plugins

Rooster supports plugins. You can use built-in plugins or build your own. Plugins call APIs to communicate with the editor. When an operation is performed by the user or when content is changed by code, the editor will trigger events for the plugins to handle.

Here's a sample plugin which will show a dialog containing "Hello Rooster" when an "a" is typed in the editor:

class HelloRooster implements EditorPlugin {
    getName() {
        return 'HelloRooster';
    }

    initialize(editor: IEditor) {}

    dispose() {}

    onPluginEvent(e: PluginEvent) {
        if (e.eventType == 'input' && e.rawEvent.which == 65) {
            alert('Hello Rooster');
        }
    }
}

Installation

Install via NPM or Yarn:

yarn add roosterjs

You can also install sub packages separately:

yarn add roosterjs-content-model-core

yarn add roosterjs-content-model-api

...

In order to run the code below, you may also need to install webpack:

yarn add webpack -g

Usage

A quick start

  1. Create editor.htm which contains a DIV with some styles, buttons to handle some click events and a reference to rooster.js (update with the path to your rooster.js file):
<html>
    <body>
        <div style="width: 500px; height: 400px; border: solid 1px black" id="contentDiv"></div>
        <button id="buttonB">B</button> <button id="buttonI">I</button>
        <button id="buttonU">U</button>
        <script src="rooster.js"></script>
        <script>
            var contentDiv = document.getElementById('contentDiv');
            var editor = roosterjs.createEditor(contentDiv);

            editor.setContent('Welcome to <b>RoosterJs</b>!');
            document.getElementById('buttonB').addEventListener('click', function () {
                roosterjs.toggleBold(editor);
            });
            document.getElementById('buttonI').addEventListener('click', function () {
                roosterjs.toggleItalic(editor);
            });
            document.getElementById('buttonU').addEventListener('click', function () {
                roosterjs.toggleUnderline(editor);
            });
        </script>
    </body>
</html>
  1. Navigate to editor.htm, you will see a editor shown in the page which includes buttons with bold, italic, underline actions.

Sample code

To view the demo site, please click here.

To build the demo site code yourself, follow these instructions:

  1. Get dependencies using yarn or npm:

    yarn
  2. Build the source code, and start the sample editor:

    yarn start
    

    or

    npm start
    

Debugging

There are two options for debugging:

  1. Debugging from VSCode

    • Ensure the sample editor is running
    • Set the breakpoints within VSCode
    • Select "Debug app in Chrome" from the VSCode debugging configuration dropdown
    • Run the scenario that needs to be debugged
  2. Debugging directly from the development tools within the web browser

    • The directions for how to do this are specific to each web browser. By opening the developer tools for the web browser that Rooster is running on, you will be able to set breakpoints in the code and debug accordingly.

Running tests

There are two ways that tests can be run:

  1. Run all tests or a single test from VSCode
    • (Skip if running all tests) Ensure the file that you want to test is selected (ie: toggleBold.ts or toggleBoldTest.ts)
    • Select "Test all files" or "Test current file" from the VSCode debugging configuration dropdown
  2. Run all tests from command line
    yarn test
    

Dependencies

As a NodeJs package, RoosterJs has dependencies for runtime (specified in package.json under each sub packages in "dependencies" section) and dependencies for build time (specified in package.json under root path in "devDependencies" section).

For runtime dependencies, there are two parts:

  • Internal dependencies (a RoosterJs package depends on other RoosterJs packages)
  • External dependencies (RoosterJs depends on other NPM packages)

Currently we have very few external dependencies. Before adding any new dependency, we need to check:

  1. What's the value of the new dependency and the code using the dependency bring into roosterjs? If we add a new dependency and create our new API to just call into the dependency, that new API doesn't actually bring too much value, and people who uses roosterjs in their project can do this themselves in their code, and we should not add such dependency to people who don't really need it.

  2. What's the dependency tree of the dependency? If we introduce a new dependency which has a deep dependency tree, we need to be careful since it means we are actually adding a lot of new dependencies and our code size may be increased a lot.

  3. How much functionalities do we need from the dependency? If the dependency provides a lot of functionalities but we actually only need a small piece of them, we may need to consider other solutions, such as find another smaller one, or do it ourselves.

  4. What's the license of the dependency? A dependency package under MIT license is good to be used for RoosterJs. For other licenses, we need to review and see if we can take it as a dependency.

If you still feel a new dependency is required after checking these questions, we can review it and finally decide whether we should add the new dependency.

For build time dependencies, it is more flexible to add new dependencies since it won't increase runtime code size or dependencies.

More documentation

We are still working on more documentation in roosterjs wiki and API reference.

License - MIT

License Copyright (c) Microsoft Corporation. All rights reserved.

Licensed under the MIT License.

roosterjs's People

Contributors

adjective-object avatar andres-ct98 avatar aniagonzalez avatar bryanvalverdeu avatar cyanzhong avatar dependabot[bot] avatar florian-msft avatar flyingbee2012 avatar francismengms avatar francismengx avatar gm-al avatar haven2world avatar ianeli1 avatar jingweixiong avatar jiuqingsong avatar juliaroldi avatar jvillalobos avatar kenotron avatar lego6245 avatar lufedi avatar marcinkiewicz avatar melvsparks avatar premnirmal avatar rain-zheng avatar redmondbowang avatar romanisa avatar shaipetel avatar stephenark30 avatar titrongt avatar tshibley 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  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

roosterjs's Issues

Style of numbers in number list is different with main text

Describe the bug
Style of numbers in number list is different with main text

To Reproduce
Steps to reproduce the behavior:

  1. Change font size to some larger ones, type something
  2. Go to a new line, type "1." and a space, then it becomes a number list
  3. Type something
    Then you will see "1." is smaller than the text

Expected behavior
the number "1." should have the same font size with text

with redux

How to combine Redux, for example, multiple editor instances, to save a history to reudx, and use redux-undo to undo/redo

Issue with BIU

Describe the bug
Sometimes Ctrl+I can't cancel italic style, similar with B,U

To Reproduce
Steps to reproduce the behavior:
(1) Type something
(2) Press 'ctrl+i'
(3) Continue typing, now in italics, and finish a word without hitting space
(4) Press 'ctrl+i'
(5) Hit space, and continue typing

Expected behavior
Not italic anymore

Device Information

  • OS: Windows 10
  • Browser Chrome 69, Edge 17.17134

When toggle header on header elements, selection is lost

When toggle header on existing header elements, we will unwrap the header elements first, this cause the selection to be lost, thus only the line where cursor is located (most likely the last line in original selection) gets toggled.

insertNode inserts text into the watermark placeholder span

Using the following code snippet:

        this.editor.addUndoSnapshot();
        let contentElement = this.createNodeFromContent(content); // creates a <span> with the contents
        this.editor.insertNode(contentElement, {
            position: ContentPosition.Begin,
            updateCursor: true,
            replaceSelection: false,
            insertOnNewLine: false,
        });
        this.editor.triggerContentChangedEvent('Source');

If the editor has a watermark placeholder, the content is inserted into the span containing the watermark. The content is then removed when the watermark is removed from the triggerContentChangedEvent call.

AltGr is treated as CTRL in Polski in Edge

Describe the bug
AltGr+Z should input ż, but not trigger Undo

To Reproduce

  1. Switch language to Polski
  2. Press AltGr (right Alt) + Z

Expected behavior
Type "ż"

Device Information

  • Browser Edge

Image Resize Plugin isn't compatible with touch + pen

Describe the bug
Touch + pen isn't supported when trying to resize image, page just pans
Also, drag target/red blocks are really small for touch

Device Information

  • OS: Windows 10 RS3
  • Browser Edge and Chrome

Additional context
Pen seems to be working for RS2

Support AMD module

We should consider supporting AMD module when compile and publish.
One solution is to have different lib folders, such as
lib
lib-amd
...

Circular dependency in roosterjs-editor-dom

We are trying to import roosterjs-react into our product and in process, have a step that ensures that the plugin being imported has no circular dependencies.
Unfortunately, importing roosterjs-editor-dom is causing this step to fail due to the following circular dependency:

Circular dependency in modules.
Attempting to add
roosterjs-editor-dom\lib\blockElements\NodeBlockElement.js

Dependencies:

  • roosterjs-editor-dom\lib\domWalker\getInlineElement.js
  • roosterjs-editor-dom\lib\domWalker\getBlockElement.js
  • roosterjs-editor-dom\lib\blockElements\NodeBlockElement.js

Do you think we can have a single dist file or are there any other ways to address this?

Consider smart merging list elements

User may accidentally create multiple list element without other content between them, such as
[ul] content [/ul]
[ul]
[ul]
content...
[/ul]
[/ul]

The render result looks good but the actual HTML structure is wrong here. We should merge the two top-level [ul] tag into single one.

Highlighting without selection

In office apps, when you use highlighting without any selection, then click on highlight will initiate the highlight and will highlight everything I select until I release the cursor. that is not the experience i see in the new rich text contro

Undo should allow empty snapshot

There is a check in undo code:
if (snapshot) ...

This check will fail when snapshot is an empty string. We should allow empty string as snapshot.

Undo Selection Marker logic prevents certain content from be replaced

To Reproduce
Steps to reproduce the behavior:

  1. Initialize the editor with this content:

<div>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</div><ol><li>BBBBBBB BBBBBBB BBB BB BBBBBBBBBB BB BBBBBBB BBB BB BBBBBBBBBBB (2d)<ul><li>a</li><li>CCCCCCCCC CCCC: CCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCoCCCCCC</li></ul></li></ol><ol><li>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (5d)</li><li>UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU. (3d)</li><li>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</li></ol>

  1. Open the sample site in Edge and select the first line (e.g. double click it), or the last line
  2. Press a key on the keyboard like 'a', text isn't replaced

Expected behavior
Text should be replaced

(sometimes it works, most times it doesn't)

Investigation so far
It looks like if you're trying add or remove elements (in this case the SPAN marker) under keypress event, there is a race condition which prevents your text from being replaced.

I've also made an attempt to fix the issue by using attribute only markers--adding them to the node's parent attribute list (this includes the node's index). There's at least one problem with this solution: the node's parent could be the contenteditable div itself, but the original issue is fixed.

A different attempt involved clearing the current selection and setting it back after calling remove markers, but that doesn't work (the selection range was created from scratch too).

Important: when I was investigating, I had to comment the code in ShowFormatState and ShowCursorPosition sample plugins as they also modify elements on the page to locate the source of the issue.

Device Information

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]: Edge
  • Version [e.g. 22] 42.17134.1

Additional context
Add any other context about the problem here.

how to modify enter tag with p?

Describe the bug
Why is enter tag div instead of P?
To Reproduce

Expected behavior
enter tag is p

Screenshots

Device Information
all

Additional context
how to modify enter tag with p?

RoosterJS: Editor only has list or table, content is not inserted properly for inserting at begin or end

RoosterJS base its insertion on object model.
For inserting at begin, it finds first block and insert content before it.
For inserting at end, it finds last block and insert content after it.

The current object model in RoosterJS has the block concept which essentially represents a parsing boundary, and is not equipped with Table or List. A List item or a table cell is a block, but beyond that (list or table), object model does not know.

So when you insert at end, and the end happens to be a table cell. The content will be inserted after the cell, but before the closing TR/Table.

A possible fix, as I discussed with Jia is to introduce a ContentBlock concept to representing block larger than block we have, i.e. TR, Table, List. BlockElement cannot be nested, but ContentBlock can be nested. For inserting at end and end is a table cell, we should go from a block element, and find topest Content Block and insert after.

Regards,
Nick Huang

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.