├── examples
├── .babelrc
├── dist
│ └── index.html
├── index.js
├── webpack.config.babel.js
├── Example.js
└── webpack.config.live.babel.js
├── .storybook
├── .babelrc
└── main.js
├── src
├── components
│ ├── ReactTrixRTEInput
│ │ ├── ReactTrixRTEInput.style.scss
│ │ ├── index.js
│ │ ├── constants.js
│ │ ├── ReactTrixRTEInput.test.js
│ │ └── ReactTrixRTEInput.jsx
│ ├── ReactTrixRTEToolbar
│ │ ├── index.js
│ │ ├── ToolbarComponent
│ │ │ ├── ToolbarButton
│ │ │ │ ├── index.js
│ │ │ │ ├── ToolbarButton.test.js
│ │ │ │ └── ToolbarButton.jsx
│ │ │ ├── ToolbarSpacer
│ │ │ │ ├── index.js
│ │ │ │ ├── ToolbarSpacer.jsx
│ │ │ │ └── ToolbarSpacer.test.js
│ │ │ ├── ToolbarLinkDialog
│ │ │ │ ├── index.js
│ │ │ │ ├── ToolbarLinkDialog.test.js
│ │ │ │ └── ToolbarLinkDialog.jsx
│ │ │ └── ToolbarButtonGroup
│ │ │ │ ├── index.js
│ │ │ │ ├── ToolbarButtonGroup.test.js
│ │ │ │ └── ToolbarButtonGroup.jsx
│ │ ├── ReactTrixRTEToolbar.test.js
│ │ ├── ReactTrixRTEToolbar.jsx
│ │ └── constants.js
│ └── Shared
│ │ └── utils.js
└── index.js
├── index.js
├── .babelrc
├── config
├── jest
│ ├── fileTransform.js
│ ├── setup.js
│ └── cssTransform.js
└── polyfills.js
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .npmignore
├── .eslintrc
├── jest.config.js
├── CONTRIBUTING.md
├── LICENSE
├── webpack.config.babel.js
├── CHANGELOG.md
├── package.json
├── stories
├── 1-ReactTrixWithCustomToolbar.stories.js
└── 0-ReactTrixInputRTE.stories.js
├── readme.md
└── dist
└── index.js
/examples/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"]
3 | }
--------------------------------------------------------------------------------
/.storybook/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"]
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEInput/ReactTrixRTEInput.style.scss:
--------------------------------------------------------------------------------
1 | @import "trix/dist/trix";
2 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // require('./dist/manifest');
2 | // require('./dist/vendor');
3 | module.exports = require('./dist/index');
4 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEInput/index.js:
--------------------------------------------------------------------------------
1 | import ReactTrixRTEInput from './ReactTrixRTEInput';
2 |
3 | export default ReactTrixRTEInput;
4 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/index.js:
--------------------------------------------------------------------------------
1 | import ReactTrixRTEToolbar from "./ReactTrixRTEToolbar";
2 |
3 | export default ReactTrixRTEToolbar;
4 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ToolbarComponent/ToolbarButton/index.js:
--------------------------------------------------------------------------------
1 | import ToolbarButton from "./ToolbarButton";
2 |
3 | export default ToolbarButton;
4 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ToolbarComponent/ToolbarSpacer/index.js:
--------------------------------------------------------------------------------
1 | import ToolbarSpacer from "./ToolbarSpacer";
2 |
3 | export default ToolbarSpacer;
4 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ToolbarComponent/ToolbarLinkDialog/index.js:
--------------------------------------------------------------------------------
1 | import ToolbarLinkDialog from "./ToolbarLinkDialog"
2 |
3 | export default ToolbarLinkDialog;
4 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ToolbarComponent/ToolbarButtonGroup/index.js:
--------------------------------------------------------------------------------
1 | import ToolbarButtonGroup from "./ToolbarButtonGroup";
2 |
3 | export default ToolbarButtonGroup;
4 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"],
3 | "plugins": [
4 | "@babel/plugin-proposal-object-rest-spread",
5 | "@babel/plugin-transform-react-jsx"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEInput/constants.js:
--------------------------------------------------------------------------------
1 | export const RAILS_SERVICE_BLOB_URL = "/rails/active_storage/blobs/:signed_id/*filename";
2 | export const RAILS_DIRECT_UPLOADS_URL = "/rails/active_storage/direct_uploads";
3 |
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const path = require('path');
3 |
4 | module.exports = {
5 | process(src, filename) {
6 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ToolbarComponent/ToolbarSpacer/ToolbarSpacer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function ToolbarSpacer() {
4 | return();
5 | }
6 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v1
10 | - name: Run Jest
11 | uses: stefanoeb/jest-action@1.0.2
12 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import ReactTrixRTEInput from "./components/ReactTrixRTEInput";
2 | import ReactTrixRTEToolbar from "./components/ReactTrixRTEToolbar/ReactTrixRTEToolbar";
3 |
4 | export {
5 | ReactTrixRTEInput,
6 | ReactTrixRTEToolbar,
7 | };
8 |
--------------------------------------------------------------------------------
/config/jest/setup.js:
--------------------------------------------------------------------------------
1 | import Enzyme from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 | // React 16 Enzyme adapter
4 | Enzyme.configure({ adapter: new Adapter() });
5 | // Make Enzyme functions available in all test files without importing
6 |
--------------------------------------------------------------------------------
/examples/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 | /.pnp
4 | .pnp.js
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ToolbarComponent/ToolbarSpacer/ToolbarSpacer.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { shallow } from "enzyme";
3 | import ToolbarSpacer from "./ToolbarSpacer";
4 |
5 | describe("ToolbarSpacer", () => {
6 | it("renders ToolbarSpacer without crashing", () => {
7 | const wrapper = shallow();
8 | expect(wrapper).toHaveLength(1);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ToolbarComponent/ToolbarLinkDialog/ToolbarLinkDialog.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { shallow } from "enzyme";
3 | import ToolbarLinkDialog from "./ToolbarLinkDialog";
4 |
5 | describe("ToolbarLinkDialog", () => {
6 | it("renders the ToolbarLinkDialog without crashing", () => {
7 | const wrapper = shallow();
8 | expect(wrapper).toHaveLength(1);
9 | })
10 | });
11 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *.log
2 | npm-debug.log*
3 | yarn-error.log
4 |
5 | # Coverage directory used by tools like istanbul
6 | coverage
7 | .nyc_output
8 |
9 | # Dependency directories
10 | node_modules
11 |
12 | # npm package lock
13 | package-lock.json
14 | yarn.lock
15 |
16 | # project files
17 | src
18 | test
19 | examples
20 | CHANGELOG.md
21 | .travis.yml
22 | .editorconfig
23 | .eslintignore
24 | .eslintrc
25 | .babelrc
26 | .gitignore
27 |
28 |
29 | # lock files
30 | package-lock.json
31 | yarn.lock
32 |
33 | # others
34 | .DS_Store
35 | .idea
36 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ToolbarComponent/ToolbarButton/ToolbarButton.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import ToolbarButton from './ToolbarButton';
4 |
5 | describe('ToolbarButton', () => {
6 | it("renders the ToolbarButton without crashing", () => {
7 | const wrapper = shallow(
8 |
14 | );
15 | expect(wrapper).toHaveLength(1);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Shared/utils.js:
--------------------------------------------------------------------------------
1 | export const groupBy = (collection, prop) => {
2 | return Object.keys(collection).reduce((groups, item) => {
3 | const groupKey = collection[item][prop];
4 | groups[groupKey] = groups[groupKey] || []
5 | groups[groupKey].push(collection[item])
6 | return groups
7 | }, {});
8 | }
9 |
10 | export const getUniqInputId = (prefix = 'react-trix-rte-input-') => {
11 | const dateTimestamp = new Date().getTime();
12 | const randomSalt = Math.random().toFixed(9).slice(2)
13 | return `${prefix}${dateTimestamp}-${randomSalt}`
14 | }
15 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "es6": true,
7 | "jest": true
8 | },
9 | "parserOptions": {
10 | "ecmaVersion": 7,
11 | "sourceType": "module",
12 | "ecmaFeatures": {
13 | "jsx": true
14 | }
15 | },
16 | "plugins": [
17 | "react"
18 | ],
19 | "extends": ["eslint:recommended", "plugin:react/recommended"],
20 | "rules": {
21 | "no-multiple-empty-lines": ["error", { "max": 2 }],
22 | "indent": ["error", 2, { "SwitchCase": 1 }],
23 | "no-console": 1
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import Example from './Example';
5 | import { AppContainer } from 'react-hot-loader';
6 | // AppContainer is a necessary wrapper component for HMR
7 |
8 | const render = (Component) => {
9 | ReactDOM.render(
10 |
11 |
12 | ,
13 | document.getElementById('root')
14 | );
15 | };
16 |
17 | render(Example);
18 |
19 | // Hot Module Replacement API
20 | if (module.hot) {
21 | module.hot.accept('./Example', () => {
22 | render(Example)
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | coverageReporters: [
3 | 'json',
4 | 'lcov',
5 | 'text-summary'
6 | ],
7 | moduleFileExtensions: [
8 | 'js',
9 | 'jsx',
10 | 'scss'
11 | ],
12 | modulePaths: [
13 | './src'
14 | ],
15 | setupFiles: [
16 | '/config/jest/setup.js'
17 | ],
18 | transform: {
19 | '^.+\\.(js|jsx)$': '/node_modules/babel-jest',
20 | '^.+\\.css$': '/config/jest/cssTransform.js',
21 | '^(?!.*\\.(js|jsx|css|json)$)': '/config/jest/fileTransform.js'
22 | },
23 | transformIgnorePatterns: [
24 | '[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/config/polyfills.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | if (typeof Promise === 'undefined') {
4 | // Rejection tracking prevents a common issue where React gets into an
5 | // inconsistent state due to an error, but it gets swallowed by a Promise,
6 | // and the user has no idea what causes React's erratic future behavior.
7 | require('promise/lib/rejection-tracking').enable();
8 | window.Promise = require('promise/lib/es6-extensions.js');
9 | }
10 |
11 | // fetch() polyfill for making API calls.
12 | require('whatwg-fetch');
13 |
14 | // Object.assign() is commonly used with React.
15 | // It will use the native implementation if it's present and isn't buggy.
16 | Object.assign = require('object-assign');
17 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ToolbarComponent/ToolbarButtonGroup/ToolbarButtonGroup.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { shallow } from "enzyme";
3 | import ToolbarButtonGroup from "./ToolbarButtonGroup";
4 |
5 | describe("ToolbarButtonGroup", () => {
6 | let defaultProps = {
7 | groupName: "text-tools",
8 | toolbarActionOptions: [{
9 | type: "button",
10 | classNames: "trix-button trix-button--icon trix-button--icon-bold",
11 | languageKey: "bold",
12 | tabIndex: "-1",
13 | trixButtonGroup: "text-tools",
14 | }]
15 | }
16 |
17 | it("renders the ToolbarButtonGroup without crashing", () => {
18 | const wrapper = shallow();
19 | expect(wrapper).toHaveLength(1);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ToolbarComponent/ToolbarButtonGroup/ToolbarButtonGroup.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import ToolbarButton from "../ToolbarButton";
4 | import { TOOLBAR_ACTION_GROUP_OPTS } from "../../constants";
5 |
6 | function ToolbarButtonGroup(props) {
7 | const { groupName, toolbarActionOptions } = props;
8 |
9 | function renderToolbarActions() {
10 | const toolbarButtonsHTML = []
11 | toolbarActionOptions.forEach((toolbarActionOption, index) => {
12 | toolbarButtonsHTML.push()
13 | })
14 |
15 | return toolbarButtonsHTML
16 | }
17 |
18 | return (
19 |
20 | {renderToolbarActions()}
21 |
22 | )
23 | }
24 |
25 | ToolbarButtonGroup.propTypes = {
26 | groupName: PropTypes.string.isRequired,
27 | toolbarActionOptions: PropTypes.array.isRequired
28 | }
29 |
30 | export default ToolbarButtonGroup;
31 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | stories: ['../stories/**/*.stories.js'],
5 | addons: ['@storybook/addon-actions', '@storybook/addon-links', '@storybook/addon-cssresources'],
6 | webpackFinal: (config) => {
7 | return {
8 | ...config,
9 | module: {
10 | ...config.module,
11 | rules: [
12 | {
13 | test: /\.(scss)$/,
14 | loader: 'style-loader!css-loader!sass-loader'
15 | },
16 | {
17 | test: /\.(js|jsx)$/,
18 | exclude: /node_modules/,
19 | include: path.join(__dirname, '../stories'),
20 | use: [
21 | {
22 | loader: 'babel-loader',
23 | options: {
24 | presets: ['@babel/preset-env', '@babel/preset-react']
25 | }
26 | }
27 | ]
28 | }
29 | ]
30 | },
31 | resolve: {
32 | extensions: ['.js', '.jsx', '.scss']
33 | },
34 | }
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We love contributions from everyone. Please open a issue for discussion or raise a
4 | pull request with the changes you need in the package.
5 |
6 | ## Contributing Code
7 | To start contributing to the package. We should follow following steps.
8 |
9 | - Fork the repository first to your GitHub account.
10 | - Install all package dependencies by executing `yarn/npm install` in your console.
11 | - Add test cases for the changes made in the pull request. Make sure they are passing by running `yarn/npm test`.
12 | - Update documentation for the changes made if needed.
13 | - Add a simple story to demonstrate the change for the end user if needed.
14 | - Add a changelog for the new feature or fixes.
15 |
16 | Push to your fork. Write a [good commit message][commit]. Submit a pull request.
17 |
18 | [commit]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
19 |
20 | Please do not self reject your thoughts. Let's discuss even smallest feature request as well and make
21 | this package better for everybody's use.
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Abhay Nikam
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import webpack from 'webpack'; //eslint-disable-line
3 | import CleanWebpackPlugin from 'clean-webpack-plugin';
4 |
5 | export default () => ({
6 | mode: 'production',
7 | entry: {
8 | index: path.join(__dirname, './index.js')
9 | },
10 |
11 | output: {
12 | filename: 'bundle.js',
13 | path: path.resolve(__dirname, 'dist')
14 | },
15 |
16 | module: {
17 | rules: [
18 | {
19 | test: /.jsx?$/,
20 | exclude: /node_modules/,
21 |
22 | use: [
23 | {
24 | loader: 'babel-loader',
25 | options: {
26 | presets: ['@babel/preset-env', '@babel/preset-react']
27 | }
28 | }
29 | ]
30 | },
31 | {
32 | test: /\.(scss)$/,
33 | loader: 'style-loader!css-loader!sass-loader'
34 | }
35 | ]
36 | },
37 |
38 | resolve: {
39 | extensions: ['.js', '.jsx', '.scss']
40 | },
41 |
42 | plugins: [
43 | // Clean dist folder
44 | new CleanWebpackPlugin(['./dist/build.js'])
45 | ]
46 | });
47 |
--------------------------------------------------------------------------------
/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import CleanWebpackPlugin from 'clean-webpack-plugin';
3 | const packageJson = require('./package.json');
4 |
5 | export default () => ({
6 | mode: 'production',
7 | entry: {
8 | index: path.join(__dirname, 'src/index.js')
9 | },
10 |
11 | output: {
12 | path: path.join(__dirname, 'dist'),
13 | filename: '[name].js',
14 | library: packageJson.name,
15 | libraryTarget: 'umd',
16 | globalObject: 'this'
17 | },
18 |
19 | module: {
20 | rules: [
21 | {
22 | test: /.jsx?$/,
23 | exclude: /node_modules/,
24 | include: path.join(__dirname, 'src'),
25 | use: [
26 | {
27 | loader: 'babel-loader',
28 | options: {
29 | presets: ['@babel/preset-env', '@babel/preset-react']
30 | }
31 | }
32 | ]
33 | },
34 | {
35 | test: /\.(scss)$/,
36 | loader: 'style-loader!css-loader!sass-loader'
37 | }
38 | ]
39 | },
40 |
41 | resolve: {
42 | extensions: ['.js', '.jsx', '.scss']
43 | },
44 |
45 | externals: {
46 | react: 'react',
47 | reactDOM: 'react-dom'
48 | },
49 |
50 | plugins: [new CleanWebpackPlugin(['dist/*.*'])],
51 | optimization: {
52 | splitChunks: {
53 | name: 'vendor',
54 | minChunks: 2
55 | }
56 | }
57 | });
58 |
--------------------------------------------------------------------------------
/examples/Example.js:
--------------------------------------------------------------------------------
1 | import Trix from 'trix';
2 | import React from 'react';
3 |
4 | import { ReactTrixRTEInput, ReactTrixRTEToolbar } from '../src/index';
5 |
6 | class Example extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | handleChange(e, newValue) {
12 | console.log(newValue);
13 | }
14 |
15 | handleFocus(event) {
16 | console.log("On Focus..");
17 | }
18 |
19 | render() {
20 | const customOption = {
21 | table: {
22 | type: "button",
23 | classNames: "trix-button trix-button--icon trix-button--icon-bold",
24 | languageKey: "table",
25 | tabIndex: "-1",
26 | trixButtonGroup: "text-tools",
27 | elementProps: {
28 | onClick: () => { alert("clicked") },
29 | },
30 | data: {
31 | trixAttribute: "table",
32 | trixKey: "ta",
33 | },
34 | }
35 | };
36 |
37 | return (
38 |
39 |
43 |
49 |
50 | );
51 | }
52 | }
53 |
54 | export default Example;
55 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ToolbarComponent/ToolbarButton/ToolbarButton.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from 'prop-types';
3 |
4 | import { TOOLBAR_LANGUAGE_OPTS } from "../../constants";
5 |
6 | function ToolbarButton(props) {
7 | const { type, classNames, data, tabIndex, languageKey, elementProps } = props;
8 |
9 | let dataAttributeOptions = {};
10 | if(data) {
11 | const { trixAttribute, trixKey, trixAction } = data;
12 | if(trixAttribute) dataAttributeOptions["data-trix-attribute"] = trixAttribute;
13 | if(trixAction) dataAttributeOptions["data-trix-action"] = trixAction;
14 | if(trixAttribute) dataAttributeOptions["data-trix-key"] = trixKey;
15 | }
16 |
17 | return (
18 |
28 | )
29 | }
30 |
31 | ToolbarButton.defaultProps = {
32 | elementProps: {},
33 | }
34 |
35 | ToolbarButton.propTypes = {
36 | type: PropTypes.string.isRequired,
37 | classNames: PropTypes.string.isRequired,
38 | data: PropTypes.object,
39 | tabIndex: PropTypes.string,
40 | languageKey: PropTypes.string.isRequired,
41 | elementProps: PropTypes.object,
42 | }
43 |
44 | export default ToolbarButton;
45 |
46 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ToolbarComponent/ToolbarLinkDialog/ToolbarLinkDialog.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { TOOLBAR_LANGUAGE_OPTS } from "../../constants";
3 |
4 | export default function ToolbarLinkDialog() {
5 | return (
6 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ReactTrixRTEToolbar.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import ReactTrixRTEToolbar from './ReactTrixRTEToolbar';
4 | import ToolbarButtonGroup from "./ToolbarComponent/ToolbarButtonGroup";
5 | import ToolbarSpacer from "./ToolbarComponent/ToolbarSpacer";
6 |
7 | describe('ReactTrixRTEToolbar', () => {
8 | it('renders without crashing', () => {
9 | const wrapper = shallow();
10 | expect(wrapper).toHaveLength(1);
11 | });
12 |
13 | it('renders with toolbarId', () => {
14 | const wrapper = shallow();
15 | expect(wrapper.find('trix-toolbar').props().id).toEqual("react-trix-rte-editor");
16 | });
17 |
18 | it('renders toolbar with specified toolbarActions', () => {
19 | let toolbarActions = ["bold", "indent", "undo"]
20 | const wrapper = shallow();
21 | expect(wrapper.find(ToolbarButtonGroup)).toHaveLength(3);
22 | });
23 |
24 | it('renders toolbar without grouping actions', () => {
25 | const wrapper = shallow();
26 | expect(wrapper.find('.trix-button-group')).toHaveLength(1);
27 | });
28 |
29 | it('renders toolbar with grouping actions', () => {
30 | const wrapper = shallow();
31 | expect(wrapper.find(ToolbarButtonGroup)).toHaveLength(4);
32 | expect(wrapper.find(ToolbarSpacer)).toHaveLength(1);
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/examples/webpack.config.live.babel.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import webpack from 'webpack';
3 |
4 | export default () => ({
5 | mode: 'development',
6 | entry: [
7 | 'react-hot-loader/patch',
8 | // activate HMR for React
9 |
10 | 'webpack-dev-server/client?http://localhost:8080',
11 | // bundle the client for webpack-dev-server
12 | // and connect to the provided endpoint
13 |
14 | 'webpack/hot/only-dev-server',
15 | // bundle the client for hot reloading
16 | // only- means to only hot reload for successful updates
17 |
18 | './examples/index.js'
19 | // the entry point of our app
20 | ],
21 |
22 | output: {
23 | filename: 'bundle.js',
24 | path: path.resolve(__dirname, 'dist'),
25 | publicPath: '/'
26 | // necessary for HMR to know where to load the hot update chunks
27 | },
28 |
29 | devtool: 'inline-source-map',
30 |
31 | devServer: {
32 | hot: true,
33 | // enable HMR on the server
34 |
35 | contentBase: path.resolve(__dirname, 'dist'),
36 | // match the output path
37 |
38 | publicPath: '/',
39 | // match the output `publicPath`
40 |
41 | stats: 'minimal'
42 | },
43 |
44 | module: {
45 | rules: [
46 | {
47 | test: /.jsx?$/,
48 | exclude: /node_modules/,
49 | use: [
50 | {
51 | loader: 'babel-loader',
52 | options: {
53 | presets: ['@babel/preset-env', '@babel/preset-react']
54 | }
55 | }
56 | ]
57 | },
58 | {
59 | test: /\.(scss)$/,
60 | loader: 'style-loader!css-loader!sass-loader'
61 | }
62 | ]
63 | },
64 |
65 | resolve: {
66 | extensions: ['.js', '.jsx', '.scss']
67 | },
68 |
69 | plugins: [new webpack.HotModuleReplacementPlugin()],
70 | optimization: {
71 | namedModules: true
72 | }
73 | });
74 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEInput/ReactTrixRTEInput.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import ReactTrixRTEInput from './ReactTrixRTEInput';
4 | import { RAILS_DIRECT_UPLOADS_URL, RAILS_SERVICE_BLOB_URL } from "./constants";
5 |
6 | describe('ReactTrixRTEInput', () => {
7 | it('renders without crashing', () => {
8 | const wrapper = shallow();
9 | expect(wrapper).toHaveLength(1);
10 | });
11 |
12 | it('renders with toolbarId', () => {
13 | const wrapper = shallow();
14 | expect(wrapper.find('trix-editor').props().toolbar).toEqual("react-trix-rte-editor");
15 | });
16 |
17 | it('renders with default value', () => {
18 | const wrapper = shallow();
19 | expect(wrapper.find('input').props().value).toEqual("React Trix RTE
");
20 | });
21 |
22 | it('renders with rails direct upload URL', () => {
23 | const wrapper = shallow();
24 | expect(wrapper.find('trix-editor').props()['data-direct-upload-url']).toEqual(RAILS_DIRECT_UPLOADS_URL);
25 | expect(wrapper.find('trix-editor').props()['data-blob-url-template']).toEqual(RAILS_SERVICE_BLOB_URL);
26 | });
27 |
28 | it('renders with custom rails direct upload URL', () => {
29 | const wrapper = shallow();
30 | expect(wrapper.find('trix-editor').props()['data-direct-upload-url']).toEqual("A");
31 | expect(wrapper.find('trix-editor').props()['data-blob-url-template']).toEqual("B");
32 | });
33 |
34 | it('renders with placeholder', () => {
35 | const wrapper = shallow();
36 | expect(wrapper.find('trix-editor').props().placeholder).toEqual("Enter the description");
37 |
38 | const withoutPlaceholderWrapper = shallow();
39 | expect(withoutPlaceholderWrapper.find('trix-editor').props().placeholder).toEqual(undefined);
40 | });
41 |
42 | it('renders with autofocus', () => {
43 | const wrapper = shallow();
44 | expect(wrapper.find('trix-editor').props().autofocus).toEqual(true);
45 | });
46 |
47 | it('renders with custom class via className prop', () => {
48 | const wrapper = shallow();
49 | expect(wrapper.find('trix-editor').props().class).toEqual('tx-editor')
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change log
2 |
3 | ## master (unreleased)
4 |
5 | ## 1.0.16 (14-Jan-2022)
6 | * Fixes multiple instances of Trix editor triggering same onChange event. Fixes #47. ([@abhaynikam][])
7 |
8 | ## 1.0.15 (18-Nov-2021)
9 | * Enable custom URL for Rails direct upload with `railsDirectUploadUrl` and `railsBlobUrl` prop in `ReactTrixRTEInput`. Refer: [#43](/issues/43). ([@mech][])
10 | * Fixes conflicting Trix editor id ([@rjclaasen ][])
11 |
12 | ## 1.0.14 (21-July-2021)
13 | * Fixes onAttachmentRemove trix event listener. Fixes #36. ([@radhenazarkar][])
14 | * Enable `className` prop in `ReactTrixRTEInput`. Refer: [#30](/issues/30). ([@alejo4373][])
15 |
16 | ## 1.0.13 (29-March-2021)
17 | * Fixes issue when binding Trix events on the DOM element when React has not rendered the component yet and no DOM element reference is present. ([@CUnknown][])
18 | * Fixes ReactTrixRTEToolbar component crashing when using `toolbarActions`. Refer: (#24). ([@alejo4373][])
19 | * Removes Ramda dependency. ([@alejo4373][])
20 |
21 | ## 1.0.11 (11-March-2021)
22 | * Fixes the ReactTrixRTEToolbar breaking because of missing dependency - ([@Axxxx0n][])
23 |
24 | ## 1.0.10 (11-February-2021)
25 | * Bump React version to 1.17.1. ([@abhaynikam][])
26 | * Bump Trix version to 1.13.1. ([@abhaynikam][])
27 | * Update package.json to move React to peer dependencies. ([@abhaynikam][])
28 |
29 | ## 1.0.9 (18-January-2021)
30 | * Adds `elementProps` option to add custom props to the ToolbarButton. ([@abhaynikam][])
31 |
32 | ## 1.0.8 (18-January-2021)
33 | * Adds option to override or add custom toolbar actions. ([@abhaynikam][])
34 |
35 | ## 1.0.7 (02-December-2020)
36 | * Remove 'unused' import of Trix to prevent webpack packaging conflicts with projects. ([@CUnknown][])
37 | * Support ID and Name input attribute as component props. ([@CUnknown][])
38 |
39 | ## 1.0.6 (17-September-2020)
40 |
41 | * Bump trix version `1.2.3` -> `1.2.4`. ([@abhaynikam][])
42 |
43 | ## 1.0.5 (29-July-2020)
44 |
45 | * Add ability to allow Rails direct uploading via Trix Editor. ([@abhaynikam][])
46 | * Add options to configure placeholder and autofocus for Trix Editor. ([@abhaynikam][])
47 |
48 | ## 1.0.4 (30-March-2020)
49 |
50 | * Create React Trix RTE NPM package. ([@abhaynikam][])
51 |
52 | [@abhaynikam]: https://github.com/abhaynikam
53 | [@CUnknown]: https://github.com/CUnknown
54 | [@Axxxx0n]: https://github.com/Axxxx0n
55 | [@alejo4373]: https://github.com/alejo4373
56 | [@radhenazarkar]: https://github.com/radhenazarkar
57 | [@mech]: https://github.com/mech
58 | [@rjclaasen]: https://github.com/rjclaasen
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-trix-rte",
3 | "version": "1.0.15",
4 | "description": "React wrapper for Trix rich text editor created by Basecamp",
5 | "main": "./index.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.config.babel.js",
8 | "build-examples": "webpack --config examples/webpack.config.babel.js --progress",
9 | "clean": "rm -rf dist coverage",
10 | "coverage": "jest --coverage",
11 | "lint": "eslint ./src",
12 | "prepublish": "npm run clean && npm run test && npm run build",
13 | "start": "webpack-dev-server --config examples/webpack.config.live.babel.js",
14 | "test": "npm run lint && npm run coverage",
15 | "storybook": "start-storybook -p 6006",
16 | "build-storybook": "build-storybook",
17 | "deploy-storybook": "storybook-to-ghpages"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/abhaynikam/react-trix-rte.git"
22 | },
23 | "keywords": [
24 | "React",
25 | "Trix"
26 | ],
27 | "author": "Abhay Nikam",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/abhaynikam/react-trix-rte/issues"
31 | },
32 | "homepage": "https://abhaynikam.github.io/react-trix-rte/",
33 | "dependencies": {
34 | "trix": "^1.3.1"
35 | },
36 | "peerDependencies ": {
37 | "react": "^17.0.1",
38 | "react-dom": "^17.0.1"
39 | },
40 | "devDependencies": {
41 | "@babel/core": "^7.1.2",
42 | "@babel/preset-env": "^7.1.0",
43 | "@babel/preset-react": "^7.0.0",
44 | "@babel/register": "^7.0.0",
45 | "@storybook/addon-actions": "^6.2.9",
46 | "@storybook/addon-cssresources": "^6.2.9",
47 | "@storybook/addon-links": "^6.2.9",
48 | "@storybook/addons": "^6.2.9",
49 | "@storybook/react": "^6.2.9",
50 | "@storybook/storybook-deployer": "^2.8.5",
51 | "babel-eslint": "^10.0.1",
52 | "babel-jest": "^23.6.0",
53 | "babel-loader": "^8.0.4",
54 | "chai": "^4.1.2",
55 | "clean-webpack-plugin": "^0.1.16",
56 | "css-loader": "^1.0.1",
57 | "enzyme": "^3.3.0",
58 | "enzyme-adapter-react-16": "^1.1.1",
59 | "eslint": "^5.8.0",
60 | "eslint-plugin-react": "^7.4.0",
61 | "gh-pages": "^2.2.0",
62 | "jest-cli": "^23.6.0",
63 | "node-sass": "^7.0.0",
64 | "prop-types": "^15.6.0",
65 | "react-hot-loader": "next",
66 | "react-test-renderer": "^16.0.0",
67 | "regenerator-runtime": "^0.12.1",
68 | "sass-loader": "^7.0.1",
69 | "style-loader": "^0.23.1",
70 | "webpack": "^4.42.1",
71 | "webpack-cli": "^3.3.11",
72 | "webpack-dev-server": "^3.10.3"
73 | },
74 | "resolutions": {
75 | "babel-core": "7.0.0-bridge.0",
76 | "react": "^17.0.1",
77 | "react-dom": "^17.0.1",
78 | "minimist": "^1.2.2"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/stories/1-ReactTrixWithCustomToolbar.stories.js:
--------------------------------------------------------------------------------
1 | import Trix from 'trix';
2 | import React, { Fragment } from 'react';
3 | import { action } from '@storybook/addon-actions';
4 | import { ReactTrixRTEInput, ReactTrixRTEToolbar } from '../index';
5 |
6 | export default {
7 | title: 'React Trix Editor With Custom Toolbar',
8 | component: ReactTrixRTEToolbar,
9 | };
10 |
11 |
12 | export const WithCustomToolbar = () => (
13 |
14 |
15 |
16 |
17 | );
18 |
19 | export const WithToolbarActionNotGrouped = () => (
20 |
21 |
22 |
23 |
24 | );
25 |
26 | export const WithCustomizedToolbarActionWithoutGrouping = () => (
27 |
28 |
33 |
34 |
35 | );
36 |
37 | export const WithCustomizedToolbarActionWithGrouping = () => (
38 |
39 |
43 |
44 |
45 | );
46 |
47 | export const WithCustomToolbarAction = () => {
48 | const customOption = {
49 | table: {
50 | type: "button",
51 | classNames: "trix-button trix-button--icon trix-button--icon-bold",
52 | languageKey: "table",
53 | tabIndex: "-1",
54 | trixButtonGroup: "text-tools",
55 | data: {
56 | trixAttribute: "table",
57 | trixKey: "ta",
58 | },
59 | }
60 | };
61 |
62 | return (
63 |
64 |
68 |
69 |
70 | );
71 | };
72 |
73 | export const WithRailsDirectUpload = () => (
74 |
75 |
76 |
77 |
78 | );
79 |
80 | export const WithPlaceholder = () => (
81 |
82 |
83 |
84 |
85 | );
86 |
87 | export const WithAutoFocus = () => (
88 |
89 |
90 |
91 |
92 | );
93 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/ReactTrixRTEToolbar.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from 'prop-types';
3 |
4 | import ToolbarButton from "./ToolbarComponent/ToolbarButton";
5 | import ToolbarSpacer from "./ToolbarComponent/ToolbarSpacer";
6 | import ToolbarLinkDialog from "./ToolbarComponent/ToolbarLinkDialog";
7 | import ToolbarButtonGroup from "./ToolbarComponent/ToolbarButtonGroup";
8 | import { groupBy } from "../Shared/utils";
9 | import { TOOLBAR_ACTION_OPTS, SPACER_BEFORE_TOOL_GROUP } from "./constants";
10 |
11 | function ReactTrixRTEToolbar(props) {
12 | const { disableGroupingAction = false, toolbarId, toolbarActions, customToolbarActions } = props;
13 | const isToolbarActionPresent = toolbarActions && toolbarActions.length !== 0;
14 | let allowedToolbarActions = Object.assign(TOOLBAR_ACTION_OPTS, customToolbarActions);
15 | if(isToolbarActionPresent) {
16 | allowedToolbarActions = toolbarActions.reduce((actions, toolbarAction) => {
17 | const actionOptions = TOOLBAR_ACTION_OPTS[toolbarAction]
18 | if (actionOptions) actions[toolbarAction] = actionOptions
19 | return actions
20 | }, {});
21 | }
22 |
23 | function renderGroupedToolbarActions() {
24 | const groupedToolbarActionOptions = groupBy(allowedToolbarActions, "trixButtonGroup");
25 | let groupedToolbarActionHTML = [];
26 |
27 | for (let groupName in groupedToolbarActionOptions) {
28 | const toolbarActionOptions = groupedToolbarActionOptions[groupName]
29 | if (groupName === SPACER_BEFORE_TOOL_GROUP) {
30 | const dateTimestamp = new Date().getTime();
31 | groupedToolbarActionHTML.push();
32 | }
33 |
34 | groupedToolbarActionHTML.push(
35 |
40 | );
41 | }
42 |
43 | return groupedToolbarActionHTML;
44 | }
45 |
46 | function renderUnGroupedToolbarActions() {
47 | const ungroupedToolbarActionsHTML = []
48 | for (let toolbarActionKey in allowedToolbarActions) {
49 | ungroupedToolbarActionsHTML.push(
50 |
51 | )
52 | }
53 | return ungroupedToolbarActionsHTML
54 | }
55 |
56 | function renderToolbarActions() {
57 | if(disableGroupingAction) {
58 | return(
59 |
60 | {renderUnGroupedToolbarActions()}
61 |
62 | );
63 | } else {
64 | return renderGroupedToolbarActions();
65 | }
66 | }
67 |
68 | function renderToolbarLinkDialog() {
69 | if (isToolbarActionPresent && toolbarActions.includes("link")) {
70 | return ;
71 | } else if(!isToolbarActionPresent) {
72 | return ;
73 | }
74 | }
75 |
76 | return (
77 |
78 |
79 | {renderToolbarActions()}
80 |
81 | {renderToolbarLinkDialog()}
82 |
83 | );
84 | }
85 |
86 | ReactTrixRTEToolbar.propTypes = {
87 | disableGroupingAction: PropTypes.bool,
88 | toolbarId: PropTypes.string,
89 | toolbarActions: PropTypes.array,
90 | customToolbarActions: PropTypes.object,
91 | }
92 |
93 | export default ReactTrixRTEToolbar;
94 |
--------------------------------------------------------------------------------
/stories/0-ReactTrixInputRTE.stories.js:
--------------------------------------------------------------------------------
1 | import Trix from 'trix';
2 | import React, { useRef } from 'react';
3 | import { action } from '@storybook/addon-actions';
4 | import { withCssResources } from '@storybook/addon-cssresources';
5 | import { ReactTrixRTEInput } from '../index';
6 |
7 | export default {
8 | title: 'React Trix Editor',
9 | component: ReactTrixRTEInput,
10 | };
11 |
12 | function ReactTrixInputRefComponent() {
13 | const reactTrixInputRef = useRef()
14 | const handleInitialize = (event) => {
15 | reactTrixInputRef.current.editor.insertHTML("Added using trixInputRef programmatically!");
16 | }
17 |
18 | return (
19 |
23 | )
24 | }
25 |
26 | export const DefaultReactTrixEditor = () => (
27 |
28 | );
29 |
30 | export const WithDefaultValue = () => (
31 |
34 | );
35 |
36 | export const WithTrixInputRef = () => (
37 |
38 | )
39 |
40 | export const WithOnChangeHandler = () => (
41 |
42 | );
43 |
44 | export const WithOnBlur = () => (
45 |
46 | );
47 |
48 | export const WithOnFocus = () => (
49 |
50 | );
51 |
52 | export const WithBeforeInitialize = () => (
53 |
54 | );
55 |
56 | export const WithInitialize = () => (
57 |
58 | );
59 |
60 | export const WithSelectionChange = () => (
61 |
62 | );
63 |
64 | export const WithFileAccepted = () => (
65 |
66 | );
67 |
68 | export const WithAttachmentAdd = () => (
69 |
70 | );
71 |
72 | export const WithAttachmentRemove = () => (
73 |
74 | );
75 |
76 | export const WithIsRailsDirectUpload = () => (
77 |
78 | );
79 |
80 | export const WithPlaceholder = () => (
81 |
82 | );
83 |
84 | export const WithAutoFocus = () => (
85 |
86 | );
87 |
88 | export const WithCustomClassName = () => (
89 |
90 | );
91 |
92 | WithCustomClassName.parameters = {
93 | cssresources: [{
94 | id: "Blue Border",
95 | class: "tx-rte-with-border",
96 | code: ``,
97 | picked: false
98 | }]
99 | }
100 |
101 | WithCustomClassName.decorators = [withCssResources]
102 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEInput/ReactTrixRTEInput.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { useState, useRef, useEffect } from "react";
3 | import { getUniqInputId } from '../Shared/utils';
4 |
5 | import { RAILS_DIRECT_UPLOADS_URL, RAILS_SERVICE_BLOB_URL } from "./constants";
6 | import './ReactTrixRTEInput.style';
7 |
8 | function ReactTrixRTEInput(props) {
9 | const {
10 | defaultValue,
11 | toolbarId,
12 | onBlur,
13 | onFocus,
14 | onChange,
15 | onInitialize,
16 | onFileAccepted,
17 | onAttachmentAdd,
18 | onAttachmentRemove,
19 | onSelectionChange,
20 | onBeforeInitialize,
21 | trixInputRef,
22 | isRailsDirectUpload,
23 | placeholder,
24 | autofocus,
25 | className,
26 | railsDirectUploadUrl,
27 | railsBlobUrl
28 | } = props;
29 | const trixRTEInputRef = trixInputRef ? trixInputRef : useRef();
30 | const [value, setValue] = useState(defaultValue);
31 | const trixRTEInputId = props.id || getUniqInputId();
32 | const trixRTEInputName = props.name || "content";
33 | const directUploadOptions = isRailsDirectUpload ? {
34 | "data-direct-upload-url": railsDirectUploadUrl || RAILS_DIRECT_UPLOADS_URL,
35 | "data-blob-url-template": railsBlobUrl || RAILS_SERVICE_BLOB_URL
36 | } : {};
37 | let trixEditorOptions = {}
38 | if (autofocus) trixEditorOptions["autofocus"] = true;
39 | if (className) trixEditorOptions["class"] = className;
40 |
41 | useEffect(() => {
42 | if (trixRTEInputRef && trixRTEInputRef.current) {
43 | trixRTEInputRef.current.addEventListener("trix-change", handleChange);
44 | if (onFocus) trixRTEInputRef.current.addEventListener("trix-focus", onFocus);
45 | if (onBlur) trixRTEInputRef.current.addEventListener("trix-blur", onBlur);
46 | if (onInitialize) trixRTEInputRef.current.addEventListener("trix-initialize", onInitialize);
47 | if (onFileAccepted) trixRTEInputRef.current.addEventListener("trix-file-accept", onFileAccepted);
48 | if (onAttachmentAdd) trixRTEInputRef.current.addEventListener("trix-attachment-add", onAttachmentAdd);
49 | if (onAttachmentRemove) trixRTEInputRef.current.addEventListener("trix-attachment-remove", onAttachmentRemove);
50 | if (onSelectionChange) trixRTEInputRef.current.addEventListener("trix-selection-change", onSelectionChange);
51 | if (onBeforeInitialize) trixRTEInputRef.current.addEventListener("trix-before-initialize", onBeforeInitialize);
52 | }
53 |
54 | return () => {
55 | if (trixRTEInputRef && trixRTEInputRef.current) {
56 | trixRTEInputRef.current.removeEventListener("trix-change", handleChange);
57 | if (onFocus) trixRTEInputRef.current.removeEventListener("trix-focus", onFocus);
58 | if (onBlur) trixRTEInputRef.current.removeEventListener("trix-blur", onBlur);
59 | if (onInitialize) trixRTEInputRef.current.removeEventListener("trix-initialize", onInitialize);
60 | if (onFileAccepted) trixRTEInputRef.current.removeEventListener("trix-file-accept", onFileAccepted);
61 | if (onAttachmentAdd) trixRTEInputRef.current.removeEventListener("trix-attachment-add", onAttachmentAdd);
62 | if (onSelectionChange) trixRTEInputRef.current.removeEventListener("trix-selection-change", onSelectionChange);
63 | if (onAttachmentRemove) trixRTEInputRef.current.removeEventListener("trix-attachment-remove", onAttachmentRemove);
64 | if (onBeforeInitialize) trixRTEInputRef.current.removeEventListener("trix-before-initialize", onBeforeInitialize);
65 | }
66 | };
67 | }, [])
68 |
69 | function handleChange(event) {
70 | const newValue = event.target.value;
71 | setValue(newValue);
72 | if (onChange) {
73 | onChange(event, newValue);
74 | }
75 | }
76 |
77 | return (
78 | <>
79 |
85 |
93 | >
94 | );
95 | }
96 |
97 | ReactTrixRTEInput.defaultProps = {
98 | isRailsDirectUpload: false,
99 | defaultValue: '',
100 | };
101 |
102 | ReactTrixRTEInput.propTypes = {
103 | id: PropTypes.string,
104 | toolbarId: PropTypes.string,
105 | defaultValue: PropTypes.string,
106 | name: PropTypes.string,
107 | onChange: PropTypes.func,
108 | onBlur: PropTypes.func,
109 | onFocus: PropTypes.func,
110 | onFileAccepted: PropTypes.func,
111 | onAttachmentAdd: PropTypes.func,
112 | onAttachmentRemove: PropTypes.func,
113 | onSelectionChange: PropTypes.func,
114 | onInitialize: PropTypes.func,
115 | onBeforeInitialize: PropTypes.func,
116 | trixInputRef: PropTypes.func,
117 | isRailsDirectUpload: PropTypes.bool,
118 | placeholder: PropTypes.string,
119 | autofocus: PropTypes.bool,
120 | className: PropTypes.string,
121 | railsDirectUploadUrl: PropTypes.string,
122 | railsBlobUrl: PropTypes.string
123 | };
124 |
125 | export default ReactTrixRTEInput;
126 |
--------------------------------------------------------------------------------
/src/components/ReactTrixRTEToolbar/constants.js:
--------------------------------------------------------------------------------
1 | export const TOOLBAR_ACTION_OPTS = {
2 | bold: {
3 | type: "button",
4 | classNames: "trix-button trix-button--icon trix-button--icon-bold",
5 | languageKey: "bold",
6 | tabIndex: "-1",
7 | trixButtonGroup: "text-tools",
8 | data: {
9 | trixAttribute: "bold",
10 | trixKey: "b",
11 | },
12 | },
13 | italic: {
14 | type: "button",
15 | classNames: "trix-button trix-button--icon trix-button--icon-italic",
16 | languageKey: "italic",
17 | tabIndex: "-1",
18 | trixButtonGroup: "text-tools",
19 | data: {
20 | trixAttribute: "italic",
21 | trixKey: "i",
22 | },
23 | },
24 | strike: {
25 | type: "button",
26 | classNames: "trix-button trix-button--icon trix-button--icon-strike",
27 | languageKey: "strike",
28 | tabIndex: "-1",
29 | trixButtonGroup: "text-tools",
30 | data: {
31 | trixAttribute: "strike"
32 | },
33 | },
34 | link: {
35 | type: "button",
36 | classNames: "trix-button trix-button--icon trix-button--icon-link",
37 | languageKey: "link",
38 | tabIndex: "-1",
39 | trixButtonGroup: "text-tools",
40 | data: {
41 | trixAttribute: "href",
42 | trixKey: "k",
43 | trixAction: "link"
44 | },
45 | },
46 | heading1: {
47 | type: "button",
48 | classNames: "trix-button trix-button--icon trix-button--icon-heading-1",
49 | languageKey: "heading1",
50 | tabIndex: "-1",
51 | trixButtonGroup: "block-tools",
52 | data: {
53 | trixAttribute: "heading1"
54 | },
55 | },
56 | quote: {
57 | type: "button",
58 | classNames: "trix-button trix-button--icon trix-button--icon-quote",
59 | languageKey: "quote",
60 | tabIndex: "-1",
61 | trixButtonGroup: "block-tools",
62 | data: {
63 | trixAttribute: "quote"
64 | },
65 | },
66 | code: {
67 | type: "button",
68 | classNames: "trix-button trix-button--icon trix-button--icon-code",
69 | languageKey: "code",
70 | tabIndex: "-1",
71 | trixButtonGroup: "block-tools",
72 | data: {
73 | trixAttribute: "code"
74 | },
75 | },
76 | bullet: {
77 | type: "button",
78 | classNames: "trix-button trix-button--icon trix-button--icon-bullet-list",
79 | languageKey: "bullets",
80 | tabIndex: "-1",
81 | trixButtonGroup: "block-tools",
82 | data: {
83 | trixAttribute: "bullet"
84 | },
85 | },
86 | number: {
87 | type: "button",
88 | classNames: "trix-button trix-button--icon trix-button--icon-number-list",
89 | languageKey: "numbers",
90 | tabIndex: "-1",
91 | trixButtonGroup: "block-tools",
92 | data: {
93 | trixAttribute: "number"
94 | },
95 | },
96 | outdent: {
97 | type: "button",
98 | classNames: "trix-button trix-button--icon trix-button--icon-decrease-nesting-level",
99 | languageKey: "outdent",
100 | tabIndex: "-1",
101 | trixButtonGroup: "block-tools",
102 | data: {
103 | trixAction: "decreaseNestingLevel"
104 | },
105 | },
106 | indent: {
107 | type: "button",
108 | classNames: "trix-button trix-button--icon trix-button--icon-increase-nesting-level",
109 | languageKey: "indent",
110 | tabIndex: "-1",
111 | trixButtonGroup: "block-tools",
112 | data: {
113 | trixAction: "increaseNestingLevel"
114 | },
115 | },
116 | attachFiles: {
117 | type: "button",
118 | classNames: "trix-button trix-button--icon trix-button--icon-attach",
119 | languageKey: "attachFiles",
120 | tabIndex: "-1",
121 | trixButtonGroup: "file-tools",
122 | data: {
123 | trixAction: "attachFiles"
124 | },
125 | },
126 | undo: {
127 | type: "button",
128 | classNames: "trix-button trix-button--icon trix-button--icon-undo",
129 | languageKey: "undo",
130 | tabIndex: "-1",
131 | trixButtonGroup: "history-tools",
132 | data: {
133 | trixAction: "undo",
134 | trixKey: "z",
135 | },
136 | },
137 | redo: {
138 | type: "button",
139 | classNames: "trix-button trix-button--icon trix-button--icon-redo",
140 | languageKey: "redo",
141 | tabIndex: "-1",
142 | trixButtonGroup: "history-tools",
143 | data: {
144 | trixAction: "redo",
145 | trixKey: "shift+z",
146 | },
147 | },
148 | };
149 |
150 | export const TOOLBAR_LANGUAGE_OPTS = {
151 | attachFiles: "Attach Files",
152 | bold: "Bold",
153 | bullets: "Bullets",
154 | byte: "Byte",
155 | bytes: "Bytes",
156 | captionPlaceholder: "Add a caption…",
157 | code: "Code",
158 | heading1: "Heading",
159 | indent: "Increase Level",
160 | italic: "Italic",
161 | link: "Link",
162 | numbers: "Numbers",
163 | outdent: "Decrease Level",
164 | quote: "Quote",
165 | redo: "Redo",
166 | remove: "Remove",
167 | strike: "Strikethrough",
168 | undo: "Undo",
169 | unlink: "Unlink",
170 | url: "URL",
171 | urlPlaceholder: "Enter a URL…",
172 | GB: "GB",
173 | KB: "KB",
174 | MB: "MB",
175 | PB: "PB",
176 | TB: "TB",
177 | };
178 |
179 | export const TOOLBAR_ACTION_GROUP_OPTS = {
180 | "text-tools": "trix-button-group trix-button-group--text-tools",
181 | "block-tools": "trix-button-group trix-button-group--block-tools",
182 | "file-tools": "trix-button-group trix-button-group--file-tools",
183 | "history-tools": "trix-button-group trix-button-group--history-tools"
184 | };
185 |
186 | export const SPACER_BEFORE_TOOL_GROUP = "history-tools";
187 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # React Trix RTE
2 |
3 | 
4 | 
5 | 
6 |
7 | React Trix rich text editor is react wrapper built for the Trix editor created by [Basecamp](https://trix-editor.org/). We have built this library because we were rewriting the same component in multiple project.
8 |
9 | This wrapper uses React hooks and adds readable event listeners on the Trix editor. The library also adds two toolbar components which has the ability to customize the toolbar actions as per our need.
10 |
11 | ### Demo
12 |
13 | Please see the some live example on [ReactTrixRTE-Storybook](https://abhaynikam.github.io/react-trix-rte)
14 |
15 | 
16 |
17 |
18 | ### Installation
19 |
20 | To install the React Trix RTE, run the following command in the console.
21 |
22 | ```
23 | npm install react-trix-rte
24 | OR
25 | yarn add react-trix-rte
26 | ```
27 |
28 | ### Usage
29 |
30 | ```javascript
31 | import Trix from "trix";
32 | import React, { useState } from "react";
33 | import { ReactTrixRTEInput } from "react-trix-rte";
34 |
35 | export default function TrixEditor(props) {
36 | const [value, setValue] = useState("");
37 |
38 | function handleChange(event, newValue) {
39 | setValue(newValue); // OR custom on change listener.
40 | }
41 |
42 | return (
43 |
47 | )
48 | }
49 | ```
50 |
51 | ### Upgrading to 1.0.7 or higher
52 | React Trix RTE version `1.0.7` removes the dependency import for Trix because using Trix outside directly causes problems. Read issue: [17](https://github.com/abhaynikam/react-trix-rte/issues/17) and [19](https://github.com/abhaynikam/react-trix-rte/pull/19).
53 |
54 | Import `import Trix from "trix";` to the component using React Trix RTE.
55 |
56 | ### ReactTrixInput API
57 |
58 | `ReactTrixInput` is the Trix editor input which by default comes with the toolbar. The `ReactTrixInput` comes with following properties that could be accepted.
59 |
60 | | Name | Type | Description |
61 | | ------------------- | ---- | ----------- |
62 | | id | string | The HTML id attribute for the input field
63 | | name | string | The HTML name attribute for the input field
64 | | toolbarId | string | If a custom toolbar is used for the Trix Input, pass the `toolbarId` of the custom toolbar to the input. |
65 | | isRailsDirectUpload | boolean | React Trix editor support direct uploading of the files to the service if you are using Rails as a backend server. This defaults to `false` |
66 | | railsDirectUploadUrl| string | Custom URL for Rails direct upload (`data-direct-upload-url`) |
67 | | railsBlobUrl | string | Custom URL for Rails blob template (`data-blob-url-template`) |
68 | | placeholder | string | Adds a placeholder to the React Trix Input |
69 | | defaultValue | string | The default value of the React Trix Input |
70 | | autofocus | boolean | Autofocus in the trix input. This is defaults to `false` |
71 | | className | string | Apply a custom css class to the editor |
72 | | trixInputRef | function | Adds a custom ref to the React Trix Input to programmatically edit text. Read the documentation for manual things you can perform on Trix editor [here](https://github.com/basecamp/trix#editing-text-programmatically) |
73 | | onBeforeInitialize | function | Fires when the `` element is attached to the DOM just before Trix installs its editor object. |
74 | | onInitialize | function | Fires when the `` element is attached to the DOM and its editor object is ready for use. |
75 | | onChange | function | Fires whenever the editor’s contents have changed. |
76 | | onSelectionChange | function | Fires any time the selected range changes in the editor. |
77 | | onBlur | function | Fire when the editor loses focus. |
78 | | onFocus | function | Fire when the editor gains focus. |
79 | | onFileAccepted | function | Fires when a file is dropped or inserted into the editor. You can access the DOM File object through the file property on the event. Call preventDefault on the event to prevent attaching the file to the document. |
80 | | onAttachmentAdd | function | Fires after an attachment is added to the document. You can access the Trix attachment object through the attachment property on the event. If the attachment object has a file property, you should store this file remotely and set the attachment’s URL attribute. See the attachment example for detailed information. |
81 | | onAttachmentRemove | function | Fires when an attachment is removed from the document. You can access the Trix attachment object through the attachment property on the event. You may wish to use this event to clean up remotely stored files. |
82 |
83 | ### ReactTrixRTEToolbar API
84 |
85 | `ReactTrixRTEToolbar` is the custom Trix editor toolbar component. This component helps in customizing the toolbar options, classes and attributes in better way.
86 |
87 | | Name | Type | Description |
88 | | ------------------- | ---- | ----------- |
89 | | toolbarId | string | The ReactTrixInput initialize the default toolbar if the `toolbarId` is missing or not matching. Make sure the `toolbarId` matches. |
90 | | disableGroupingAction | boolean | Defaults to `false`. If the `disableGroupingAction` is enabled the toolbar actions are not grouped for a type. Example: text tools won't be grouped |
91 | | toolbarActions | array | Allows customizing the list of toolbar actions. The available actions are `["bold", "italic", "strike", "link", "heading1", "quote", "code", "bullet", "number", "outdent", "indent", "attachFiles", "undo", "redo"]`. |
92 | | customToolbarActions | object | Override the toolbar actions properties or add custom toolbar actions. You can refer to default [toolbar actions options](https://github.com/abhaynikam/react-trix-rte/blob/master/src/components/ReactTrixRTEToolbar/constants.js) |
93 |
94 | ### Custom Toolbar Usage
95 |
96 | ```javascript
97 | import React, { useState } from "react";
98 | import Trix from "trix";
99 | import { ReactTrixRTEInput, ReactTrixRTEToolbar } from "react-trix-rte";
100 |
101 | export default function TrixEditor(props) {
102 | const [value, setValue] = useState("");
103 |
104 | function handleChange(event, newValue) {
105 | setValue(newValue); // OR custom on change listener.
106 | }
107 |
108 | return (
109 |
110 |
111 |
116 |
117 | )
118 | }
119 | ```
120 |
121 | ### Contributing
122 |
123 | Read more about [contributing](https://github.com/abhaynikam/react-trix-rte/blob/7704eb681801fe2eacd00a5da50cd0bfb8c0238d/CONTRIBUTING.md) to the ReactTrixRTE.
124 |
125 | ### Author
126 | [Abhay Nikam](https://www.abhaynikam.me/pages/about)
127 |
128 | ### Contributor
129 | [CUnknown](https://github.com/CUnknown)
130 |
131 | ### License
132 | This project is licensed under the [MIT License](https://github.com/abhaynikam/react-trix-rte/blob/7704eb681801fe2eacd00a5da50cd0bfb8c0238d/LICENSE)
133 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("react")):"function"==typeof define&&define.amd?define(["react"],e):"object"==typeof exports?exports["react-trix-rte"]=e(require("react")):t["react-trix-rte"]=e(t.react)}(this,(function(t){return function(t){var e={};function r(o){if(e[o])return e[o].exports;var i=e[o]={i:o,l:!1,exports:{}};return t[o].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=t,r.c=e,r.d=function(t,e,o){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:o})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)r.d(o,i,function(e){return t[e]}.bind(null,i));return o},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=9)}([function(t,e,r){t.exports=r(2)()},function(e,r){e.exports=t},function(t,e,r){"use strict";var o=r(3);function i(){}function n(){}n.resetWarningCache=i,t.exports=function(){function t(t,e,r,i,n,a){if(a!==o){var l=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw l.name="Invariant Violation",l}}function e(){return t}t.isRequired=t;var r={array:t,bool:t,func:t,number:t,object:t,string:t,symbol:t,any:t,arrayOf:e,element:t,elementType:t,instanceOf:e,node:t,objectOf:e,oneOf:e,oneOfType:e,shape:e,exact:e,checkPropTypes:n,resetWarningCache:i};return r.PropTypes=r,r}},function(t,e,r){"use strict";t.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},function(t,e,r){var o=r(5);"string"==typeof o&&(o=[[t.i,o,""]]);var i={hmr:!0,transform:void 0,insertInto:void 0};r(7)(o,i);o.locals&&(t.exports=o.locals)},function(t,e,r){(t.exports=r(6)(!1)).push([t.i,'trix-editor{border:1px solid #bbb;border-radius:3px;margin:0;padding:0.4em 0.6em;min-height:5em;outline:none}trix-toolbar *{box-sizing:border-box}trix-toolbar .trix-button-row{display:flex;flex-wrap:nowrap;justify-content:space-between;overflow-x:auto}trix-toolbar .trix-button-group{display:flex;margin-bottom:10px;border:1px solid #bbb;border-top-color:#ccc;border-bottom-color:#888;border-radius:3px}trix-toolbar .trix-button-group:not(:first-child){margin-left:1.5vw}@media (max-device-width: 768px){trix-toolbar .trix-button-group:not(:first-child){margin-left:0}}trix-toolbar .trix-button-group-spacer{flex-grow:1}@media (max-device-width: 768px){trix-toolbar .trix-button-group-spacer{display:none}}trix-toolbar .trix-button{position:relative;float:left;color:rgba(0,0,0,0.6);font-size:0.75em;font-weight:600;white-space:nowrap;padding:0 0.5em;margin:0;outline:none;border:none;border-bottom:1px solid #ddd;border-radius:0;background:transparent}trix-toolbar .trix-button:not(:first-child){border-left:1px solid #ccc}trix-toolbar .trix-button.trix-active{background:#cbeefa;color:black}trix-toolbar .trix-button:not(:disabled){cursor:pointer}trix-toolbar .trix-button:disabled{color:rgba(0,0,0,0.125)}@media (max-device-width: 768px){trix-toolbar .trix-button{letter-spacing:-0.01em;padding:0 0.3em}}trix-toolbar .trix-button--icon{font-size:inherit;width:2.6em;height:1.6em;max-width:calc(0.8em + 4vw);text-indent:-9999px}@media (max-device-width: 768px){trix-toolbar .trix-button--icon{height:2em;max-width:calc(0.8em + 3.5vw)}}trix-toolbar .trix-button--icon::before{display:inline-block;position:absolute;top:0;right:0;bottom:0;left:0;opacity:0.6;content:"";background-position:center;background-repeat:no-repeat;background-size:contain}@media (max-device-width: 768px){trix-toolbar .trix-button--icon::before{right:6%;left:6%}}trix-toolbar .trix-button--icon.trix-active::before{opacity:1}trix-toolbar .trix-button--icon:disabled::before{opacity:0.125}trix-toolbar .trix-button--icon-attach::before{background-image:url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M16.5%206v11.5a4%204%200%201%201-8%200V5a2.5%202.5%200%200%201%205%200v10.5a1%201%200%201%201-2%200V6H10v9.5a2.5%202.5%200%200%200%205%200V5a4%204%200%201%200-8%200v12.5a5.5%205.5%200%200%200%2011%200V6h-1.5z%22%2F%3E%3C%2Fsvg%3E);top:8%;bottom:4%}trix-toolbar .trix-button--icon-bold::before{background-image:url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M15.6%2011.8c1-.7%201.6-1.8%201.6-2.8a4%204%200%200%200-4-4H7v14h7c2.1%200%203.7-1.7%203.7-3.8%200-1.5-.8-2.8-2.1-3.4zM10%207.5h3a1.5%201.5%200%201%201%200%203h-3v-3zm3.5%209H10v-3h3.5a1.5%201.5%200%201%201%200%203z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-button--icon-italic::before{background-image:url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M10%205v3h2.2l-3.4%208H6v3h8v-3h-2.2l3.4-8H18V5h-8z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-button--icon-link::before{background-image:url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M9.88%2013.7a4.3%204.3%200%200%201%200-6.07l3.37-3.37a4.26%204.26%200%200%201%206.07%200%204.3%204.3%200%200%201%200%206.06l-1.96%201.72a.91.91%200%201%201-1.3-1.3l1.97-1.71a2.46%202.46%200%200%200-3.48-3.48l-3.38%203.37a2.46%202.46%200%200%200%200%203.48.91.91%200%201%201-1.3%201.3z%22%2F%3E%3Cpath%20d%3D%22M4.25%2019.46a4.3%204.3%200%200%201%200-6.07l1.93-1.9a.91.91%200%201%201%201.3%201.3l-1.93%201.9a2.46%202.46%200%200%200%203.48%203.48l3.37-3.38c.96-.96.96-2.52%200-3.48a.91.91%200%201%201%201.3-1.3%204.3%204.3%200%200%201%200%206.07l-3.38%203.38a4.26%204.26%200%200%201-6.07%200z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-button--icon-strike::before{background-image:url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M12.73%2014l.28.14c.26.15.45.3.57.44.12.14.18.3.18.5%200%20.3-.15.56-.44.75-.3.2-.76.3-1.39.3A13.52%2013.52%200%200%201%207%2014.95v3.37a10.64%2010.64%200%200%200%204.84.88c1.26%200%202.35-.19%203.28-.56.93-.37%201.64-.9%202.14-1.57s.74-1.45.74-2.32c0-.26-.02-.51-.06-.75h-5.21zm-5.5-4c-.08-.34-.12-.7-.12-1.1%200-1.29.52-2.3%201.58-3.02%201.05-.72%202.5-1.08%204.34-1.08%201.62%200%203.28.34%204.97%201l-1.3%202.93c-1.47-.6-2.73-.9-3.8-.9-.55%200-.96.08-1.2.26-.26.17-.38.38-.38.64%200%20.27.16.52.48.74.17.12.53.3%201.05.53H7.23zM3%2013h18v-2H3v2z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-button--icon-quote::before{background-image:url(data:image/svg+xml,%3Csvg%20version%3D%221%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M6%2017h3l2-4V7H5v6h3zm8%200h3l2-4V7h-6v6h3z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-button--icon-heading-1::before{background-image:url(data:image/svg+xml,%3Csvg%20version%3D%221%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M12%209v3H9v7H6v-7H3V9h9zM8%204h14v3h-6v12h-3V7H8V4z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-button--icon-code::before{background-image:url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M18.2%2012L15%2015.2l1.4%201.4L21%2012l-4.6-4.6L15%208.8l3.2%203.2zM5.8%2012L9%208.8%207.6%207.4%203%2012l4.6%204.6L9%2015.2%205.8%2012z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-button--icon-bullet-list::before{background-image:url(data:image/svg+xml,%3Csvg%20version%3D%221%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%204a2%202%200%201%200%200%204%202%202%200%200%200%200-4zm0%206a2%202%200%201%200%200%204%202%202%200%200%200%200-4zm0%206a2%202%200%201%200%200%204%202%202%200%200%200%200-4zm4%203h14v-2H8v2zm0-6h14v-2H8v2zm0-8v2h14V5H8z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-button--icon-number-list::before{background-image:url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M2%2017h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1%203h1.8L2%2013.1v.9h3v-1H3.2L5%2010.9V10H2v1zm5-6v2h14V5H7zm0%2014h14v-2H7v2zm0-6h14v-2H7v2z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-button--icon-undo::before{background-image:url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M12.5%208c-2.6%200-5%201-6.9%202.6L2%207v9h9l-3.6-3.6A8%208%200%200%201%2020%2016l2.4-.8a10.5%2010.5%200%200%200-10-7.2z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-button--icon-redo::before{background-image:url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M18.4%2010.6a10.5%2010.5%200%200%200-16.9%204.6L4%2016a8%208%200%200%201%2012.7-3.6L13%2016h9V7l-3.6%203.6z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-button--icon-decrease-nesting-level::before{background-image:url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M3%2019h19v-2H3v2zm7-6h12v-2H10v2zm-8.3-.3l2.8%202.9L6%2014.2%204%2012l2-2-1.4-1.5L1%2012l.7.7zM3%205v2h19V5H3z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-button--icon-increase-nesting-level::before{background-image:url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M3%2019h19v-2H3v2zm7-6h12v-2H10v2zm-6.9-1L1%2014.2l1.4%201.4L6%2012l-.7-.7-2.8-2.8L1%209.9%203.1%2012zM3%205v2h19V5H3z%22%2F%3E%3C%2Fsvg%3E)}trix-toolbar .trix-dialogs{position:relative}trix-toolbar .trix-dialog{position:absolute;top:0;left:0;right:0;font-size:0.75em;padding:15px 10px;background:#fff;box-shadow:0 0.3em 1em #ccc;border-top:2px solid #888;border-radius:5px;z-index:5}trix-toolbar .trix-input--dialog{font-size:inherit;font-weight:normal;padding:0.5em 0.8em;margin:0 10px 0 0;border-radius:3px;border:1px solid #bbb;background-color:#fff;box-shadow:none;outline:none;-webkit-appearance:none;-moz-appearance:none}trix-toolbar .trix-input--dialog.validate:invalid{box-shadow:#F00 0px 0px 1.5px 1px}trix-toolbar .trix-button--dialog{font-size:inherit;padding:0.5em;border-bottom:none}trix-toolbar .trix-dialog--link{max-width:600px}trix-toolbar .trix-dialog__link-fields{display:flex;align-items:baseline}trix-toolbar .trix-dialog__link-fields .trix-input{flex:1}trix-toolbar .trix-dialog__link-fields .trix-button-group{flex:0 0 content;margin:0}trix-editor [data-trix-mutable]:not(.attachment__caption-editor){-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}trix-editor [data-trix-mutable]::-moz-selection,trix-editor [data-trix-cursor-target]::-moz-selection,trix-editor [data-trix-mutable] ::-moz-selection{background:none}trix-editor [data-trix-mutable]::selection,trix-editor [data-trix-cursor-target]::selection,trix-editor [data-trix-mutable] ::selection{background:none}trix-editor [data-trix-mutable].attachment__caption-editor:focus::-moz-selection{background:highlight}trix-editor [data-trix-mutable].attachment__caption-editor:focus::selection{background:highlight}trix-editor [data-trix-mutable].attachment.attachment--file{box-shadow:0 0 0 2px highlight;border-color:transparent}trix-editor [data-trix-mutable].attachment img{box-shadow:0 0 0 2px highlight}trix-editor .attachment{position:relative}trix-editor .attachment:hover{cursor:default}trix-editor .attachment--preview .attachment__caption:hover{cursor:text}trix-editor .attachment__progress{position:absolute;z-index:1;height:20px;top:calc(50% - 10px);left:5%;width:90%;opacity:0.9;transition:opacity 200ms ease-in}trix-editor .attachment__progress[value="100"]{opacity:0}trix-editor .attachment__caption-editor{display:inline-block;width:100%;margin:0;padding:0;font-size:inherit;font-family:inherit;line-height:inherit;color:inherit;text-align:center;vertical-align:top;border:none;outline:none;-webkit-appearance:none;-moz-appearance:none}trix-editor .attachment__toolbar{position:absolute;z-index:1;top:-0.9em;left:0;width:100%;text-align:center}trix-editor .trix-button-group{display:inline-flex}trix-editor .trix-button{position:relative;float:left;color:#666;white-space:nowrap;font-size:80%;padding:0 0.8em;margin:0;outline:none;border:none;border-radius:0;background:transparent}trix-editor .trix-button:not(:first-child){border-left:1px solid #ccc}trix-editor .trix-button.trix-active{background:#cbeefa}trix-editor .trix-button:not(:disabled){cursor:pointer}trix-editor .trix-button--remove{text-indent:-9999px;display:inline-block;padding:0;outline:none;width:1.8em;height:1.8em;line-height:1.8em;border-radius:50%;background-color:#fff;border:2px solid highlight;box-shadow:1px 1px 6px rgba(0,0,0,0.25)}trix-editor .trix-button--remove::before{display:inline-block;position:absolute;top:0;right:0;bottom:0;left:0;opacity:0.7;content:"";background-image:url(data:image/svg+xml,%3Csvg%20height%3D%2224%22%20width%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M19%206.4L17.6%205%2012%2010.6%206.4%205%205%206.4l5.6%205.6L5%2017.6%206.4%2019l5.6-5.6%205.6%205.6%201.4-1.4-5.6-5.6z%22%2F%3E%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E);background-position:center;background-repeat:no-repeat;background-size:90%}trix-editor .trix-button--remove:hover{border-color:#333}trix-editor .trix-button--remove:hover::before{opacity:1}trix-editor .attachment__metadata-container{position:relative}trix-editor .attachment__metadata{position:absolute;left:50%;top:2em;transform:translate(-50%, 0);max-width:90%;padding:0.1em 0.6em;font-size:0.8em;color:#fff;background-color:rgba(0,0,0,0.7);border-radius:3px}trix-editor .attachment__metadata .attachment__name{display:inline-block;max-width:100%;vertical-align:bottom;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}trix-editor .attachment__metadata .attachment__size{margin-left:0.2em;white-space:nowrap}.trix-content{line-height:1.5}.trix-content *{box-sizing:border-box;margin:0;padding:0}.trix-content h1{font-size:1.2em;line-height:1.2}.trix-content blockquote{border:0 solid #ccc;border-left-width:0.3em;margin-left:0.3em;padding-left:0.6em}.trix-content [dir=rtl] blockquote,.trix-content blockquote[dir=rtl]{border-width:0;border-right-width:0.3em;margin-right:0.3em;padding-right:0.6em}.trix-content li{margin-left:1em}.trix-content [dir=rtl] li{margin-right:1em}.trix-content pre{display:inline-block;width:100%;vertical-align:top;font-family:monospace;font-size:0.9em;padding:0.5em;white-space:pre;background-color:#eee;overflow-x:auto}.trix-content img{max-width:100%;height:auto}.trix-content .attachment{display:inline-block;position:relative;max-width:100%}.trix-content .attachment a{color:inherit;text-decoration:none}.trix-content .attachment a:hover,.trix-content .attachment a:visited:hover{color:inherit}.trix-content .attachment__caption{text-align:center}.trix-content .attachment__caption .attachment__name+.attachment__size::before{content:\' \\B7 \'}.trix-content .attachment--preview{width:100%;text-align:center}.trix-content .attachment--preview .attachment__caption{color:#666;font-size:0.9em;line-height:1.2}.trix-content .attachment--file{color:#333;line-height:1;margin:0 2px 2px 2px;padding:0.4em 1em;border:1px solid #bbb;border-radius:5px}.trix-content .attachment-gallery{display:flex;flex-wrap:wrap;position:relative}.trix-content .attachment-gallery .attachment{flex:1 0 33%;padding:0 0.5em;max-width:33%}.trix-content .attachment-gallery.attachment-gallery--2 .attachment,.trix-content .attachment-gallery.attachment-gallery--4 .attachment{flex-basis:50%;max-width:50%}\n',""])},function(t,e){t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var r=function(t,e){var r=t[1]||"",o=t[3];if(!o)return r;if(e&&"function"==typeof btoa){var i=(a=o,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(a))))+" */"),n=o.sources.map((function(t){return"/*# sourceURL="+o.sourceRoot+t+" */"}));return[r].concat(n).concat([i]).join("\n")}var a;return[r].join("\n")}(e,t);return e[2]?"@media "+e[2]+"{"+r+"}":r})).join("")},e.i=function(t,r){"string"==typeof t&&(t=[[null,t,""]]);for(var o={},i=0;i=0&&d.splice(e,1)}function m(t){var e=document.createElement("style");if(void 0===t.attrs.type&&(t.attrs.type="text/css"),void 0===t.attrs.nonce){var o=function(){0;return r.nc}();o&&(t.attrs.nonce=o)}return f(e,t.attrs),h(t,e),e}function f(t,e){Object.keys(e).forEach((function(r){t.setAttribute(r,e[r])}))}function v(t,e){var r,o,i,n;if(e.transform&&t.css){if(!(n="function"==typeof e.transform?e.transform(t.css):e.transform.default(t.css)))return function(){};t.css=n}if(e.singleton){var a=u++;r=s||(s=m(e)),o=F.bind(null,r,a,!1),i=F.bind(null,r,a,!0)}else t.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(r=function(t){var e=document.createElement("link");return void 0===t.attrs.type&&(t.attrs.type="text/css"),t.attrs.rel="stylesheet",f(e,t.attrs),h(t,e),e}(e),o=E.bind(null,r,e),i=function(){g(r),r.href&&URL.revokeObjectURL(r.href)}):(r=m(e),o=k.bind(null,r),i=function(){g(r)});return o(t),function(e){if(e){if(e.css===t.css&&e.media===t.media&&e.sourceMap===t.sourceMap)return;o(t=e)}else i()}}t.exports=function(t,e){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(e=e||{}).attrs="object"==typeof e.attrs?e.attrs:{},e.singleton||"boolean"==typeof e.singleton||(e.singleton=a()),e.insertInto||(e.insertInto="head"),e.insertAt||(e.insertAt="bottom");var r=x(t,e);return p(r,e),function(t){for(var o=[],i=0;it.length)&&(e=t.length);for(var r=0,o=new Array(e);r0&&void 0!==arguments[0]?arguments[0]:"react-trix-rte-input-",e=(new Date).getTime(),r=Math.random().toFixed(9).slice(2);return"".concat(t).concat(e,"-").concat(r)}(),C=t.name||"content",_=m?{"data-direct-upload-url":w||"/rails/active_storage/direct_uploads","data-blob-url-template":F||"/rails/active_storage/blobs/:signed_id/*filename"}:{},O={};function j(t){var e=t.target.value;z(e),s&&s(t,e)}return v&&(O.autofocus=!0),y&&(O.class=y),Object(n.useEffect)((function(){if(k&&k.current){var t=k.current.addEventListener;t("trix-change",j),i&&t("trix-focus",i),o&&t("trix-blur",o),u&&t("trix-initialize",u),d&&t("trix-file-accept",d),b&&t("trix-attachment-add",b),p&&t("trix-attachment-remove",p),x&&t("trix-selection-change",x),h&&t("trix-before-initialize",h)}return function(){if(k&&k.current){var t=k.current.removeEventListener;t("trix-change",j),i&&t("trix-focus",i),o&&t("trix-blur",o),u&&t("trix-initialize",u),d&&t("trix-file-accept",d),b&&t("trix-attachment-add",b),x&&t("trix-selection-change",x),p&&t("trix-attachment-remove",p),h&&t("trix-before-initialize",h)}}}),[]),a.a.createElement(a.a.Fragment,null,a.a.createElement("input",{id:A,value:D,type:"hidden",name:C}),a.a.createElement("trix-editor",l({toolbar:r,placeholder:f,ref:k,input:A},_,O)))}u.defaultProps={isRailsDirectUpload:!1,defaultValue:""},u.propTypes={id:i.a.string,toolbarId:i.a.string,defaultValue:i.a.string,name:i.a.string,onChange:i.a.func,onBlur:i.a.func,onFocus:i.a.func,onFileAccepted:i.a.func,onAttachmentAdd:i.a.func,onAttachmentRemove:i.a.func,onSelectionChange:i.a.func,onInitialize:i.a.func,onBeforeInitialize:i.a.func,trixInputRef:i.a.func,isRailsDirectUpload:i.a.bool,placeholder:i.a.string,autofocus:i.a.bool,className:i.a.string,railsDirectUploadUrl:i.a.string,railsBlobUrl:i.a.string};var d=u,b={bold:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-bold",languageKey:"bold",tabIndex:"-1",trixButtonGroup:"text-tools",data:{trixAttribute:"bold",trixKey:"b"}},italic:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-italic",languageKey:"italic",tabIndex:"-1",trixButtonGroup:"text-tools",data:{trixAttribute:"italic",trixKey:"i"}},strike:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-strike",languageKey:"strike",tabIndex:"-1",trixButtonGroup:"text-tools",data:{trixAttribute:"strike"}},link:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-link",languageKey:"link",tabIndex:"-1",trixButtonGroup:"text-tools",data:{trixAttribute:"href",trixKey:"k",trixAction:"link"}},heading1:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-heading-1",languageKey:"heading1",tabIndex:"-1",trixButtonGroup:"block-tools",data:{trixAttribute:"heading1"}},quote:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-quote",languageKey:"quote",tabIndex:"-1",trixButtonGroup:"block-tools",data:{trixAttribute:"quote"}},code:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-code",languageKey:"code",tabIndex:"-1",trixButtonGroup:"block-tools",data:{trixAttribute:"code"}},bullet:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-bullet-list",languageKey:"bullets",tabIndex:"-1",trixButtonGroup:"block-tools",data:{trixAttribute:"bullet"}},number:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-number-list",languageKey:"numbers",tabIndex:"-1",trixButtonGroup:"block-tools",data:{trixAttribute:"number"}},outdent:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-decrease-nesting-level",languageKey:"outdent",tabIndex:"-1",trixButtonGroup:"block-tools",data:{trixAction:"decreaseNestingLevel"}},indent:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-increase-nesting-level",languageKey:"indent",tabIndex:"-1",trixButtonGroup:"block-tools",data:{trixAction:"increaseNestingLevel"}},attachFiles:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-attach",languageKey:"attachFiles",tabIndex:"-1",trixButtonGroup:"file-tools",data:{trixAction:"attachFiles"}},undo:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-undo",languageKey:"undo",tabIndex:"-1",trixButtonGroup:"history-tools",data:{trixAction:"undo",trixKey:"z"}},redo:{type:"button",classNames:"trix-button trix-button--icon trix-button--icon-redo",languageKey:"redo",tabIndex:"-1",trixButtonGroup:"history-tools",data:{trixAction:"redo",trixKey:"shift+z"}}},p={attachFiles:"Attach Files",bold:"Bold",bullets:"Bullets",byte:"Byte",bytes:"Bytes",captionPlaceholder:"Add a caption…",code:"Code",heading1:"Heading",indent:"Increase Level",italic:"Italic",link:"Link",numbers:"Numbers",outdent:"Decrease Level",quote:"Quote",redo:"Redo",remove:"Remove",strike:"Strikethrough",undo:"Undo",unlink:"Unlink",url:"URL",urlPlaceholder:"Enter a URL…",GB:"GB",KB:"KB",MB:"MB",PB:"PB",TB:"TB"},x={"text-tools":"trix-button-group trix-button-group--text-tools","block-tools":"trix-button-group trix-button-group--block-tools","file-tools":"trix-button-group trix-button-group--file-tools","history-tools":"trix-button-group trix-button-group--history-tools"};function h(){return(h=Object.assign||function(t){for(var e=1;e