feat: markdown to html
This commit is contained in:
parent
c58e021b5a
commit
84a2b6bec2
@ -20,7 +20,7 @@ const renderSelectedWorkSpace = () => {
|
||||
<div className='flex-1'>
|
||||
<div className="py-1">
|
||||
<div className="mx-auto px-4 sm:px-6 md:px-8">
|
||||
<div className="py-2">
|
||||
<div className="py-1">
|
||||
<div className="mx-auto px-4 sm:px-6 md:px-8">
|
||||
<h1 className="text-2xl font-semibold text-gray-900">
|
||||
{getSelectedDocument()?.name || 'Image Processor'}
|
||||
|
||||
@ -1,18 +1,31 @@
|
||||
import Editor, { DiffEditor, useMonaco } from '@monaco-editor/react'
|
||||
import { DiffEditor } from '@monaco-editor/react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useProject } from '../../context/Project/provider'
|
||||
import type { DiffOnMount } from '@monaco-editor/react/'
|
||||
import TextEditorButtons from './TextEditorButtons'
|
||||
import createDiffEditorInteractions from '../../useCases/createDiffEditorInteractions'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
|
||||
let editorInteractions: ReturnType<typeof createDiffEditorInteractions>
|
||||
|
||||
const TextEditor = () => {
|
||||
const monaco = useMonaco()
|
||||
const { selectedDocumentId, getProcessedAreasByDocumentId } = useProject()
|
||||
const [editorHeight, setEditorHeight] = useState(window.innerHeight - 200)
|
||||
const [editorValue, setEditorValue] = useState('')
|
||||
const [isEditorReady, setIsEditorReady] = useState(false)
|
||||
const [isPreviewOpen, setIsPreviewOpen] = useState(false)
|
||||
const [modifiedEditorValue, setIsModifiedEditorValue] = useState('')
|
||||
|
||||
// useEffect(() => {
|
||||
// if (monaco) {
|
||||
// console.log("here is the monaco instance:", monaco);
|
||||
// }
|
||||
// }, [monaco])
|
||||
const handleEditorDidMount: DiffOnMount = (editor, _) => {
|
||||
editorInteractions = createDiffEditorInteractions(editor)
|
||||
const modifiedEditor = editor.getModifiedEditor()
|
||||
setIsModifiedEditorValue(modifiedEditor.getValue())
|
||||
modifiedEditor.onDidChangeModelContent(() => {
|
||||
setIsModifiedEditorValue(modifiedEditor.getValue())
|
||||
})
|
||||
|
||||
setIsEditorReady(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedDocumentId) {
|
||||
@ -20,31 +33,60 @@ const TextEditor = () => {
|
||||
return
|
||||
}
|
||||
|
||||
getProcessedAreasByDocumentId(selectedDocumentId)
|
||||
.then(response => {
|
||||
const requestProcessedArea = async () => {
|
||||
let response
|
||||
try {
|
||||
response = await getProcessedAreasByDocumentId(selectedDocumentId)
|
||||
if (!response || response.length === 0) return
|
||||
|
||||
const fullTextOfAreas = response
|
||||
.map(area => area.fullText + '\n')
|
||||
.join('\n')
|
||||
|
||||
const fullTextOfAreas = response.map(area => area.fullText + '\n').join('\n')
|
||||
setEditorValue(fullTextOfAreas)
|
||||
})
|
||||
.catch(console.error)
|
||||
|
||||
}, [selectedDocumentId])
|
||||
} catch (err) { console.error(err) }
|
||||
}
|
||||
requestProcessedArea()
|
||||
}, [selectedDocumentId, getProcessedAreasByDocumentId])
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
setEditorHeight(window.innerHeight - 200)
|
||||
})
|
||||
|
||||
return <div>
|
||||
<Editor
|
||||
value={editorValue}
|
||||
defaultLanguage='markdown'
|
||||
height={`${editorHeight}px`}
|
||||
return <div className='relative m-0 p-0'>
|
||||
{ isEditorReady
|
||||
? <TextEditorButtons
|
||||
togglePreview={() => setIsPreviewOpen(!isPreviewOpen)}
|
||||
editorInteractions={editorInteractions}
|
||||
/>
|
||||
: ''
|
||||
}
|
||||
<DiffEditor
|
||||
original={editorValue}
|
||||
modified={editorValue}
|
||||
language='markdown'
|
||||
height={`${editorHeight}px`}
|
||||
onMount={handleEditorDidMount}
|
||||
/>
|
||||
|
||||
{isPreviewOpen
|
||||
? <div
|
||||
className='absolute w-1/2 top-[30px] bg-white overflow-y-scroll p-0 m-0'
|
||||
style={{'height': `${editorHeight}px`}}>
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
h1: ({node, ...props}) => <h1 {...props} className='font-extrabold text-2xl' />,
|
||||
h2: ({node, ...props}) => <h2 {...props} className='font-extrabold text-xl' />,
|
||||
h3: ({node, ...props}) => <h3 {...props} className='font-extrabold text-lg' />,
|
||||
h4: ({node, ...props}) => <h4 {...props} className='font-extrabold text-base' />,
|
||||
h5: ({node, ...props}) => <h5 {...props} className='font-bold text-base' />,
|
||||
h6: ({node, ...props}) => <h6 {...props} className='font-semibold text-base' />,
|
||||
ul: ({node, ...props}) => <ul {...props} className='list-disc list-inside ml-2' />,
|
||||
ol: ({node, ...props}) => <ol {...props} className='list-decimal list-inside ml-2' />,
|
||||
}}
|
||||
>
|
||||
{modifiedEditorValue}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default TextEditor
|
||||
|
||||
115
frontend/components/workspace/TextEditorButtons.tsx
Normal file
115
frontend/components/workspace/TextEditorButtons.tsx
Normal file
@ -0,0 +1,115 @@
|
||||
import { ListBulletIcon } from '@heroicons/react/20/solid'
|
||||
import { EyeIcon } from '@heroicons/react/24/outline'
|
||||
import createDiffEditorInteractions, { MarkdownOperator } from '../../useCases/createDiffEditorInteractions'
|
||||
|
||||
function classNames(...classes: any[]) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
type Props = {
|
||||
editorInteractions: ReturnType<typeof createDiffEditorInteractions>
|
||||
togglePreview: () => void
|
||||
}
|
||||
|
||||
const TextEditorButtons = (props: Props) => {
|
||||
const { editorInteractions, togglePreview } = props
|
||||
return <span className="isolate inline-flex rounded-md shadow-sm">
|
||||
<button
|
||||
type="button"
|
||||
onClick={togglePreview}
|
||||
className="relative -ml-px inline-flex items-center rounded-r-md border border-gray-300 bg-white px-2 py-0 text-sm font-medium text-gray-500 hover:bg-gray-50 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
||||
>
|
||||
<span className="sr-only">Next</span>
|
||||
<EyeIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => editorInteractions.insertMarkdownOperator(MarkdownOperator.H1)}
|
||||
className={classNames(
|
||||
'text-lg relative inline-flex items-center rounded-l-md border',
|
||||
'border-gray-300 bg-white px-2 py-0 text-gray-700 hover:bg-gray-50',
|
||||
'focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1',
|
||||
'focus:ring-indigo-500 font-extrabold',
|
||||
)}>
|
||||
<span className="sr-only">Header 1</span>
|
||||
H1
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => editorInteractions.insertMarkdownOperator(MarkdownOperator.H2)}
|
||||
className={classNames(
|
||||
'text-base relative inline-flex items-center border',
|
||||
'border-gray-300 bg-white px-2 py-0 text-gray-700 hover:bg-gray-50',
|
||||
'focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1',
|
||||
'focus:ring-indigo-500 font-bold',
|
||||
)}>
|
||||
<span className="sr-only">Header 2</span>
|
||||
H2
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => editorInteractions.insertMarkdownOperator(MarkdownOperator.H3)}
|
||||
className={classNames(
|
||||
'text-sm relative inline-flex items-center border',
|
||||
'border-gray-300 bg-white px-2 py-0 text-gray-700 hover:bg-gray-50',
|
||||
'focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1',
|
||||
'focus:ring-indigo-500 font-bold',
|
||||
)}>
|
||||
<span className="sr-only">Header 3</span>
|
||||
H3
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => editorInteractions.insertMarkdownOperator(MarkdownOperator.H4)}
|
||||
className={classNames(
|
||||
'text-xs relative inline-flex items-center border',
|
||||
'border-gray-300 bg-white px-2 py-0 text-gray-700 hover:bg-gray-50',
|
||||
'focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1',
|
||||
'focus:ring-indigo-500 font-bold',
|
||||
)}>
|
||||
<span className="sr-only">Header 4</span>
|
||||
H4
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => editorInteractions.insertMarkdownOperator(MarkdownOperator.ITALLICS)}
|
||||
className={classNames(
|
||||
'text-sm relative inline-flex items-center rounded-r-md border',
|
||||
'border-gray-300 bg-white px-2 py-0 text-gray-700 hover:bg-gray-50',
|
||||
'focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1',
|
||||
'focus:ring-indigo-500 italic font-serif',
|
||||
)}>
|
||||
<span className="sr-only">Italic</span>
|
||||
I
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => editorInteractions.insertMarkdownOperator(MarkdownOperator.BOLD)}
|
||||
className={classNames(
|
||||
'text-sm relative inline-flex items-center rounded-r-md border',
|
||||
'border-gray-300 bg-white px-2 py-0 text-gray-700 hover:bg-gray-50',
|
||||
'focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1',
|
||||
'focus:ring-indigo-500 font-extrabold',
|
||||
)}>
|
||||
<span className="sr-only">Bold</span>
|
||||
B
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => editorInteractions.insertMarkdownOperator(MarkdownOperator.BULLET)}
|
||||
className={classNames(
|
||||
'text-sm relative inline-flex items-center rounded-r-md border',
|
||||
'border-gray-300 bg-white px-2 py-0 text-gray-700 hover:bg-gray-50',
|
||||
'focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1',
|
||||
'focus:ring-indigo-500 italic',
|
||||
)}>
|
||||
<span className="sr-only">Bullet</span>
|
||||
<ListBulletIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
|
||||
export default TextEditorButtons
|
||||
1461
frontend/package-lock.json
generated
1461
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,7 @@
|
||||
"next": "^13.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^8.0.5",
|
||||
"tesseract.js": "^4.0.2",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
|
||||
@ -1 +1 @@
|
||||
6ae330b5487329c78105faea78224dd4
|
||||
963e9c454713999e77f8d595ec4cc5ea
|
||||
75
frontend/useCases/createDiffEditorInteractions.ts
Normal file
75
frontend/useCases/createDiffEditorInteractions.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
|
||||
|
||||
export enum MarkdownOperator {
|
||||
H1 = '# ',
|
||||
H2 = '## ',
|
||||
H3 = '### ',
|
||||
H4 = '#### ',
|
||||
ITALLICS = '__',
|
||||
BOLD = '**',
|
||||
BULLET = '* ',
|
||||
}
|
||||
|
||||
const wrapperOperators = [
|
||||
MarkdownOperator.ITALLICS,
|
||||
MarkdownOperator.BOLD
|
||||
]
|
||||
|
||||
const createDiffEditorInteractions = (editor: monaco.editor.IStandaloneDiffEditor)=> {
|
||||
const modifiedEditor = editor.getModifiedEditor()
|
||||
const originalEditor = editor.getOriginalEditor()
|
||||
|
||||
return {
|
||||
undo: () => { },
|
||||
redo: () => { },
|
||||
insertMarkdownOperator: (operator: MarkdownOperator) => {
|
||||
const selection = modifiedEditor.getSelection()
|
||||
if (!selection) return
|
||||
|
||||
const { startColumn, startLineNumber, endColumn, endLineNumber } = selection
|
||||
|
||||
const doesSelectionHaveRange = (endLineNumber > startLineNumber) || (endColumn > startColumn)
|
||||
|
||||
const lineOfCursor = startLineNumber
|
||||
const lengthOfLine = (modifiedEditor.getModel()?.getLineLength(lineOfCursor) || 1) + 1
|
||||
|
||||
let range: monaco.IRange = { startColumn, startLineNumber, endColumn, endLineNumber, }
|
||||
let newText = modifiedEditor.getModel()?.getValueInRange(range) || ''
|
||||
|
||||
if (wrapperOperators.includes(operator)) {
|
||||
if (!doesSelectionHaveRange) {
|
||||
range = {
|
||||
startLineNumber: lineOfCursor,
|
||||
endLineNumber: lineOfCursor,
|
||||
startColumn: 0,
|
||||
endColumn: lengthOfLine
|
||||
}
|
||||
}
|
||||
|
||||
newText = `${operator}${modifiedEditor.getModel()?.getValueInRange(range)}${operator}`
|
||||
} else {
|
||||
const wordAtStartPosition = modifiedEditor.getModel()?.getWordAtPosition({
|
||||
column:startColumn, lineNumber: startLineNumber
|
||||
})
|
||||
|
||||
if (!doesSelectionHaveRange && wordAtStartPosition) {
|
||||
range = {
|
||||
startLineNumber,
|
||||
startColumn: wordAtStartPosition.startColumn,
|
||||
endLineNumber,
|
||||
endColumn: wordAtStartPosition.endColumn
|
||||
}
|
||||
}
|
||||
|
||||
newText = `${operator}${modifiedEditor.getModel()?.getValueInRange(range)}`
|
||||
}
|
||||
|
||||
modifiedEditor.executeEdits('editor', [{
|
||||
range,
|
||||
text: newText
|
||||
}])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default createDiffEditorInteractions
|
||||
Loading…
x
Reference in New Issue
Block a user