From 1690a7bdcfc63f18c44c254a9d8182223cfc367e Mon Sep 17 00:00:00 2001 From: mrdrogdrog Date: Wed, 28 Oct 2020 22:15:00 +0100 Subject: [PATCH] Improvement/move document content into redux (#691) --- .../editor/document-bar/document-bar.tsx | 10 +++++----- .../editor/document-bar/import/import-file.tsx | 16 ++++++++++------ .../editor/document-bar/menus/export-menu.tsx | 8 ++------ .../document-bar/menus/export/markdown.tsx | 9 ++++++--- .../editor/document-bar/menus/import-menu.tsx | 9 ++------- .../document-bar/revisions/revision-button.tsx | 8 ++------ .../document-bar/revisions/revision-modal.tsx | 7 ++++--- .../document-render-pane.tsx | 7 ++++--- .../scrolling-document-render-pane.tsx | 7 ++++--- src/components/editor/editor.tsx | 16 ++++++++++------ src/components/pad-view-only/pad-view-only.tsx | 7 +++++-- src/redux/document-content/methods.ts | 10 ++++++++++ src/redux/document-content/reducers.ts | 15 +++++++++++++++ src/redux/document-content/types.ts | 17 +++++++++++++++++ src/redux/index.ts | 6 +++++- 15 files changed, 101 insertions(+), 51 deletions(-) create mode 100644 src/redux/document-content/methods.ts create mode 100644 src/redux/document-content/reducers.ts create mode 100644 src/redux/document-content/types.ts diff --git a/src/components/editor/document-bar/document-bar.tsx b/src/components/editor/document-bar/document-bar.tsx index 4ed941137..32ccd382a 100644 --- a/src/components/editor/document-bar/document-bar.tsx +++ b/src/components/editor/document-bar/document-bar.tsx @@ -6,7 +6,7 @@ import { ConnectionIndicator } from './connection-indicator/connection-indicator import { DocumentInfoButton } from './document-info/document-info-button' import { EditorMenu } from './menus/editor-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 { RevisionButton } from './revisions/revision-button' @@ -14,7 +14,7 @@ export interface DocumentBarProps { title: string } -export const DocumentBar: React.FC = ({ title, noteContent, updateNoteContent }) => { +export const DocumentBar: React.FC = ({ title }) => { useTranslation() return ( @@ -22,13 +22,13 @@ export const DocumentBar: React.FC = ({ title, n
- +
- - + +
diff --git a/src/components/editor/document-bar/import/import-file.tsx b/src/components/editor/document-bar/import/import-file.tsx index b34926ecd..57efdce76 100644 --- a/src/components/editor/document-bar/import/import-file.tsx +++ b/src/components/editor/document-bar/import/import-file.tsx @@ -1,10 +1,14 @@ import React, { Fragment, useCallback, useRef } from 'react' import { Dropdown } from 'react-bootstrap' 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 { ImportProps } from '../menus/import-menu' -export const ImportFile: React.FC = ({ noteContent, updateNoteContent }) => { +export const ImportFile: React.FC = () => { + const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content) + const fileInputReference = useRef(null) const doImport = useCallback(() => { const fileInput = fileInputReference.current @@ -19,10 +23,10 @@ export const ImportFile: React.FC = ({ noteContent, updateNoteConte const fileReader = new FileReader() fileReader.addEventListener('load', () => { const newContent = fileReader.result as string - if (noteContent.length === 0) { - updateNoteContent(newContent) + if (markdownContent.length === 0) { + setDocumentContent(newContent) } else { - updateNoteContent(noteContent + '\n' + newContent) + setDocumentContent(markdownContent + '\n' + newContent) } }) fileReader.addEventListener('loadend', () => { @@ -31,7 +35,7 @@ export const ImportFile: React.FC = ({ noteContent, updateNoteConte fileReader.readAsText(file) }) fileInput.click() - }, [fileInputReference, noteContent, updateNoteContent]) + }, [markdownContent]) return ( diff --git a/src/components/editor/document-bar/menus/export-menu.tsx b/src/components/editor/document-bar/menus/export-menu.tsx index 4c4ea510b..680083dd9 100644 --- a/src/components/editor/document-bar/menus/export-menu.tsx +++ b/src/components/editor/document-bar/menus/export-menu.tsx @@ -7,10 +7,9 @@ import { MarkdownExportDropdownItem } from './export/markdown' export interface ExportMenuProps { title: string - noteContent: string } -export const ExportMenu: React.FC = ({ title, noteContent }) => { +export const ExportMenu: React.FC = ({ title }) => { useTranslation() return ( @@ -40,10 +39,7 @@ export const ExportMenu: React.FC = ({ title, noteContent }) => - + HTML diff --git a/src/components/editor/document-bar/menus/export/markdown.tsx b/src/components/editor/document-bar/menus/export/markdown.tsx index e2bd268ef..9422c2262 100644 --- a/src/components/editor/document-bar/menus/export/markdown.tsx +++ b/src/components/editor/document-bar/menus/export/markdown.tsx @@ -1,16 +1,19 @@ import React from 'react' import { Dropdown } from 'react-bootstrap' +import { useSelector } from 'react-redux' +import { ApplicationState } from '../../../../../redux' import { download } from '../../../../common/download/download' import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon' export interface MarkdownExportDropdownItemProps { title: string - noteContent: string } -export const MarkdownExportDropdownItem: React.FC = ({ title, noteContent }) => { +export const MarkdownExportDropdownItem: React.FC = ({ title }) => { + const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content) + return ( - download(noteContent, `${title}.md`, 'text/markdown')}> + download(markdownContent, `${title}.md`, 'text/markdown')}> Markdown diff --git a/src/components/editor/document-bar/menus/import-menu.tsx b/src/components/editor/document-bar/menus/import-menu.tsx index d43b972fe..27e7409c1 100644 --- a/src/components/editor/document-bar/menus/import-menu.tsx +++ b/src/components/editor/document-bar/menus/import-menu.tsx @@ -4,12 +4,7 @@ import { Trans } from 'react-i18next' import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon' import { ImportFile } from '../import/import-file' -export interface ImportProps { - noteContent: string - updateNoteContent: (content: string) => void -} - -export const ImportMenu: React.FC = ({ updateNoteContent, noteContent }) => { +export const ImportMenu: React.FC = () => { return ( @@ -33,7 +28,7 @@ export const ImportMenu: React.FC = ({ updateNoteContent, noteConte - + ) diff --git a/src/components/editor/document-bar/revisions/revision-button.tsx b/src/components/editor/document-bar/revisions/revision-button.tsx index 0372509e7..548f35143 100644 --- a/src/components/editor/document-bar/revisions/revision-button.tsx +++ b/src/components/editor/document-bar/revisions/revision-button.tsx @@ -2,17 +2,13 @@ import React, { Fragment, useState } from 'react' import { TranslatedIconButton } from '../../../common/icon-button/translated-icon-button' import { RevisionModal } from './revision-modal' -export interface RevisionButtonProps { - noteContent: string -} - -export const RevisionButton: React.FC = ({ noteContent }) => { +export const RevisionButton: React.FC = () => { const [show, setShow] = useState(false) return ( setShow(true)}/> - setShow(false)} titleI18nKey={'editor.modal.revision.title'} icon={'history'} noteContent={noteContent}/> + setShow(false)} titleI18nKey={'editor.modal.revision.title'} icon={'history'}/> ) } diff --git a/src/components/editor/document-bar/revisions/revision-modal.tsx b/src/components/editor/document-bar/revisions/revision-modal.tsx index dc8b42e9d..c23059480 100644 --- a/src/components/editor/document-bar/revisions/revision-modal.tsx +++ b/src/components/editor/document-bar/revisions/revision-modal.tsx @@ -10,12 +10,11 @@ import { UserResponse } from '../../../../api/users/types' import { ApplicationState } from '../../../../redux' import { CommonModal, CommonModalProps } from '../../../common/modals/common-modal' import { ShowIf } from '../../../common/show-if/show-if' -import { RevisionButtonProps } from './revision-button' import { RevisionModalListEntry } from './revision-modal-list-entry' import './revision-modal.scss' import { downloadRevision, getUserDataForRevision } from './utils' -export const RevisionModal: React.FC = ({ show, onHide, icon, titleI18nKey, noteContent }) => { +export const RevisionModal: React.FC = ({ show, onHide, icon, titleI18nKey }) => { useTranslation() const [revisions, setRevisions] = useState([]) const [selectedRevisionTimestamp, setSelectedRevisionTimestamp] = useState(null) @@ -47,6 +46,8 @@ export const RevisionModal: React.FC = ( }).catch(() => setError(true)) }, [selectedRevisionTimestamp, id]) + const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content) + return ( @@ -75,7 +76,7 @@ export const RevisionModal: React.FC = ( void onLineMarkerPositionChanged?: (lineMarkerPosition: LineMarkerPosition[]) => void @@ -23,7 +24,6 @@ export interface DocumentRenderPaneProps { } export const DocumentRenderPane: React.FC = ({ - content, extraClasses, onFirstHeadingChange, onLineMarkerPositionChanged, @@ -37,6 +37,7 @@ export const DocumentRenderPane: React.FC = ({ const [tocAst, setTocAst] = useState() const { width } = useResizeObserver(rendererReference ? { ref: rendererReference } : undefined) const realWidth = width || 0 + const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content) return (
= ({
= ({ - content, scrollState, wide, onFirstHeadingChange, @@ -15,16 +16,16 @@ export const ScrollingDocumentRenderPane: React.FC { + const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content) const renderer = useRef(null) const [lineMarks, setLineMarks] = useState() - const contentLineCount = useMemo(() => content.split('\n').length, [content]) + const contentLineCount = useMemo(() => markdownContent.split('\n').length, [markdownContent]) useScrollToLineMark(scrollState, lineMarks, contentLineCount, renderer) const userScroll = useUserScroll(lineMarks, renderer, onScroll) return ( { const { t } = useTranslation() const untitledNote = t('editor.untitledNote') - const [markdownContent, setMarkdownContent] = useState(editorTestContent) + const markdownContent = useSelector((state: ApplicationState) => state.documentContent.content) const isWide = useMedia({ minWidth: 576 }) const [documentTitle, setDocumentTitle] = useState(untitledNote) const noteMetadata = useRef() @@ -49,6 +50,10 @@ export const Editor: React.FC = () => { rendererScrollState: { firstLineInView: 1, scrolledPercentage: 0 } })) + useEffect(() => { + setDocumentContent(editorTestContent) + }, []) + const updateDocumentTitle = useCallback(() => { const noteTitle = extractNoteTitle(untitledNote, noteMetadata.current, firstHeading.current) setDocumentTitle(noteTitle) @@ -71,9 +76,9 @@ export const Editor: React.FC = () => { const before = results[1] const after = results[3] lines[lineInMarkdown] = `${before}[${checked ? 'x' : ' '}]${after}` - setMarkdownContent(lines.join('\n')) + setDocumentContent(lines.join('\n')) } - }, [markdownContent, setMarkdownContent]) + }, [markdownContent]) useViewModeShortcuts() @@ -105,12 +110,12 @@ export const Editor: React.FC = () => {
- setMarkdownContent(newContent)}/> + setMarkdownContent(content)} + onContentChange={setDocumentContent} content={markdownContent} scrollState={scrollState.editorScrollState} onScroll={onEditorScroll} @@ -120,7 +125,6 @@ export const Editor: React.FC = () => { showRight={editorMode === EditorMode.PREVIEW || (editorMode === EditorMode.BOTH)} right={ { scrollSource.current = ScrollSource.RENDERER diff --git a/src/components/pad-view-only/pad-view-only.tsx b/src/components/pad-view-only/pad-view-only.tsx index 18ca94e66..1b3be3074 100644 --- a/src/components/pad-view-only/pad-view-only.tsx +++ b/src/components/pad-view-only/pad-view-only.tsx @@ -5,6 +5,7 @@ import { useParams } from 'react-router' import { getNote, Note } from '../../api/notes' import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode' 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 { MotdBanner } from '../common/motd-banner/motd-banner' import { ShowIf } from '../common/show-if/show-if' @@ -42,7 +43,10 @@ export const PadViewOnly: React.FC = () => { useEffect(() => { getNote(id) - .then(note => setNoteData(note)) + .then(note => { + setNoteData(note) + setDocumentContent(note.content) + }) .catch(() => setError(true)) .finally(() => setLoading(false)) }, [id]) @@ -80,7 +84,6 @@ export const PadViewOnly: React.FC = () => { viewCount={noteData?.viewcount ?? 0} /> false} diff --git a/src/redux/document-content/methods.ts b/src/redux/document-content/methods.ts new file mode 100644 index 000000000..f1c13a795 --- /dev/null +++ b/src/redux/document-content/methods.ts @@ -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) +} diff --git a/src/redux/document-content/reducers.ts b/src/redux/document-content/reducers.ts new file mode 100644 index 000000000..d030f1ed1 --- /dev/null +++ b/src/redux/document-content/reducers.ts @@ -0,0 +1,15 @@ +import { Reducer } from 'redux' +import { DocumentContent, DocumentContentAction, DocumentContentActionType, SetDocumentContentAction } from './types' + +export const initialState: DocumentContent = { + content: '' +} + +export const DocumentContentReducer: Reducer = (state: DocumentContent = initialState, action: DocumentContentAction) => { + switch (action.type) { + case DocumentContentActionType.SET_DOCUMENT_CONTENT: + return { content: (action as SetDocumentContentAction).content } + default: + return state + } +} diff --git a/src/redux/document-content/types.ts b/src/redux/document-content/types.ts new file mode 100644 index 000000000..6d87139b2 --- /dev/null +++ b/src/redux/document-content/types.ts @@ -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 { + type: DocumentContentActionType +} + +export interface SetDocumentContentAction extends DocumentContentAction { + content: string +} diff --git a/src/redux/index.ts b/src/redux/index.ts index 610521999..0bf9f8f70 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -7,6 +7,8 @@ import { BannerState } from './banner/types' import { ConfigReducer } from './config/reducers' import { DarkModeConfigReducer } from './dark-mode/reducers' import { DarkModeConfig } from './dark-mode/types' +import { DocumentContentReducer } from './document-content/reducers' +import { DocumentContent } from './document-content/types' import { EditorConfigReducer } from './editor/reducers' import { EditorConfig } from './editor/types' import { UserReducer } from './user/reducers' @@ -19,6 +21,7 @@ export interface ApplicationState { apiUrl: ApiUrlObject; editorConfig: EditorConfig; darkMode: DarkModeConfig; + documentContent: DocumentContent; } export const allReducers: Reducer = combineReducers({ @@ -27,7 +30,8 @@ export const allReducers: Reducer = combineReducers