1.0.41 • Published 3 years ago

react-feel v1.0.41

Weekly downloads
-
License
MIT
Repository
-
Last release
3 years ago

FEEL (Fabric Editor Extensions Library)

FEEL is a framework for building Fabric Editor Extensions https://www.npmjs.com/package/@atlaskit/editor-core

Installation

Install the module

npm install react-feel --save

Import the module

import { FeelContext } from 'react-feel'

Dependencies

react-redux and @atlaskit/editor-core FEEL uses redux so that the extension event handlers (adding a new extension) can communicate with the FeelContext component (to do stuff like opening modals and saving user input into the extension nodes). It relies on the EditorActions to do this so this module has to be used with EditorContext and WithEditorActions @atlaskit/editor-core components.

import { Editor, EditorContext, WithEditorActions } from '@atlaskit/editor-core';
import FeelContext from 'react-feel';
...
render() {
	return (
		<EditorContext>
			<WithEditorActions
				render={actions => (
					<FeelContext actions={actions} render={fabricExtensionService => (
							{ /* FEEL's fabricExtensionService can be used here */}
							<Editor />
					)} />
			</WithEditorActions>
		</EditorContext>
	)
}

FeelContext uses render props to inject the fabricExtensionService. You can use this service to get the extensionHandlers and the insertMenuItems values that the Fabric Editor needs for handling custom extensions.

<EditorContext>
	<WithEditorActions
		render={actions => (
			<FeelContext actions={actions} render={fabricExtensionService => {
					const { getInsertMenuItems, getTransformers } = fabricExtensionService;
					return (
						<Editor
							allowExtensions={true}
							insertMenuItems={getInsertMenuItems()}
							extensionHandlers={getTransformers()}
					)
			}} />
	</WithEditorActions>
</EditorContext>

Redux State

FEEL has it's own internal redux state that handles the communication between your extension handlers and the fabric editor. Per redux's best practices, you should avoid having multiple stores inside a react-redux application so if you're adding feel to an existing react-redux application you'll have to add some additional configuration

Import the FEEL Reducers and Sagas into your application's combineReducers method and redux-saga generator function

import { getFeelReducers } from 'react-feel';

export default combineReducers({
    ...getFeelReducers()
});
import { getFeelSagas } from 'react-feel';

export default function*() {
    yield [
        ...getFeelSagas()
    ];
}

Then pass in a prop to tell Feel there is an existing redux state

<FeelContext useExistingState={true}>

Adding extensions

Create a handler that extends FEEL's ExtensionHandler class and override each method

import { ExtensionHandler } from 'react-feel';

export default class NewExtension extends ExtensionHandler {
	// override base class's methods to define your extension
}

Pass your extensionHandlers as a prop to the FeelContext component

render() {
	return (
		...
		<FeelContext extensions={[NewExtension]}>
	)
}

You can import some actions, addNewExtensionAsync and addNewExtensionRequested to add your extension to the ADF Document

import { connect } from 'react-redux';
import { newExtensionActions as actions } from 'react-feel';

connect(null, {
	addNewExtensionAsync: actions.addNewExtensionAsync,
	addNewExtensionRequested: actions.addNewExtensionRequested
})(class AComponent extends Component {...})

You can call addNewExtensionAsync to open the modal defined in _yourHandler.getNewExtensionModal by calling it with your extKey as a payload

<div onClick={() => this.props.addNewExtensionAsync({
	type: ${extKey}
})}></div>

Or you can call addNewExtensionRequested and pass the structure of your extension in ADF to get inserted into the ADF Document

Using the / Quick Insert menu

In order to use the / quick insert menu inside the fabric editor you have to define a getQuickInsertItem method inside your extension handler. This expects an object to be returned that defines how the item will appear in the quick insert menu

{
	title: string,
	icon: Function | React Element,
	action: (insert) => { ... }
}

The action property is a function that provides a insert function as a parameter, you call this method with the structure of your ADF extension to be inserted into the document. Note that if you are using addNewExtensionAsync to get user input, you can pass in an empty Fragment as the payload to the insert function. If you do not call insert an error is thrown. You can import Fragement from the prosemirror-model package.

import { Fragment } from 'prosemirror-model';

...

{
	title: string,
	icon: Function | React Element,
	action: (insert) => { 
		// call addNewExtensionAsync to open
		// your extensionHandler's modal
		return insert(Fragement.empty)
	 }
}

Getting additional data into your extensions

FeelContext provides an additional prop called providers where you can pass additional data to your extensions

<FeelContext providers={
	[
		{
			extensionKey: 'media',
			data: {
				flavors: [
					'rocky road',
					'vanilla',
					'chocolate'
				]
			}
		}
	]
} />

When FEEL calls your getExtensionComponent method on your handler it will pass your provider as the second parameter for you to pass into your extension component

yourExtensionHandler.getExtensionComponent(ext, provider) {
	// where providers = {flavors: [ 'rocky road', 'vanilla','chocolate']}
	// which you can pass into your extension
	return <Extension availableFlavors={provider.flavors}>
}

Additional Information

Core concepts

the extension-service wraps all the extension handlers and performs some global operations (getTransformers, getInsertMenuItems... etc)

the extension-handler is the core class for a custom fabric extension and performs operations to a specific extension

Folder structure

Each extension should be its own module that defines its components, handler, state, tests and styles.

src/
	/{extKey}
		{extKey}.handler.jsx
		{extKey}.state.js
		{extKey}.styles.js
		/components
			extension.jsx // <-- actual react component that Fabric will map to, should be named this
		index.js // entry point for everything you want to expose for your extension

Adding Keyboard Shortcuts

passing in a keyboardShortcuts prop to the FeelContext will enable custom keyboard shortcuts for a given extension. The format should look like this:

<FeelContext keyboardShortcuts={
	[
		{
			type: '${extensionKey}',
			key: 'KeyI',
			modifier: ['ctrl']
		}
	]
} />

Where type is the extension to target, key is a keyboard event key code, and the optional modifier key passed as an array which requires modifier keys to be pressed in conjunction with value of key. modifier options include: 'ctrl', 'cmd', 'shift', and 'alt'.

Then you create a onShortcutPress method inside your ExtensionHandler to respond to your extension's keyboard shortcut being pressed

If you want to render the shortcut inside the + dropdown, you can attach your keyboard shortcut to your ExtensionHandler's super method inside the constructor

export default class ExampleHandler extends ExtensionHandler {
    // add keyboard shortcut to constructor
    constructor(state, shortcuts) {
        super(
            {
                extensionType: 'com.atlassian.mesa',
                extensionKey: 'example'
            },
            state,
            shortcuts
        );
    }

And then you have access to this.shortcut inside your ExtensionHandler

Redux Actions available

Adding extensions

addNewExtensionAsync: If you want the user to provide some more information, when adding a new extension, you can use this Async action. Pass in an object with type: ${extensionKey} as the parameter to this action. This will trigger your modal to pop up to get more information from the user. You'll have to define a getNewExtensionModal method inside your extensionHandler to return your Modal.

addNewExtensionRequested: Will add your extension into the editor, requires the ADF structure of your extension as a parameter.

addNewExtensionCanceled: Will close your modal if opened with addNewExtensionAsync

Trello board

https://trello.com/b/UzYM1KaM/feel

1.0.40

3 years ago

1.0.41

3 years ago

1.0.39

4 years ago

2.0.2

5 years ago

1.0.38

5 years ago

1.0.37

5 years ago

2.0.1

5 years ago

1.1.0-4

5 years ago

1.1.0-3

5 years ago

1.1.0-2

5 years ago

1.0.37-rc1

5 years ago

1.1.0-1

5 years ago

1.0.36

5 years ago

1.0.35-rc1

5 years ago

1.0.34

5 years ago

1.0.34-rc1

5 years ago

1.0.33

5 years ago

1.0.31-rc2

5 years ago

1.0.31-rc1

5 years ago

1.0.30

5 years ago

1.0.30-rc3

5 years ago

1.0.30-rc2

5 years ago

1.0.30-rc1

5 years ago

1.0.29

5 years ago

1.0.28-rc3

5 years ago

1.0.28

5 years ago

1.0.28-rc2

5 years ago

1.0.28-rc1

5 years ago

1.0.27

5 years ago

1.0.26

5 years ago

1.0.25

5 years ago

1.0.24

5 years ago

1.0.23

5 years ago

1.0.22

5 years ago

1.0.21

5 years ago

1.0.20

5 years ago

1.0.19

5 years ago

1.0.18

5 years ago

1.0.17

5 years ago

1.0.16

6 years ago

1.0.15

6 years ago

1.0.14

6 years ago

1.0.13

6 years ago

1.0.12

6 years ago

1.0.11

6 years ago

1.0.10

6 years ago

1.0.9

6 years ago

1.0.7

6 years ago

1.0.6

6 years ago

1.0.5

6 years ago

1.0.4

6 years ago

1.0.3

6 years ago

1.0.2

6 years ago

1.0.1

6 years ago

1.0.0

6 years ago