diff --git a/src/renderer/assets/styles/components/_editor.scss b/src/renderer/assets/styles/components/_editor.scss index 496b0bf0..353f3138 100644 --- a/src/renderer/assets/styles/components/_editor.scss +++ b/src/renderer/assets/styles/components/_editor.scss @@ -29,9 +29,18 @@ .editor-toolbar { @include background-color("background-hover"); + display: flex; + align-items: center; + justify-content: space-between; height: $input-height; } +.word-count { + @include color("text-faded"); + margin-right: $spacing-abs-medium; + line-height: 1; +} + // stylelint-disable selector-class-pattern // Fill editor height (and compensate for paragraph spacing between the title and text) diff --git a/src/renderer/components/elements/diary-editor/editor-toolbar/EditorToolbar.tsx b/src/renderer/components/elements/diary-editor/editor-toolbar/EditorToolbar.tsx deleted file mode 100644 index 8b8f649b..00000000 --- a/src/renderer/components/elements/diary-editor/editor-toolbar/EditorToolbar.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import "draft-js/dist/Draft.css"; - -import { EditorState, RichUtils } from "draft-js"; -import BoldIcon from "feather-icons/dist/icons/bold.svg"; -import ItalicIcon from "feather-icons/dist/icons/italic.svg"; -import UlIcon from "feather-icons/dist/icons/list.svg"; -import React, { PureComponent, ReactNode } from "react"; - -import OlIcon from "../../../../assets/icons/ordered-list.svg"; -import { translations } from "../../../../utils/i18n"; -import { iconProps } from "../../../../utils/icons"; - -const STROKE_WIDTH_DEFAULT = 2; -const STROKE_WIDTH_SELECTED = 3; - -export interface CustomProps { - onTextChange: (textEditorState: EditorState) => void; - textEditorState: EditorState; -} - -type Props = CustomProps; - -export default class EditorToolbar extends PureComponent { - constructor(props: Props) { - super(props); - - // Function bindings - this.onBoldClick = this.onBoldClick.bind(this); - this.onItalicClick = this.onItalicClick.bind(this); - this.onOlClick = this.onOlClick.bind(this); - this.onUlClick = this.onUlClick.bind(this); - } - - onBoldClick(): void { - const { onTextChange, textEditorState } = this.props; - - onTextChange(RichUtils.toggleInlineStyle(textEditorState, "BOLD")); - } - - onItalicClick(): void { - const { onTextChange, textEditorState } = this.props; - - onTextChange(RichUtils.toggleInlineStyle(textEditorState, "ITALIC")); - } - - onOlClick(): void { - const { onTextChange, textEditorState } = this.props; - - onTextChange(RichUtils.toggleBlockType(textEditorState, "ordered-list-item")); - } - - onUlClick(): void { - const { onTextChange, textEditorState } = this.props; - - onTextChange(RichUtils.toggleBlockType(textEditorState, "unordered-list-item")); - } - - render(): ReactNode { - const { textEditorState } = this.props; - - // Detect active inline/block styles - const inlineStyle = textEditorState.getCurrentInlineStyle(); - const blockType = RichUtils.getCurrentBlockType(textEditorState); - const isBold = inlineStyle.has("BOLD"); - const isItalic = inlineStyle.has("ITALIC"); - const isOl = blockType === "ordered-list-item"; - const isUl = blockType === "unordered-list-item"; - - return ( -
{ - e.preventDefault(); // Keep focus on editor when a button is clicked - }} - role="none" - > - - - - -
- ); - } -} diff --git a/src/renderer/components/elements/editor/editor-toolbar/editor-toolbar/EditorToolbar.tsx b/src/renderer/components/elements/editor/editor-toolbar/editor-toolbar/EditorToolbar.tsx new file mode 100644 index 00000000..80b69279 --- /dev/null +++ b/src/renderer/components/elements/editor/editor-toolbar/editor-toolbar/EditorToolbar.tsx @@ -0,0 +1,29 @@ +import { EditorState } from "draft-js"; +import React, { ReactElement } from "react"; + +import FormattingButtons from "../formatting-buttons/FormattingButtons"; +import WordCountWrapper from "../word-count/WordCountWrapper"; + +export interface CustomProps { + onTextChange: (textEditorState: EditorState) => void; + textEditorState: EditorState; +} + +type Props = CustomProps; + +export default function EditorToolbar(props: Props): ReactElement { + const { onTextChange, textEditorState } = props; + + return ( +
{ + e.preventDefault(); // Keep focus on editor when a button is clicked + }} + role="none" + > + + +
+ ); +} diff --git a/src/renderer/components/elements/editor/editor-toolbar/formatting-buttons/FormattingButtons.tsx b/src/renderer/components/elements/editor/editor-toolbar/formatting-buttons/FormattingButtons.tsx new file mode 100644 index 00000000..6f6f40cc --- /dev/null +++ b/src/renderer/components/elements/editor/editor-toolbar/formatting-buttons/FormattingButtons.tsx @@ -0,0 +1,99 @@ +import { EditorState, RichUtils } from "draft-js"; +import BoldIcon from "feather-icons/dist/icons/bold.svg"; +import ItalicIcon from "feather-icons/dist/icons/italic.svg"; +import UlIcon from "feather-icons/dist/icons/list.svg"; +import React, { ReactElement } from "react"; + +import OlIcon from "../../../../../assets/icons/ordered-list.svg"; +import { translations } from "../../../../../utils/i18n"; +import { iconProps } from "../../../../../utils/icons"; + +const STROKE_WIDTH_DEFAULT = 2; +const STROKE_WIDTH_SELECTED = 3; + +export interface CustomProps { + onTextChange: (textEditorState: EditorState) => void; + textEditorState: EditorState; +} + +type Props = CustomProps; + +/** + * Toolbar buttons for changing the formatting of the diary entry (bold, italic, lists) + */ +export default function FormattingButtons(props: Props): ReactElement { + const { onTextChange, textEditorState } = props; + + const onBoldClick = (): void => { + onTextChange(RichUtils.toggleInlineStyle(textEditorState, "BOLD")); + }; + + const onItalicClick = (): void => { + onTextChange(RichUtils.toggleInlineStyle(textEditorState, "ITALIC")); + }; + + const onOlClick = (): void => { + onTextChange(RichUtils.toggleBlockType(textEditorState, "ordered-list-item")); + }; + + const onUlClick = (): void => { + onTextChange(RichUtils.toggleBlockType(textEditorState, "unordered-list-item")); + }; + + // Detect active inline/block styles + const inlineStyle = textEditorState.getCurrentInlineStyle(); + const blockType = RichUtils.getCurrentBlockType(textEditorState); + const isBold = inlineStyle.has("BOLD"); + const isItalic = inlineStyle.has("ITALIC"); + const isOl = blockType === "ordered-list-item"; + const isUl = blockType === "unordered-list-item"; + + return ( +
+ + + + +
+ ); +} diff --git a/src/renderer/components/elements/editor/editor-toolbar/word-count/WordCount.tsx b/src/renderer/components/elements/editor/editor-toolbar/word-count/WordCount.tsx new file mode 100644 index 00000000..80d7b985 --- /dev/null +++ b/src/renderer/components/elements/editor/editor-toolbar/word-count/WordCount.tsx @@ -0,0 +1,30 @@ +import React, { ReactElement } from "react"; +import countWords from "word-count"; + +import { toIndexDate } from "../../../../../utils/dateFormat"; + +export interface StateProps { + dateSelected: Date; + entries: Entries; +} + +type Props = StateProps; + +/** + * Text component which indicates the number of words/characters (varies per language) for the + * currently selected diary entry + */ +export default function WordCount(props: Props): ReactElement { + const { dateSelected, entries } = props; + + let wordCount = 0; + + const indexDate = toIndexDate(dateSelected); + + if (indexDate in entries) { + const entry = entries[indexDate]; + wordCount = countWords(`${entry.title ?? ""}\n${entry.text ?? ""}`); + } + + return

{wordCount}

; +} diff --git a/src/renderer/components/elements/editor/editor-toolbar/word-count/WordCountWrapper.tsx b/src/renderer/components/elements/editor/editor-toolbar/word-count/WordCountWrapper.tsx new file mode 100644 index 00000000..f5a867db --- /dev/null +++ b/src/renderer/components/elements/editor/editor-toolbar/word-count/WordCountWrapper.tsx @@ -0,0 +1,11 @@ +import { connect } from "react-redux"; + +import { RootState } from "../../../../../store/store"; +import WordCount, { StateProps } from "./WordCount"; + +const mapStateToProps = (state: RootState): StateProps => ({ + dateSelected: state.diary.dateSelected, + entries: state.file.entries, +}); + +export default connect(mapStateToProps)(WordCount); diff --git a/src/renderer/components/elements/diary-editor/editor/Editor.tsx b/src/renderer/components/elements/editor/editor/Editor.tsx similarity index 98% rename from src/renderer/components/elements/diary-editor/editor/Editor.tsx rename to src/renderer/components/elements/editor/editor/Editor.tsx index f58400ba..6351fdb3 100644 --- a/src/renderer/components/elements/diary-editor/editor/Editor.tsx +++ b/src/renderer/components/elements/editor/editor/Editor.tsx @@ -18,7 +18,7 @@ import React, { KeyboardEvent, PureComponent, ReactNode } from "react"; import { toIndexDate, toLocaleWeekday } from "../../../../utils/dateFormat"; import { translations } from "../../../../utils/i18n"; -import EditorToolbar from "../editor-toolbar/EditorToolbar"; +import EditorToolbar from "../editor-toolbar/editor-toolbar/EditorToolbar"; type DraftEditorCommandExtended = DraftEditorCommand | "enter"; diff --git a/src/renderer/components/elements/diary-editor/editor/EditorContainer.tsx b/src/renderer/components/elements/editor/editor/EditorContainer.tsx similarity index 100% rename from src/renderer/components/elements/diary-editor/editor/EditorContainer.tsx rename to src/renderer/components/elements/editor/editor/EditorContainer.tsx diff --git a/src/renderer/components/pages/diary/Diary.tsx b/src/renderer/components/pages/diary/Diary.tsx index b0af3aef..5c919a2b 100644 --- a/src/renderer/components/pages/diary/Diary.tsx +++ b/src/renderer/components/pages/diary/Diary.tsx @@ -1,6 +1,6 @@ import React, { FunctionComponent } from "react"; -import EditorContainer from "../../elements/diary-editor/editor/EditorContainer"; +import EditorContainer from "../../elements/editor/editor/EditorContainer"; import SidebarContainer from "../../elements/sidebar/sidebar/SidebarContainer"; const Diary: FunctionComponent<{}> = (): JSX.Element => (