diff --git a/src/components/document-read-only-page/document-read-only-page.tsx b/src/components/document-read-only-page/document-read-only-page.tsx index befee0a99..07327bf5b 100644 --- a/src/components/document-read-only-page/document-read-only-page.tsx +++ b/src/components/document-read-only-page/document-read-only-page.tsx @@ -9,7 +9,6 @@ import { useTranslation } from 'react-i18next' import { useParams } from 'react-router' import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode' import { useDocumentTitleWithNoteTitle } from '../../hooks/common/use-document-title-with-note-title' -import { useNoteMarkdownContent } from '../../hooks/common/use-note-markdown-content' import { updateNoteTitleByFirstHeading } from '../../redux/note-details/methods' import { MotdBanner } from '../common/motd-banner/motd-banner' import { ShowIf } from '../common/show-if/show-if' @@ -23,6 +22,7 @@ import { LoadingNoteAlert } from './LoadingNoteAlert' import { RendererType } from '../render-page/rendering-message' import { useApplicationState } from '../../hooks/common/use-application-state' import { IframeEditorToRendererCommunicatorContextProvider } from '../editor-page/render-context/iframe-editor-to-renderer-communicator-context-provider' +import { useNoteMarkdownContentWithoutFrontmatter } from '../../hooks/common/use-note-markdown-content-without-frontmatter' export const DocumentReadOnlyPage: React.FC = () => { useTranslation() @@ -33,7 +33,7 @@ export const DocumentReadOnlyPage: React.FC = () => { const onFirstHeadingChange = useCallback(updateNoteTitleByFirstHeading, []) const [error, loading] = useLoadNoteFromServer() - const markdownContent = useNoteMarkdownContent() + const markdownContent = useNoteMarkdownContentWithoutFrontmatter() const noteDetails = useApplicationState((state) => state.noteDetails) return ( diff --git a/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx b/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx new file mode 100644 index 000000000..4cc4799f5 --- /dev/null +++ b/src/components/editor-page/editor-document-renderer/editor-document-renderer.tsx @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React from 'react' +import { RenderIframe, RenderIframeProps } from '../renderer-pane/render-iframe' +import { useNoteMarkdownContentWithoutFrontmatter } from '../../../hooks/common/use-note-markdown-content-without-frontmatter' + +export type EditorDocumentRendererProps = Omit + +/** + * Renders the markdown content from the global application state with the iframe renderer. + * + * @param props Every property from the {@link RenderIframe} except the markdown content. + */ +export const EditorDocumentRenderer: React.FC = (props) => { + const markdownContent = useNoteMarkdownContentWithoutFrontmatter() + return +} diff --git a/src/components/editor-page/editor-page.tsx b/src/components/editor-page/editor-page.tsx index ca9a7e987..6fb26455b 100644 --- a/src/components/editor-page/editor-page.tsx +++ b/src/components/editor-page/editor-page.tsx @@ -8,12 +8,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode' import { useDocumentTitleWithNoteTitle } from '../../hooks/common/use-document-title-with-note-title' -import { useNoteMarkdownContent } from '../../hooks/common/use-note-markdown-content' -import { - setCheckboxInMarkdownContent, - setNoteContent, - updateNoteTitleByFirstHeading -} from '../../redux/note-details/methods' +import { setCheckboxInMarkdownContent, updateNoteTitleByFirstHeading } from '../../redux/note-details/methods' import { MotdBanner } from '../common/motd-banner/motd-banner' import { ShowIf } from '../common/show-if/show-if' import { ErrorWhileLoadingNoteAlert } from '../document-read-only-page/ErrorWhileLoadingNoteAlert' @@ -23,7 +18,6 @@ import { EditorMode } from './app-bar/editor-view-mode' import { EditorPane } from './editor-pane/editor-pane' import { useLoadNoteFromServer } from './hooks/useLoadNoteFromServer' import { useViewModeShortcuts } from './hooks/useViewModeShortcuts' -import { RenderIframe } from './renderer-pane/render-iframe' import { Sidebar } from './sidebar/sidebar' import { Splitter } from './splitter/splitter' import { DualScrollState, ScrollState } from './synced-scroll/scroll-props' @@ -34,6 +28,7 @@ import { useNotificationTest } from './use-notification-test' import { IframeEditorToRendererCommunicatorContextProvider } from './render-context/iframe-editor-to-renderer-communicator-context-provider' import { useUpdateLocalHistoryEntry } from './hooks/useUpdateLocalHistoryEntry' import { useApplicationState } from '../../hooks/common/use-application-state' +import { EditorDocumentRenderer } from './editor-document-renderer/editor-document-renderer' export interface EditorPagePathParams { id: string @@ -46,10 +41,7 @@ export enum ScrollSource { export const EditorPage: React.FC = () => { useTranslation() - const markdownContent = useNoteMarkdownContent() const scrollSource = useRef(ScrollSource.EDITOR) - - const documentContent = useApplicationState((state) => state.noteDetails.documentContent) const editorMode: EditorMode = useApplicationState((state) => state.editorConfig.editorMode) const editorSyncScroll: boolean = useApplicationState((state) => state.editorConfig.syncScroll) @@ -98,21 +90,18 @@ export const EditorPage: React.FC = () => { const leftPane = useMemo( () => ( ), - [documentContent, onEditorScroll, scrollState.editorScrollState, setEditorToScrollSource] + [onEditorScroll, scrollState.editorScrollState, setEditorToScrollSource] ) const rightPane = useMemo( () => ( - { rendererType={RendererType.DOCUMENT} /> ), - [markdownContent, onMarkdownRendererScroll, scrollState.rendererScrollState, setRendererToScrollSource] + [onMarkdownRendererScroll, scrollState.rendererScrollState, setRendererToScrollSource] ) return ( diff --git a/src/components/editor-page/editor-pane/editor-pane.tsx b/src/components/editor-page/editor-pane/editor-pane.tsx index ce48d2e97..adc19401b 100644 --- a/src/components/editor-page/editor-pane/editor-pane.tsx +++ b/src/components/editor-page/editor-pane/editor-pane.tsx @@ -19,6 +19,8 @@ import { handleUpload } from './upload-handler' import { handleFilePaste, handleTablePaste, PasteEvent } from './tool-bar/utils/pasteHandlers' import { useApplicationState } from '../../../hooks/common/use-application-state' import './codemirror-imports' +import { setNoteContent } from '../../../redux/note-details/methods' +import { useNoteMarkdownContent } from '../../../hooks/common/use-note-markdown-content' export interface EditorPaneProps { onContentChange: (content: string) => void @@ -50,13 +52,8 @@ interface DropEvent { preventDefault: () => void } -export const EditorPane: React.FC = ({ - onContentChange, - content, - scrollState, - onScroll, - onMakeScrollSource -}) => { +export const EditorPane: React.FC = ({ scrollState, onScroll, onMakeScrollSource }) => { + const markdownContent = useNoteMarkdownContent() const { t } = useTranslation() const maxLength = useApplicationState((state) => state.config.maxDocumentLength) const smartPasteEnabled = useApplicationState((state) => state.editorConfig.smartPaste) @@ -128,10 +125,11 @@ export const EditorPane: React.FC = ({ if (value.length <= maxLength) { maxLengthWarningAlreadyShown.current = false } - onContentChange(value) + setNoteContent(value) }, - [onContentChange, maxLength, maxLengthWarningAlreadyShown] + [maxLength] ) + const onEditorDidMount = useCallback( (mountedEditor: Editor) => { setStatusBarInfo(createStatusInfo(mountedEditor, maxLength)) @@ -204,7 +202,7 @@ export const EditorPane: React.FC = ({ { const { t } = useTranslation() - const documentContent = useApplicationState((state) => state.noteDetails.documentContent) + const markdownContent = useNoteMarkdownContent() const onClick = useCallback(() => { const sanitized = sanitize(store.getState().noteDetails.noteTitle) - download(documentContent, `${sanitized !== '' ? sanitized : t('editor.untitledNote')}.md`, 'text/markdown') - }, [documentContent, t]) + download(markdownContent, `${sanitized !== '' ? sanitized : t('editor.untitledNote')}.md`, 'text/markdown') + }, [markdownContent, t]) return ( diff --git a/src/hooks/common/use-note-markdown-content-without-frontmatter.ts b/src/hooks/common/use-note-markdown-content-without-frontmatter.ts new file mode 100644 index 000000000..35037ae3c --- /dev/null +++ b/src/hooks/common/use-note-markdown-content-without-frontmatter.ts @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useNoteMarkdownContent } from './use-note-markdown-content' +import { useApplicationState } from './use-application-state' +import { useMemo } from 'react' + +/** + * Extracts the markdown content of the current note from the global application state and removes the frontmatter. + * @return the markdown content of the note without frontmatter + */ +export const useNoteMarkdownContentWithoutFrontmatter = (): string => { + const markdownContent = useNoteMarkdownContent() + const offsetLines = useApplicationState((state) => state.noteDetails.frontmatterRendererInfo.offsetLines) + + return useMemo(() => markdownContent.split('\n').slice(offsetLines).join('\n'), [markdownContent, offsetLines]) +} diff --git a/src/hooks/common/use-note-markdown-content.ts b/src/hooks/common/use-note-markdown-content.ts index 2c33f140a..54b1e52b9 100644 --- a/src/hooks/common/use-note-markdown-content.ts +++ b/src/hooks/common/use-note-markdown-content.ts @@ -6,6 +6,10 @@ import { useApplicationState } from './use-application-state' +/** + * Extracts the markdown content of the current note from the global application state. + * @return the markdown content of the note + */ export const useNoteMarkdownContent = (): string => { return useApplicationState((state) => state.noteDetails.markdownContent) } diff --git a/src/redux/note-details/initial-state.ts b/src/redux/note-details/initial-state.ts index f08ce184d..1f222a8fc 100644 --- a/src/redux/note-details/initial-state.ts +++ b/src/redux/note-details/initial-state.ts @@ -9,7 +9,6 @@ import { DateTime } from 'luxon' import { NoteTextDirection, NoteType } from '../../components/common/note-frontmatter/types' export const initialState: NoteDetails = { - documentContent: '', markdownContent: '', rawFrontmatter: '', frontmatterRendererInfo: { diff --git a/src/redux/note-details/reducer.ts b/src/redux/note-details/reducer.ts index 496c21ebe..81f8ce878 100644 --- a/src/redux/note-details/reducer.ts +++ b/src/redux/note-details/reducer.ts @@ -19,7 +19,7 @@ export const NoteDetailsReducer: Reducer = ( ) => { switch (action.type) { case NoteDetailsActionType.SET_DOCUMENT_CONTENT: - return buildStateFromDocumentContentUpdate(state, action.content) + return buildStateFromMarkdownContentUpdate(state, action.content) case NoteDetailsActionType.UPDATE_NOTE_TITLE_BY_FIRST_HEADING: return buildStateFromFirstHeadingUpdate(state, action.firstHeading) case NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER: @@ -40,7 +40,7 @@ const TASK_REGEX = /(\s*(?:[-*+]|\d+[.)]) )(\[[ xX]])( .*)/ */ const buildStateFromServerDto = (dto: NoteDto): NoteDetails => { const newState = convertNoteDtoToNoteDetails(dto) - return buildStateFromDocumentContentUpdate(newState, newState.documentContent) + return buildStateFromMarkdownContentUpdate(newState, newState.markdownContent) } /** @@ -55,13 +55,13 @@ const buildStateFromTaskListUpdate = ( changedLine: number, checkboxChecked: boolean ): NoteDetails => { - const lines = state.documentContent.split('\n') + const lines = state.markdownContent.split('\n') const results = TASK_REGEX.exec(lines[changedLine]) if (results) { const before = results[1] const after = results[3] lines[changedLine] = `${before}[${checkboxChecked ? 'x' : ' '}]${after}` - return buildStateFromDocumentContentUpdate(state, lines.join('\n')) + return buildStateFromMarkdownContentUpdate(state, lines.join('\n')) } return state } @@ -69,17 +69,17 @@ const buildStateFromTaskListUpdate = ( /** * Builds a {@link NoteDetails} redux state from a fresh document content. * @param state The previous redux state. - * @param documentContent The fresh document content consisting of the frontmatter and markdown part. + * @param markdownContent The fresh document content consisting of the frontmatter and markdown part. * @return An updated {@link NoteDetails} redux state. */ -const buildStateFromDocumentContentUpdate = (state: NoteDetails, documentContent: string): NoteDetails => { - const frontmatterExtraction = extractFrontmatter(documentContent) +const buildStateFromMarkdownContentUpdate = (state: NoteDetails, markdownContent: string): NoteDetails => { + const frontmatterExtraction = extractFrontmatter(markdownContent) if (!frontmatterExtraction.frontmatterPresent) { return { ...state, - documentContent: documentContent, - markdownContent: documentContent, + markdownContent: markdownContent, rawFrontmatter: '', + noteTitle: generateNoteTitle(initialState.frontmatter, state.firstHeading), frontmatter: initialState.frontmatter, frontmatterRendererInfo: initialState.frontmatterRendererInfo } @@ -87,8 +87,7 @@ const buildStateFromDocumentContentUpdate = (state: NoteDetails, documentContent return buildStateFromFrontmatterUpdate( { ...state, - documentContent: documentContent, - markdownContent: documentContent.split('\n').slice(frontmatterExtraction.frontmatterLines).join('\n') + markdownContent: markdownContent }, frontmatterExtraction ) @@ -113,7 +112,7 @@ const buildStateFromFrontmatterUpdate = ( ...state, rawFrontmatter: frontmatterExtraction.rawFrontmatterText, frontmatter: frontmatter, - noteTitle: generateNoteTitle(frontmatter), + noteTitle: generateNoteTitle(frontmatter, state.firstHeading), frontmatterRendererInfo: { offsetLines: frontmatterExtraction.frontmatterLines, deprecatedSyntax: frontmatter.deprecatedTagsSyntax, @@ -123,6 +122,7 @@ const buildStateFromFrontmatterUpdate = ( } catch (e) { return { ...state, + noteTitle: generateNoteTitle(initialState.frontmatter, state.firstHeading), rawFrontmatter: frontmatterExtraction.rawFrontmatterText, frontmatter: initialState.frontmatter, frontmatterRendererInfo: { @@ -170,8 +170,7 @@ const generateNoteTitle = (frontmatter: NoteFrontmatter, firstHeading?: string) */ const convertNoteDtoToNoteDetails = (note: NoteDto): NoteDetails => { return { - documentContent: note.content, - markdownContent: '', + markdownContent: note.content, rawFrontmatter: '', frontmatterRendererInfo: { frontmatterInvalid: false, diff --git a/src/redux/note-details/types.ts b/src/redux/note-details/types.ts index 298473836..8ddeca91a 100644 --- a/src/redux/note-details/types.ts +++ b/src/redux/note-details/types.ts @@ -25,7 +25,6 @@ interface LastChange { * Redux state containing the currently loaded note with its content and metadata. */ export interface NoteDetails { - documentContent: string markdownContent: string rawFrontmatter: string frontmatter: NoteFrontmatter