Skip to content

Commit

Permalink
feat: add TinyMCE editor
Browse files Browse the repository at this point in the history
  • Loading branch information
dziraf committed Feb 29, 2024
1 parent edb490b commit 38109ea
Show file tree
Hide file tree
Showing 10 changed files with 515 additions and 100 deletions.
9 changes: 4 additions & 5 deletions config/rollup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
import babel from '@rollup/plugin-babel'
import commonjs from '@rollup/plugin-commonjs'
import resolve from '@rollup/plugin-node-resolve'
import terser from '@rollup/plugin-terser'
import { minify } from 'rollup-plugin-esbuild-minify'
import replace from '@rollup/plugin-replace'
import presetEnv from '@babel/preset-env'
import presetReact from '@babel/preset-react'
import presetTs from '@babel/preset-typescript'

const minify = process.env.NODE_ENV === 'production'
const extensions = ['.mjs', '.js', '.jsx', '.json', '.ts', '.tsx']

const plugins = [
Expand All @@ -35,7 +34,7 @@ const plugins = [
presetTs,
],
}),
...(minify ? [terser()] : []),
...(process.env.NODE_ENV === 'production' ? [minify()] : []),
]

export default {
Expand All @@ -50,8 +49,8 @@ export default {
'react-feather',
],
output: {
file: minify ? 'bundle.production.js' : 'bundle.development.js',
sourcemap: minify ? false : 'inline',
file: process.env.NODE_ENV === 'production' ? 'bundle.production.js' : 'bundle.development.js',
sourcemap: process.env.NODE_ENV === 'production' ? false : 'inline',
name: 'AdminJSDesignSystem',
format: 'iife',
interop: 'auto',
Expand Down
19 changes: 12 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,10 @@
"@babel/preset-typescript": "^7.21.0",
"@commitlint/cli": "^17.5.0",
"@commitlint/config-conventional": "^17.4.4",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-terser": "^0.4.0",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.5",
"@semantic-release/git": "^10.0.1",
"@storybook/addon-essentials": "^7.6.3",
"@storybook/addon-interactions": "^7.6.3",
Expand All @@ -69,6 +68,7 @@
"@storybook/source-loader": "^7.6.3",
"@storybook/testing-library": "^0.2.2",
"@storybook/theming": "^7.6.3",
"@types/hoist-non-react-statics": "3.3.1",
"@types/react": "^18.0.15",
"@types/react-datepicker": "^4.10.0",
"@types/react-text-mask": "^5.4.11",
Expand All @@ -90,7 +90,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"resolve-typescript-plugin": "^2.0.1",
"rollup": "^3.17.3",
"rollup": "^4.12.0",
"rollup-plugin-esbuild-minify": "^1.1.1",
"semantic-release": "^20.1.3",
"semantic-release-slack-bot": "^4.0.0",
"source-map-loader": "^4.0.1",
Expand All @@ -101,6 +102,7 @@
},
"dependencies": {
"@hypnosphi/create-react-context": "^0.3.1",
"@tinymce/tinymce-react": "^4.3.2",
"@tiptap/core": "2.1.13",
"@tiptap/extension-bubble-menu": "2.1.13",
"@tiptap/extension-character-count": "2.1.13",
Expand All @@ -122,20 +124,22 @@
"@tiptap/starter-kit": "2.1.13",
"date-fns": "^2.29.3",
"flat": "^5.0.2",
"hoist-non-react-statics": "3.3.2",
"jw-paginate": "^1.0.4",
"lodash": "^4.17.21",
"polished": "^4.2.2",
"react-currency-input-field": "^3.6.10",
"react-datepicker": "^4.10.0",
"react-feather": "^2.0.10",
"react-phone-input-2": "^2.15.1",
"react-select": "^5.7.2",
"react-select": "^5.8.0",
"react-text-mask": "^5.5.0",
"styled-components": "5.3.9",
"styled-system": "^5.1.5",
"text-mask-addons": "^3.8.0"
},
"resolutions": {
"@emotion/react": "11.10.6",
"@tiptap/core": "2.1.13",
"@tiptap/extension-bubble-menu": "2.1.13",
"@tiptap/extension-character-count": "2.1.13",
Expand All @@ -155,6 +159,7 @@
"@tiptap/pm": "2.1.13",
"@tiptap/react": "2.1.13",
"@tiptap/starter-kit": "2.1.13",
"hoist-non-react-statics": "3.3.2",
"styled-components": "5.3.9"
}
}
1 change: 1 addition & 0 deletions src/molecules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './modal/index.js'
export * from './navigation-element/index.js'
export * from './pagination/index.js'
export * from './rich-text-editor/index.js'
export * from './tinymce-editor/index.js'
export * from './select/index.js'
export * from './stepper/index.js'
export * from './value-group/index.js'
18 changes: 11 additions & 7 deletions src/molecules/select/select-async.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
/* eslint-disable import/no-named-default */
import noop from 'lodash/noop.js'
import React, { FC, lazy } from 'react'
import { AsyncProps } from 'react-select/async'
import React, { FC } from 'react'
import { AsyncProps, default as ReactAsyncSelect } from 'react-select/async'

import useSelectTheme from './select-theme.js'
import { cssClass, filterStyles, selectStyles } from '../../utils/index.js'

const ReactAsyncSelect = lazy(() => import('react-select/async') as any) as any

const SelectAsyncComponent = ReactAsyncSelect.default || ReactAsyncSelect

interface SelectProps<Option = unknown, IsMulti extends boolean = false>
extends AsyncProps<Option, IsMulti, any> {
value: Option
onChange?: (selected) => void
variant?: 'default' | 'filter'
}

let SelectComponent: typeof ReactAsyncSelect
if ((ReactAsyncSelect as any).default) {
SelectComponent = (ReactAsyncSelect as any).default
} else {
SelectComponent = ReactAsyncSelect
}

export const SelectAsync: FC<SelectProps> = (props) => {
const { value, onChange, variant, ...selectProps } = props
const { theme, selectTheme } = useSelectTheme()
Expand All @@ -26,7 +30,7 @@ export const SelectAsync: FC<SelectProps> = (props) => {
}

return (
<SelectAsyncComponent
<SelectComponent
className={cssClass('Select')}
theme={selectTheme}
value={value}
Expand Down
16 changes: 10 additions & 6 deletions src/molecules/select/select.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
/* eslint-disable import/no-named-default */
import noop from 'lodash/noop.js'
import React, { FC, lazy } from 'react'
import { Props } from 'react-select'
import React, { FC } from 'react'
import { Props, default as ReactSelect } from 'react-select'

import { cssClass, filterStyles, selectStyles } from '../../utils/index.js'
import useSelectTheme from './select-theme.js'

const ReactSelect = lazy(() => import('react-select') as any) as any

const SelectComponent = ReactSelect.default || ReactSelect

interface SelectProps extends Props {
value: any
onChange?: (selected) => void
variant?: 'default' | 'filter'
}

let SelectComponent: typeof ReactSelect
if ((ReactSelect as any).default) {
SelectComponent = (ReactSelect as any).default
} else {
SelectComponent = ReactSelect
}

export const Select: FC<SelectProps> = (props) => {
const { value, onChange, variant, isMulti, ...selectProps } = props
const { theme, selectTheme } = useSelectTheme()
Expand Down
1 change: 1 addition & 0 deletions src/molecules/tinymce-editor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './tinymce-editor.jsx'
22 changes: 22 additions & 0 deletions src/molecules/tinymce-editor/tinymce-editor.styled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { styled } from '@styled-components'

import { BoxProps } from '../../atoms/box/index.js'
import { InputProps } from '../../atoms/input/index.js'
import { Text, TextProps } from '../../atoms/text/index.js'
import { cssClass } from '../../utils/index.js'

export type EditorWrapperProps = TextProps & InputProps & BoxProps

export const EditorWrapper = styled(Text)<EditorWrapperProps>`
position: relative;
z-index: 1;
& .tox-tinymce {
padding: 8px 4px;
border-width: 1.5px;
}
`

EditorWrapper.defaultProps = {
className: cssClass('EditorWrapper'),
}
107 changes: 107 additions & 0 deletions src/molecules/tinymce-editor/tinymce-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React, { useRef, useCallback } from 'react'
import { Editor, IAllProps } from '@tinymce/tinymce-react'

import { EditorWrapper } from './tinymce-editor.styled.js'

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface TinymceEditorOptions extends Omit<Partial<IAllProps>, 'onEditorChange'> {
contentStyle?: string | null;
height?: string | null;
}

export interface TinymceEditorProps {
value: string
onChange: (value: string) => void
options?: TinymceEditorOptions
}

const TinyMCE: React.FC<TinymceEditorProps> = (props) => {
const editorRef = useRef(null)
const { onChange, value, options = {} } = props
const {
plugins = null,
toolbar = null,
init = null,
contentStyle = null,
height = null,
...otherProps
} = options

const handleUpdate = useCallback((newValue: string) => {
onChange(newValue)
}, [])

const handleInit = (evt, editor) => {
editorRef.current = editor
}

const defaultPlugins = ['image', 'code', 'table', 'link', 'media', 'codesample']
const defaultToolbar = [
'undo redo formatselect bold italic backolor alignleft aligncenter alignright alignjustify bullist numlist outdent indent removeformat link image',
]
const defaultInit = {
height: height !== null ? height : 500,
menubar: true,
plugins: plugins !== null ? plugins : defaultPlugins,
toolbar: toolbar !== null ? toolbar : defaultToolbar,
file_picker_types: 'file image media',
file_picker_callback(cb, value, meta) {

Check warning on line 48 in src/molecules/tinymce-editor/tinymce-editor.tsx

View workflow job for this annotation

GitHub Actions / test

'value' is already declared in the upper scope on line 20 column 21

Check warning on line 48 in src/molecules/tinymce-editor/tinymce-editor.tsx

View workflow job for this annotation

GitHub Actions / test

'value' is defined but never used

Check warning on line 48 in src/molecules/tinymce-editor/tinymce-editor.tsx

View workflow job for this annotation

GitHub Actions / test

'meta' is defined but never used
const input = document.createElement('input')
input.setAttribute('type', 'file')
input.setAttribute('accept', 'image/*')

/*
Note: In modern browsers input[type="file"] is functional without
even adding it to the DOM, but that might not be the case in some older
or quirky browsers like IE, so you might want to add it to the DOM
just in case, and visually hide it. And do not forget do remove it
once you do not need it anymore.
*/

input.onchange = function fpOnChange(e) {
// eslint-disable-next-line react/no-this-in-sfc
const file = (e as any).target?.files?.[0]

const reader = new FileReader()
reader.onload = function fpOnLoad() {
/*
Note: Now we need to register the blob in TinyMCEs image blob
registry. In the next release this part hopefully won't be
necessary, as we are looking to handle it internally.
*/
const id = `blobid${new Date().getTime()}`
const { blobCache } = (window as any).tinymce?.activeEditor?.editorUpload ?? {}
const base64 = (reader.result as string | null)?.split?.(',')?.[1]
const blobInfo = blobCache.create(id, file, base64)
blobCache.add(blobInfo)

/* call the callback and populate the Title field with the file name */
cb(blobInfo.blobUri(), { title: file.name })
}
reader.readAsDataURL(file)
}

input.click()
},
content_style:
contentStyle !== null
? contentStyle
: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
}

return (
<EditorWrapper>
<Editor
tinymceScriptSrc="https://cdn.jsdelivr.net/npm/[email protected]/tinymce.min.js"
onInit={handleInit}
onEditorChange={handleUpdate}
value={value}
init={init !== null ? init : defaultInit}
{...otherProps}
/>
</EditorWrapper>
)
}

export { TinyMCE }
export default TinyMCE
1 change: 1 addition & 0 deletions src/utils/select-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const selectStyles = (theme: DefaultTheme): Props['styles'] => ({
boxShadow: 'none',
background: theme.colors.container,
border: theme.borders.input,
zIndex: 100,
}),
})

Expand Down
Loading

0 comments on commit 38109ea

Please sign in to comment.