Skip to content

Commit

Permalink
feature: html editor v1
Browse files Browse the repository at this point in the history
  • Loading branch information
hassanad94 committed May 10, 2024
1 parent 7b54885 commit dcf0ef0
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ export const RichTextEditor: React.FC<
''
const classes = useStyles(props)

console.log(props.settings.ControlHint)

switch (props.actionName) {
case 'edit':
case 'new':
Expand Down
Original file line number Diff line number Diff line change
@@ -1,152 +1,84 @@
import {
Button,
CircularProgress,
createStyles,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
IconButton,
IconButtonProps,
makeStyles,
Tooltip,
} from '@material-ui/core'
import CodeIcon from '@material-ui/icons/Code'
import { Editor } from '@tiptap/react'
import React, { FC, useRef, useState } from 'react'
import React, { FC, useState } from 'react'
import { useLocalization } from '../../hooks'
import { HtmlEditor } from '../html-editor'

const useStyles = makeStyles(() => {
return createStyles({
image: {
maxWidth: '100%',
height: 'auto',
},
fileName: {
marginTop: '0.5rem',
textAlign: 'center',
},
})
})

interface ImageControlProps {
interface HTMLEditorControlProps {
editor: Editor
buttonProps?: Partial<IconButtonProps>
}

type ImageFile = File & { src?: string }

export const HtmlEditorControl: FC<ImageControlProps> = ({ buttonProps, editor }) => {
export const HTMLEditorControl: FC<HTMLEditorControlProps> = ({ editor, buttonProps }) => {
const [open, setOpen] = useState(false)
const [uploadedFile, setUploadedFile] = useState<ImageFile>()
const [isUploadInProgress, setIsUploadInProgress] = useState(false)
const fileInput = useRef<HTMLInputElement>(null)

const classes = useStyles()
const [html, setHtml] = useState(editor.getHTML())
const localization = useLocalization()

const handleClickOpen = () => {
if (isUploadInProgress) {
return
}
fileInput.current && fileInput.current.click()
setOpen(true)
}

const handleClose = () => {
setOpen(false)
}

const addImage = () => {
if (uploadedFile?.src) {
editor.chain().focus().setImage({ src: uploadedFile.src }).run()
if (editor.getHTML() === html) {
setOpen(false)
return
}
}

const imageToBase64 = (image: File) => {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader()
reader.onload = (e) => {
if (e.target?.result) {
resolve(e.target.result as string)
} else {
reject(e)
}
}
reader.readAsDataURL(image)
})
}

const upload = async (fileToUpload: File) => {
if (!fileToUpload) {
const confirmResult = window.confirm(localization.HTMLEditorControl.confirm)
if (!confirmResult) {
return
}
editor.commands.setContent(html)

setIsUploadInProgress(true)

const imageSrc = await imageToBase64(fileToUpload)
const image: ImageFile = new File([fileToUpload], fileToUpload.name, { type: fileToUpload.type })
image.src = imageSrc

setIsUploadInProgress(false)
setUploadedFile(image)
setOpen(false)
}

return (
<>
<Tooltip title={`${localization.menubar.EditHtml}`}>
<IconButton
onClick={() => editor.chain().focus().toggleCode().run()}
onClick={() => handleClickOpen()}
color={editor.isActive('code') ? 'primary' : 'default'}
{...buttonProps}>
<CodeIcon style={{ marginTop: '-7px' }} />
<span style={{ position: 'absolute', fontSize: '12px', top: '13px', fontWeight: 'bold' }}>html</span>
</IconButton>
</Tooltip>
<input
onChange={(ev) => {
setOpen(true)

ev.target.files && upload(ev.target.files[0])
}}
style={{ display: 'none' }}
ref={fileInput}
type="file"
accept="image/*"
multiple={false}
/>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
onExited={() => {
setUploadedFile(undefined)

if (fileInput.current) {
fileInput.current.value = ''
}
}}>
<DialogTitle id="form-dialog-title">{localization.imageControl.title}</DialogTitle>
aria-labelledby="html-editor-control-title"
fullWidth
maxWidth="sm"
onExited={() => {}}>
<DialogTitle id="html-editor-control-title">{localization.HTMLEditorControl.title}</DialogTitle>
<DialogContent>
{uploadedFile ? (
<>
<img src={uploadedFile.src} alt="" className={classes.image} />
<div className={classes.fileName}>{uploadedFile.name}</div>
</>
) : (
<div style={{ textAlign: 'center' }}>
<CircularProgress />
</div>
)}
<HtmlEditor initialState={editor.getHTML()} fieldOnChange={setHtml} />
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>{localization.common.cancel}</Button>
<Button
onClick={() => {
handleClose()
addImage()
setOpen(false)
}}>
{localization.common.cancel}
</Button>
<Button
onClick={() => {
editor.commands.setContent(html)
setOpen(false)
}}
color="primary">
{localization.imageControl.submit}
{localization.linkControl.submit}
</Button>
</DialogActions>
</Dialog>
Expand Down
83 changes: 83 additions & 0 deletions packages/sn-editor-react/src/components/html-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// import { InputLabel, Theme } from '@material-ui/core'
import React, { useEffect, useRef, useState } from 'react'
import MonacoEditor from 'react-monaco-editor'

/**
* Field control that represents a HTMLEDITOr.
*/
export const HtmlEditor: React.FC<{
initialState?: string
fieldOnChange?: (value: string) => void
}> = (props) => {
const [value, setValue] = useState(props.initialState)

const editorRef = useRef<MonacoEditor>(null)
const containerRef = useRef<HTMLDivElement>(null)

useEffect(() => {
if (!editorRef.current) {
return
}
editorRef.current.editor!.onDidContentSizeChange(() => {
containerRef.current!.style.height = `${400}px`
})
}, [editorRef])

const editorChangeHandler = (newValue: string) => {
setValue(newValue)
props.fieldOnChange?.(newValue)
}

return (
<div style={{ height: '400px', margin: '0.5rem 0' }} ref={containerRef} data-test="html-editor-container">
<MonacoEditor
ref={editorRef}
{...props}
width="100%"
height="100%"
value={value}
onChange={editorChangeHandler}
options={{
automaticLayout: true,
contextmenu: true,
hideCursorInOverviewRuler: true,
lineNumbers: 'on',
selectOnLineNumbers: true,
scrollBeyondLastLine: false,
minimap: {
enabled: true,
},
roundedSelection: false,
cursorStyle: 'line',
scrollbar: {
horizontalSliderSize: 4,
verticalScrollbarSize: 6,
// vertical: 'hidden',
},

wordWrap: 'on',
autoIndent: 'advanced',
matchBrackets: 'always',
language: 'html',
suggest: {
snippetsPreventQuickSuggestions: false,
showProperties: true,
showKeywords: true,
showWords: true,
},
}}
// theme={props.theme?.palette.type === 'dark' ? 'admin-ui-dark' : 'vs-light'}
editorWillMount={(monaco) => {
monaco.editor.defineTheme('admin-ui-dark', {
base: 'vs-dark',
inherit: true,
rules: [],
colors: {
'editor.background': '#121212',
},
})
}}
/>
</div>
)
}
6 changes: 3 additions & 3 deletions packages/sn-editor-react/src/components/menu-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Editor } from '@tiptap/react'
import React, { FC } from 'react'
import { useLocalization } from '../hooks'
import { getCommonStyles } from '../styles'
import { HtmlEditorControl, ImageControl, LinkControl, TableControl, TypographyControl } from './controls'
import { HTMLEditorControl, ImageControl, TableControl, TypographyControl } from './controls'

const useStyles = makeStyles((theme) => {
const commonStyles = getCommonStyles(theme)
Expand Down Expand Up @@ -163,7 +163,7 @@ export const MenuBar: FC<MenuBarProps> = ({ editor }) => {
<FormatListNumberedIcon />
</IconButton>
</Tooltip>
<LinkControl
<HTMLEditorControl
editor={editor}
buttonProps={{ classes: { root: classes.button, colorPrimary: classes.buttonPrimary } }}
/>
Expand Down Expand Up @@ -201,7 +201,7 @@ export const MenuBar: FC<MenuBarProps> = ({ editor }) => {
<RedoIcon />
</IconButton>
</Tooltip>
<HtmlEditorControl
<HTMLEditorControl
editor={editor}
buttonProps={{ classes: { root: classes.button, colorPrimary: classes.buttonPrimary } }}
/>
Expand Down
4 changes: 4 additions & 0 deletions packages/sn-editor-react/src/context/localization-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export const defaultLocalization = {
openInNewTab: 'Open link in a new tab',
submit: 'Insert',
},
HTMLEditorControl: {
title: 'Edit HTML',
confirm: 'Would you like to save the changes?',
},
tableControl: {
title: 'Insert table',
rows: 'Rows',
Expand Down

0 comments on commit dcf0ef0

Please sign in to comment.