mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-15 15:44:45 -04:00
Improvement/move document content into redux (#691)
This commit is contained in:
parent
0750695e2f
commit
1690a7bdcf
15 changed files with 101 additions and 51 deletions
|
@ -6,7 +6,7 @@ import { ConnectionIndicator } from './connection-indicator/connection-indicator
|
||||||
import { DocumentInfoButton } from './document-info/document-info-button'
|
import { DocumentInfoButton } from './document-info/document-info-button'
|
||||||
import { EditorMenu } from './menus/editor-menu'
|
import { EditorMenu } from './menus/editor-menu'
|
||||||
import { ExportMenu } from './menus/export-menu'
|
import { ExportMenu } from './menus/export-menu'
|
||||||
import { ImportMenu, ImportProps } from './menus/import-menu'
|
import { ImportMenu } from './menus/import-menu'
|
||||||
import { PermissionButton } from './permissions/permission-button'
|
import { PermissionButton } from './permissions/permission-button'
|
||||||
import { RevisionButton } from './revisions/revision-button'
|
import { RevisionButton } from './revisions/revision-button'
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ export interface DocumentBarProps {
|
||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentBar: React.FC<DocumentBarProps & ImportProps> = ({ title, noteContent, updateNoteContent }) => {
|
export const DocumentBar: React.FC<DocumentBarProps> = ({ title }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -22,13 +22,13 @@ export const DocumentBar: React.FC<DocumentBarProps & ImportProps> = ({ title, n
|
||||||
<div className="navbar-nav">
|
<div className="navbar-nav">
|
||||||
<ShareLinkButton/>
|
<ShareLinkButton/>
|
||||||
<DocumentInfoButton/>
|
<DocumentInfoButton/>
|
||||||
<RevisionButton noteContent={noteContent}/>
|
<RevisionButton/>
|
||||||
<PinToHistoryButton/>
|
<PinToHistoryButton/>
|
||||||
<PermissionButton/>
|
<PermissionButton/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-auto navbar-nav">
|
<div className="ml-auto navbar-nav">
|
||||||
<ImportMenu updateNoteContent={updateNoteContent} noteContent={noteContent}/>
|
<ImportMenu/>
|
||||||
<ExportMenu title={title} noteContent={noteContent}/>
|
<ExportMenu title={title}/>
|
||||||
<EditorMenu noteTitle={title}/>
|
<EditorMenu noteTitle={title}/>
|
||||||
<ConnectionIndicator/>
|
<ConnectionIndicator/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import React, { Fragment, useCallback, useRef } from 'react'
|
import React, { Fragment, useCallback, useRef } from 'react'
|
||||||
import { Dropdown } from 'react-bootstrap'
|
import { Dropdown } from 'react-bootstrap'
|
||||||
import { Trans } from 'react-i18next'
|
import { Trans } from 'react-i18next'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import { ApplicationState } from '../../../../redux'
|
||||||
|
import { setDocumentContent } from '../../../../redux/document-content/methods'
|
||||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { ImportProps } from '../menus/import-menu'
|
|
||||||
|
|
||||||
export const ImportFile: React.FC<ImportProps> = ({ noteContent, updateNoteContent }) => {
|
export const ImportFile: React.FC = () => {
|
||||||
|
const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content)
|
||||||
|
|
||||||
const fileInputReference = useRef<HTMLInputElement>(null)
|
const fileInputReference = useRef<HTMLInputElement>(null)
|
||||||
const doImport = useCallback(() => {
|
const doImport = useCallback(() => {
|
||||||
const fileInput = fileInputReference.current
|
const fileInput = fileInputReference.current
|
||||||
|
@ -19,10 +23,10 @@ export const ImportFile: React.FC<ImportProps> = ({ noteContent, updateNoteConte
|
||||||
const fileReader = new FileReader()
|
const fileReader = new FileReader()
|
||||||
fileReader.addEventListener('load', () => {
|
fileReader.addEventListener('load', () => {
|
||||||
const newContent = fileReader.result as string
|
const newContent = fileReader.result as string
|
||||||
if (noteContent.length === 0) {
|
if (markdownContent.length === 0) {
|
||||||
updateNoteContent(newContent)
|
setDocumentContent(newContent)
|
||||||
} else {
|
} else {
|
||||||
updateNoteContent(noteContent + '\n' + newContent)
|
setDocumentContent(markdownContent + '\n' + newContent)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
fileReader.addEventListener('loadend', () => {
|
fileReader.addEventListener('loadend', () => {
|
||||||
|
@ -31,7 +35,7 @@ export const ImportFile: React.FC<ImportProps> = ({ noteContent, updateNoteConte
|
||||||
fileReader.readAsText(file)
|
fileReader.readAsText(file)
|
||||||
})
|
})
|
||||||
fileInput.click()
|
fileInput.click()
|
||||||
}, [fileInputReference, noteContent, updateNoteContent])
|
}, [markdownContent])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|
|
@ -7,10 +7,9 @@ import { MarkdownExportDropdownItem } from './export/markdown'
|
||||||
|
|
||||||
export interface ExportMenuProps {
|
export interface ExportMenuProps {
|
||||||
title: string
|
title: string
|
||||||
noteContent: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExportMenu: React.FC<ExportMenuProps> = ({ title, noteContent }) => {
|
export const ExportMenu: React.FC<ExportMenuProps> = ({ title }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
return (
|
return (
|
||||||
<Dropdown className='small mx-1' alignRight={true}>
|
<Dropdown className='small mx-1' alignRight={true}>
|
||||||
|
@ -40,10 +39,7 @@ export const ExportMenu: React.FC<ExportMenuProps> = ({ title, noteContent }) =>
|
||||||
<Dropdown.Header>
|
<Dropdown.Header>
|
||||||
<Trans i18nKey='editor.documentBar.download'/>
|
<Trans i18nKey='editor.documentBar.download'/>
|
||||||
</Dropdown.Header>
|
</Dropdown.Header>
|
||||||
<MarkdownExportDropdownItem
|
<MarkdownExportDropdownItem title={title} />
|
||||||
title={title}
|
|
||||||
noteContent={noteContent}
|
|
||||||
/>
|
|
||||||
<Dropdown.Item className='small'>
|
<Dropdown.Item className='small'>
|
||||||
<ForkAwesomeIcon icon='file-code-o' className={'mx-2'}/>
|
<ForkAwesomeIcon icon='file-code-o' className={'mx-2'}/>
|
||||||
HTML
|
HTML
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Dropdown } from 'react-bootstrap'
|
import { Dropdown } from 'react-bootstrap'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import { ApplicationState } from '../../../../../redux'
|
||||||
import { download } from '../../../../common/download/download'
|
import { download } from '../../../../common/download/download'
|
||||||
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
|
||||||
export interface MarkdownExportDropdownItemProps {
|
export interface MarkdownExportDropdownItemProps {
|
||||||
title: string
|
title: string
|
||||||
noteContent: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MarkdownExportDropdownItem: React.FC<MarkdownExportDropdownItemProps> = ({ title, noteContent }) => {
|
export const MarkdownExportDropdownItem: React.FC<MarkdownExportDropdownItemProps> = ({ title }) => {
|
||||||
|
const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown.Item className='small' onClick={() => download(noteContent, `${title}.md`, 'text/markdown')}>
|
<Dropdown.Item className='small' onClick={() => download(markdownContent, `${title}.md`, 'text/markdown')}>
|
||||||
<ForkAwesomeIcon icon='file-text' className={'mx-2'}/>
|
<ForkAwesomeIcon icon='file-text' className={'mx-2'}/>
|
||||||
Markdown
|
Markdown
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
|
|
@ -4,12 +4,7 @@ import { Trans } from 'react-i18next'
|
||||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { ImportFile } from '../import/import-file'
|
import { ImportFile } from '../import/import-file'
|
||||||
|
|
||||||
export interface ImportProps {
|
export const ImportMenu: React.FC = () => {
|
||||||
noteContent: string
|
|
||||||
updateNoteContent: (content: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ImportMenu: React.FC<ImportProps> = ({ updateNoteContent, noteContent }) => {
|
|
||||||
return (
|
return (
|
||||||
<Dropdown className='small mx-1' alignRight={true}>
|
<Dropdown className='small mx-1' alignRight={true}>
|
||||||
<Dropdown.Toggle variant='light' size='sm' id='editor-menu-import' className=''>
|
<Dropdown.Toggle variant='light' size='sm' id='editor-menu-import' className=''>
|
||||||
|
@ -33,7 +28,7 @@ export const ImportMenu: React.FC<ImportProps> = ({ updateNoteContent, noteConte
|
||||||
<ForkAwesomeIcon icon='clipboard' className={'mx-2'}/>
|
<ForkAwesomeIcon icon='clipboard' className={'mx-2'}/>
|
||||||
<Trans i18nKey='editor.import.clipboard'/>
|
<Trans i18nKey='editor.import.clipboard'/>
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<ImportFile updateNoteContent={updateNoteContent} noteContent={noteContent}/>
|
<ImportFile/>
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,17 +2,13 @@ import React, { Fragment, useState } from 'react'
|
||||||
import { TranslatedIconButton } from '../../../common/icon-button/translated-icon-button'
|
import { TranslatedIconButton } from '../../../common/icon-button/translated-icon-button'
|
||||||
import { RevisionModal } from './revision-modal'
|
import { RevisionModal } from './revision-modal'
|
||||||
|
|
||||||
export interface RevisionButtonProps {
|
export const RevisionButton: React.FC = () => {
|
||||||
noteContent: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RevisionButton: React.FC<RevisionButtonProps> = ({ noteContent }) => {
|
|
||||||
const [show, setShow] = useState(false)
|
const [show, setShow] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<TranslatedIconButton size={'sm'} className={'mx-1'} icon={'history'} variant={'light'} i18nKey={'editor.documentBar.revision'} onClick={() => setShow(true)}/>
|
<TranslatedIconButton size={'sm'} className={'mx-1'} icon={'history'} variant={'light'} i18nKey={'editor.documentBar.revision'} onClick={() => setShow(true)}/>
|
||||||
<RevisionModal show={show} onHide={() => setShow(false)} titleI18nKey={'editor.modal.revision.title'} icon={'history'} noteContent={noteContent}/>
|
<RevisionModal show={show} onHide={() => setShow(false)} titleI18nKey={'editor.modal.revision.title'} icon={'history'}/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,11 @@ import { UserResponse } from '../../../../api/users/types'
|
||||||
import { ApplicationState } from '../../../../redux'
|
import { ApplicationState } from '../../../../redux'
|
||||||
import { CommonModal, CommonModalProps } from '../../../common/modals/common-modal'
|
import { CommonModal, CommonModalProps } from '../../../common/modals/common-modal'
|
||||||
import { ShowIf } from '../../../common/show-if/show-if'
|
import { ShowIf } from '../../../common/show-if/show-if'
|
||||||
import { RevisionButtonProps } from './revision-button'
|
|
||||||
import { RevisionModalListEntry } from './revision-modal-list-entry'
|
import { RevisionModalListEntry } from './revision-modal-list-entry'
|
||||||
import './revision-modal.scss'
|
import './revision-modal.scss'
|
||||||
import { downloadRevision, getUserDataForRevision } from './utils'
|
import { downloadRevision, getUserDataForRevision } from './utils'
|
||||||
|
|
||||||
export const RevisionModal: React.FC<CommonModalProps & RevisionButtonProps> = ({ show, onHide, icon, titleI18nKey, noteContent }) => {
|
export const RevisionModal: React.FC<CommonModalProps> = ({ show, onHide, icon, titleI18nKey }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const [revisions, setRevisions] = useState<RevisionListEntry[]>([])
|
const [revisions, setRevisions] = useState<RevisionListEntry[]>([])
|
||||||
const [selectedRevisionTimestamp, setSelectedRevisionTimestamp] = useState<number | null>(null)
|
const [selectedRevisionTimestamp, setSelectedRevisionTimestamp] = useState<number | null>(null)
|
||||||
|
@ -47,6 +46,8 @@ export const RevisionModal: React.FC<CommonModalProps & RevisionButtonProps> = (
|
||||||
}).catch(() => setError(true))
|
}).catch(() => setError(true))
|
||||||
}, [selectedRevisionTimestamp, id])
|
}, [selectedRevisionTimestamp, id])
|
||||||
|
|
||||||
|
const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonModal show={show} onHide={onHide} titleI18nKey={titleI18nKey} icon={icon} closeButton={true} size={'xl'} additionalClasses='revision-modal'>
|
<CommonModal show={show} onHide={onHide} titleI18nKey={titleI18nKey} icon={icon} closeButton={true} size={'xl'} additionalClasses='revision-modal'>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
|
@ -75,7 +76,7 @@ export const RevisionModal: React.FC<CommonModalProps & RevisionButtonProps> = (
|
||||||
<ShowIf condition={!error && !!selectedRevision}>
|
<ShowIf condition={!error && !!selectedRevision}>
|
||||||
<ReactDiffViewer
|
<ReactDiffViewer
|
||||||
oldValue={selectedRevision?.content}
|
oldValue={selectedRevision?.content}
|
||||||
newValue={noteContent}
|
newValue={markdownContent}
|
||||||
splitView={false}
|
splitView={false}
|
||||||
compareMethod={DiffMethod.WORDS}
|
compareMethod={DiffMethod.WORDS}
|
||||||
useDarkTheme={darkModeEnabled}
|
useDarkTheme={darkModeEnabled}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import React, { RefObject, useState } from 'react'
|
import React, { RefObject, useState } from 'react'
|
||||||
import { Dropdown } from 'react-bootstrap'
|
import { Dropdown } from 'react-bootstrap'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
import useResizeObserver from 'use-resize-observer'
|
import useResizeObserver from 'use-resize-observer'
|
||||||
import { TocAst } from '../../../external-types/markdown-it-toc-done-right/interface'
|
import { TocAst } from '../../../external-types/markdown-it-toc-done-right/interface'
|
||||||
|
import { ApplicationState } from '../../../redux'
|
||||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { ShowIf } from '../../common/show-if/show-if'
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
import { LineMarkerPosition } from '../../markdown-renderer/types'
|
import { LineMarkerPosition } from '../../markdown-renderer/types'
|
||||||
|
@ -10,7 +12,6 @@ import { TableOfContents } from '../table-of-contents/table-of-contents'
|
||||||
import { YAMLMetaData } from '../yaml-metadata/yaml-metadata'
|
import { YAMLMetaData } from '../yaml-metadata/yaml-metadata'
|
||||||
|
|
||||||
export interface DocumentRenderPaneProps {
|
export interface DocumentRenderPaneProps {
|
||||||
content: string
|
|
||||||
extraClasses?: string
|
extraClasses?: string
|
||||||
onFirstHeadingChange: (firstHeading: string | undefined) => void
|
onFirstHeadingChange: (firstHeading: string | undefined) => void
|
||||||
onLineMarkerPositionChanged?: (lineMarkerPosition: LineMarkerPosition[]) => void
|
onLineMarkerPositionChanged?: (lineMarkerPosition: LineMarkerPosition[]) => void
|
||||||
|
@ -23,7 +24,6 @@ export interface DocumentRenderPaneProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentRenderPane: React.FC<DocumentRenderPaneProps> = ({
|
export const DocumentRenderPane: React.FC<DocumentRenderPaneProps> = ({
|
||||||
content,
|
|
||||||
extraClasses,
|
extraClasses,
|
||||||
onFirstHeadingChange,
|
onFirstHeadingChange,
|
||||||
onLineMarkerPositionChanged,
|
onLineMarkerPositionChanged,
|
||||||
|
@ -37,6 +37,7 @@ export const DocumentRenderPane: React.FC<DocumentRenderPaneProps> = ({
|
||||||
const [tocAst, setTocAst] = useState<TocAst>()
|
const [tocAst, setTocAst] = useState<TocAst>()
|
||||||
const { width } = useResizeObserver(rendererReference ? { ref: rendererReference } : undefined)
|
const { width } = useResizeObserver(rendererReference ? { ref: rendererReference } : undefined)
|
||||||
const realWidth = width || 0
|
const realWidth = width || 0
|
||||||
|
const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`bg-light flex-fill pb-5 flex-row d-flex w-100 h-100 ${extraClasses ?? ''}`}
|
<div className={`bg-light flex-fill pb-5 flex-row d-flex w-100 h-100 ${extraClasses ?? ''}`}
|
||||||
|
@ -45,7 +46,7 @@ export const DocumentRenderPane: React.FC<DocumentRenderPaneProps> = ({
|
||||||
<div className={'bg-light flex-fill'}>
|
<div className={'bg-light flex-fill'}>
|
||||||
<FullMarkdownRenderer
|
<FullMarkdownRenderer
|
||||||
className={'flex-fill mb-3'}
|
className={'flex-fill mb-3'}
|
||||||
content={content}
|
content={markdownContent}
|
||||||
onFirstHeadingChange={onFirstHeadingChange}
|
onFirstHeadingChange={onFirstHeadingChange}
|
||||||
onLineMarkerPositionChanged={onLineMarkerPositionChanged}
|
onLineMarkerPositionChanged={onLineMarkerPositionChanged}
|
||||||
onMetaDataChange={onMetadataChange}
|
onMetaDataChange={onMetadataChange}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import React, { useMemo, useRef, useState } from 'react'
|
import React, { useMemo, useRef, useState } from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import { ApplicationState } from '../../../redux'
|
||||||
import { LineMarkerPosition } from '../../markdown-renderer/types'
|
import { LineMarkerPosition } from '../../markdown-renderer/types'
|
||||||
import { useScrollToLineMark } from '../scroll/hooks/use-scroll-to-line-mark'
|
import { useScrollToLineMark } from '../scroll/hooks/use-scroll-to-line-mark'
|
||||||
import { useUserScroll } from '../scroll/hooks/use-user-scroll'
|
import { useUserScroll } from '../scroll/hooks/use-user-scroll'
|
||||||
|
@ -6,7 +8,6 @@ import { ScrollProps } from '../scroll/scroll-props'
|
||||||
import { DocumentRenderPane, DocumentRenderPaneProps } from './document-render-pane'
|
import { DocumentRenderPane, DocumentRenderPaneProps } from './document-render-pane'
|
||||||
|
|
||||||
export const ScrollingDocumentRenderPane: React.FC<DocumentRenderPaneProps & ScrollProps> = ({
|
export const ScrollingDocumentRenderPane: React.FC<DocumentRenderPaneProps & ScrollProps> = ({
|
||||||
content,
|
|
||||||
scrollState,
|
scrollState,
|
||||||
wide,
|
wide,
|
||||||
onFirstHeadingChange,
|
onFirstHeadingChange,
|
||||||
|
@ -15,16 +16,16 @@ export const ScrollingDocumentRenderPane: React.FC<DocumentRenderPaneProps & Scr
|
||||||
onScroll,
|
onScroll,
|
||||||
onTaskCheckedChange
|
onTaskCheckedChange
|
||||||
}) => {
|
}) => {
|
||||||
|
const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content)
|
||||||
const renderer = useRef<HTMLDivElement>(null)
|
const renderer = useRef<HTMLDivElement>(null)
|
||||||
const [lineMarks, setLineMarks] = useState<LineMarkerPosition[]>()
|
const [lineMarks, setLineMarks] = useState<LineMarkerPosition[]>()
|
||||||
|
|
||||||
const contentLineCount = useMemo(() => content.split('\n').length, [content])
|
const contentLineCount = useMemo(() => markdownContent.split('\n').length, [markdownContent])
|
||||||
useScrollToLineMark(scrollState, lineMarks, contentLineCount, renderer)
|
useScrollToLineMark(scrollState, lineMarks, contentLineCount, renderer)
|
||||||
const userScroll = useUserScroll(lineMarks, renderer, onScroll)
|
const userScroll = useUserScroll(lineMarks, renderer, onScroll)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocumentRenderPane
|
<DocumentRenderPane
|
||||||
content={content}
|
|
||||||
extraClasses={'overflow-y-scroll'}
|
extraClasses={'overflow-y-scroll'}
|
||||||
rendererReference={renderer}
|
rendererReference={renderer}
|
||||||
wide={wide}
|
wide={wide}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import useMedia from 'use-media'
|
||||||
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
||||||
import { useDocumentTitle } from '../../hooks/common/use-document-title'
|
import { useDocumentTitle } from '../../hooks/common/use-document-title'
|
||||||
import { ApplicationState } from '../../redux'
|
import { ApplicationState } from '../../redux'
|
||||||
|
import { setDocumentContent } from '../../redux/document-content/methods'
|
||||||
import { setEditorMode } from '../../redux/editor/methods'
|
import { setEditorMode } from '../../redux/editor/methods'
|
||||||
import { extractNoteTitle } from '../common/document-title/note-title-extractor'
|
import { extractNoteTitle } from '../common/document-title/note-title-extractor'
|
||||||
import { MotdBanner } from '../common/motd-banner/motd-banner'
|
import { MotdBanner } from '../common/motd-banner/motd-banner'
|
||||||
|
@ -34,7 +35,7 @@ const TASK_REGEX = /(\s*[-*] )(\[[ xX]])( .*)/
|
||||||
export const Editor: React.FC = () => {
|
export const Editor: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const untitledNote = t('editor.untitledNote')
|
const untitledNote = t('editor.untitledNote')
|
||||||
const [markdownContent, setMarkdownContent] = useState(editorTestContent)
|
const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content)
|
||||||
const isWide = useMedia({ minWidth: 576 })
|
const isWide = useMedia({ minWidth: 576 })
|
||||||
const [documentTitle, setDocumentTitle] = useState(untitledNote)
|
const [documentTitle, setDocumentTitle] = useState(untitledNote)
|
||||||
const noteMetadata = useRef<YAMLMetaData>()
|
const noteMetadata = useRef<YAMLMetaData>()
|
||||||
|
@ -49,6 +50,10 @@ export const Editor: React.FC = () => {
|
||||||
rendererScrollState: { firstLineInView: 1, scrolledPercentage: 0 }
|
rendererScrollState: { firstLineInView: 1, scrolledPercentage: 0 }
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDocumentContent(editorTestContent)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const updateDocumentTitle = useCallback(() => {
|
const updateDocumentTitle = useCallback(() => {
|
||||||
const noteTitle = extractNoteTitle(untitledNote, noteMetadata.current, firstHeading.current)
|
const noteTitle = extractNoteTitle(untitledNote, noteMetadata.current, firstHeading.current)
|
||||||
setDocumentTitle(noteTitle)
|
setDocumentTitle(noteTitle)
|
||||||
|
@ -71,9 +76,9 @@ export const Editor: React.FC = () => {
|
||||||
const before = results[1]
|
const before = results[1]
|
||||||
const after = results[3]
|
const after = results[3]
|
||||||
lines[lineInMarkdown] = `${before}[${checked ? 'x' : ' '}]${after}`
|
lines[lineInMarkdown] = `${before}[${checked ? 'x' : ' '}]${after}`
|
||||||
setMarkdownContent(lines.join('\n'))
|
setDocumentContent(lines.join('\n'))
|
||||||
}
|
}
|
||||||
}, [markdownContent, setMarkdownContent])
|
}, [markdownContent])
|
||||||
|
|
||||||
useViewModeShortcuts()
|
useViewModeShortcuts()
|
||||||
|
|
||||||
|
@ -105,12 +110,12 @@ export const Editor: React.FC = () => {
|
||||||
<MotdBanner/>
|
<MotdBanner/>
|
||||||
<div className={'d-flex flex-column vh-100'}>
|
<div className={'d-flex flex-column vh-100'}>
|
||||||
<AppBar mode={AppBarMode.EDITOR}/>
|
<AppBar mode={AppBarMode.EDITOR}/>
|
||||||
<DocumentBar title={documentTitle} noteContent={markdownContent} updateNoteContent={(newContent) => setMarkdownContent(newContent)}/>
|
<DocumentBar title={documentTitle}/>
|
||||||
<Splitter
|
<Splitter
|
||||||
showLeft={editorMode === EditorMode.EDITOR || editorMode === EditorMode.BOTH}
|
showLeft={editorMode === EditorMode.EDITOR || editorMode === EditorMode.BOTH}
|
||||||
left={
|
left={
|
||||||
<EditorPane
|
<EditorPane
|
||||||
onContentChange={content => setMarkdownContent(content)}
|
onContentChange={setDocumentContent}
|
||||||
content={markdownContent}
|
content={markdownContent}
|
||||||
scrollState={scrollState.editorScrollState}
|
scrollState={scrollState.editorScrollState}
|
||||||
onScroll={onEditorScroll}
|
onScroll={onEditorScroll}
|
||||||
|
@ -120,7 +125,6 @@ export const Editor: React.FC = () => {
|
||||||
showRight={editorMode === EditorMode.PREVIEW || (editorMode === EditorMode.BOTH)}
|
showRight={editorMode === EditorMode.PREVIEW || (editorMode === EditorMode.BOTH)}
|
||||||
right={
|
right={
|
||||||
<ScrollingDocumentRenderPane
|
<ScrollingDocumentRenderPane
|
||||||
content={markdownContent}
|
|
||||||
onFirstHeadingChange={onFirstHeadingChange}
|
onFirstHeadingChange={onFirstHeadingChange}
|
||||||
onMakeScrollSource={() => {
|
onMakeScrollSource={() => {
|
||||||
scrollSource.current = ScrollSource.RENDERER
|
scrollSource.current = ScrollSource.RENDERER
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { useParams } from 'react-router'
|
||||||
import { getNote, Note } from '../../api/notes'
|
import { getNote, Note } from '../../api/notes'
|
||||||
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
||||||
import { useDocumentTitle } from '../../hooks/common/use-document-title'
|
import { useDocumentTitle } from '../../hooks/common/use-document-title'
|
||||||
|
import { setDocumentContent } from '../../redux/document-content/methods'
|
||||||
import { extractNoteTitle } from '../common/document-title/note-title-extractor'
|
import { extractNoteTitle } from '../common/document-title/note-title-extractor'
|
||||||
import { MotdBanner } from '../common/motd-banner/motd-banner'
|
import { MotdBanner } from '../common/motd-banner/motd-banner'
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
import { ShowIf } from '../common/show-if/show-if'
|
||||||
|
@ -42,7 +43,10 @@ export const PadViewOnly: React.FC = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getNote(id)
|
getNote(id)
|
||||||
.then(note => setNoteData(note))
|
.then(note => {
|
||||||
|
setNoteData(note)
|
||||||
|
setDocumentContent(note.content)
|
||||||
|
})
|
||||||
.catch(() => setError(true))
|
.catch(() => setError(true))
|
||||||
.finally(() => setLoading(false))
|
.finally(() => setLoading(false))
|
||||||
}, [id])
|
}, [id])
|
||||||
|
@ -80,7 +84,6 @@ export const PadViewOnly: React.FC = () => {
|
||||||
viewCount={noteData?.viewcount ?? 0}
|
viewCount={noteData?.viewcount ?? 0}
|
||||||
/>
|
/>
|
||||||
<DocumentRenderPane
|
<DocumentRenderPane
|
||||||
content={noteData?.content ?? ''}
|
|
||||||
onFirstHeadingChange={onFirstHeadingChange}
|
onFirstHeadingChange={onFirstHeadingChange}
|
||||||
onMetadataChange={onMetadataChange}
|
onMetadataChange={onMetadataChange}
|
||||||
onTaskCheckedChange={() => false}
|
onTaskCheckedChange={() => false}
|
||||||
|
|
10
src/redux/document-content/methods.ts
Normal file
10
src/redux/document-content/methods.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { store } from '..'
|
||||||
|
import { DocumentContentActionType, SetDocumentContentAction } from './types'
|
||||||
|
|
||||||
|
export const setDocumentContent = (content: string): void => {
|
||||||
|
const action: SetDocumentContentAction = {
|
||||||
|
type: DocumentContentActionType.SET_DOCUMENT_CONTENT,
|
||||||
|
content: content
|
||||||
|
}
|
||||||
|
store.dispatch(action)
|
||||||
|
}
|
15
src/redux/document-content/reducers.ts
Normal file
15
src/redux/document-content/reducers.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { Reducer } from 'redux'
|
||||||
|
import { DocumentContent, DocumentContentAction, DocumentContentActionType, SetDocumentContentAction } from './types'
|
||||||
|
|
||||||
|
export const initialState: DocumentContent = {
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DocumentContentReducer: Reducer<DocumentContent, DocumentContentAction> = (state: DocumentContent = initialState, action: DocumentContentAction) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case DocumentContentActionType.SET_DOCUMENT_CONTENT:
|
||||||
|
return { content: (action as SetDocumentContentAction).content }
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
17
src/redux/document-content/types.ts
Normal file
17
src/redux/document-content/types.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { Action } from 'redux'
|
||||||
|
|
||||||
|
export enum DocumentContentActionType {
|
||||||
|
SET_DOCUMENT_CONTENT = 'document-content/set',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DocumentContent {
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DocumentContentAction extends Action<DocumentContentActionType> {
|
||||||
|
type: DocumentContentActionType
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetDocumentContentAction extends DocumentContentAction {
|
||||||
|
content: string
|
||||||
|
}
|
|
@ -7,6 +7,8 @@ import { BannerState } from './banner/types'
|
||||||
import { ConfigReducer } from './config/reducers'
|
import { ConfigReducer } from './config/reducers'
|
||||||
import { DarkModeConfigReducer } from './dark-mode/reducers'
|
import { DarkModeConfigReducer } from './dark-mode/reducers'
|
||||||
import { DarkModeConfig } from './dark-mode/types'
|
import { DarkModeConfig } from './dark-mode/types'
|
||||||
|
import { DocumentContentReducer } from './document-content/reducers'
|
||||||
|
import { DocumentContent } from './document-content/types'
|
||||||
import { EditorConfigReducer } from './editor/reducers'
|
import { EditorConfigReducer } from './editor/reducers'
|
||||||
import { EditorConfig } from './editor/types'
|
import { EditorConfig } from './editor/types'
|
||||||
import { UserReducer } from './user/reducers'
|
import { UserReducer } from './user/reducers'
|
||||||
|
@ -19,6 +21,7 @@ export interface ApplicationState {
|
||||||
apiUrl: ApiUrlObject;
|
apiUrl: ApiUrlObject;
|
||||||
editorConfig: EditorConfig;
|
editorConfig: EditorConfig;
|
||||||
darkMode: DarkModeConfig;
|
darkMode: DarkModeConfig;
|
||||||
|
documentContent: DocumentContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
|
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
|
||||||
|
@ -27,7 +30,8 @@ export const allReducers: Reducer<ApplicationState> = combineReducers<Applicatio
|
||||||
banner: BannerReducer,
|
banner: BannerReducer,
|
||||||
apiUrl: ApiUrlReducer,
|
apiUrl: ApiUrlReducer,
|
||||||
editorConfig: EditorConfigReducer,
|
editorConfig: EditorConfigReducer,
|
||||||
darkMode: DarkModeConfigReducer
|
darkMode: DarkModeConfigReducer,
|
||||||
|
documentContent: DocumentContentReducer
|
||||||
})
|
})
|
||||||
|
|
||||||
export const store = createStore(allReducers)
|
export const store = createStore(allReducers)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue