From 239024cdc37e7ff7f5d9b52171a21819d586d840 Mon Sep 17 00:00:00 2001 From: BradyMitch Date: Mon, 20 Nov 2023 14:31:49 -0800 Subject: [PATCH 1/3] Added header size options --- src/RichTextEditor.tsx | 102 +++++++++++++++++++++------- src/assets/FontSizeIcon.svg | 3 + src/assets/FontSizeIconDisabled.svg | 3 + src/assets/index.ts | 2 + src/styles.css | 26 ++++++- src/types.ts | 2 +- 6 files changed, 113 insertions(+), 25 deletions(-) create mode 100644 src/assets/FontSizeIcon.svg create mode 100644 src/assets/FontSizeIconDisabled.svg diff --git a/src/RichTextEditor.tsx b/src/RichTextEditor.tsx index dc59bae..1bd2bd7 100644 --- a/src/RichTextEditor.tsx +++ b/src/RichTextEditor.tsx @@ -1,5 +1,7 @@ import "./styles.css"; import { + FontSizeIcon, + FontSizeIconDisabled, HighlighterIcon, HighlighterIconDisabled, ListIcon, @@ -7,7 +9,7 @@ import { NumberedListIcon, NumberedListIconDisabled, } from "./assets"; -import React, { useRef, useEffect } from "react"; +import React, { useRef, useEffect, useState } from "react"; import { HTMLTag, RichTextEditorProps } from "./types"; import { getParentElement, @@ -56,6 +58,15 @@ export const RichTextEditor = (props: RichTextEditorProps) => { } }; + // State to manage popover visibility + const [showHeaderPopover, setShowHeaderPopover] = useState(false); + + // Function to handle header style change and close popover + const handleHeaderStyleChange = (size) => { + toggleHeaderStyle(size); + setShowHeaderPopover(false); // Hide popover + }; + const toggleStyle = (tag: HTMLTag, className?: string) => { const { selection, range } = getSelectionContext(); const styledParentElement = getParentElement({ @@ -117,6 +128,17 @@ export const RichTextEditor = (props: RichTextEditorProps) => { element.appendChild(range?.extractContents()); range?.insertNode(element); } + + // Remove any parent header sizes + if ( + tag.startsWith("H") && + ["H1", "H2", "H3"].includes( + getParentElement({ contentRef })?.nodeName + ) + ) { + getParentElement({ contentRef }).replaceWith(element); + range.setEndAfter(element); + } } // Update content @@ -129,12 +151,13 @@ export const RichTextEditor = (props: RichTextEditorProps) => { selection?.addRange(range); } } + // Maintain focus. contentRef.current?.focus(); }; - // Function to toggle the header (h3) style - const toggleHeaderStyle = () => { + // Function to toggle the header style + const toggleHeaderStyle = (size: "H1" | "H2" | "H3") => { const { currentNode, range } = getSelectionContext(); const selectedText = range?.toString() ?? ""; const currentElement = @@ -149,35 +172,35 @@ export const RichTextEditor = (props: RichTextEditorProps) => { currentElement.outerHTML === "


"; if (selectedText.trim() === "" && isEmptyOrBr) { - // Clear the line and replace with an h3 containing a zero-width space - const h3 = document.createElement("h3"); - h3.innerHTML = "\u200B"; // Zero-width space + // Clear the line and replace with a header containing a zero-width space + const header = document.createElement(size); + header.innerHTML = "\u200B"; // Zero-width space if (currentElement) { - // If the current element is a P that contains only a BR, replace it entirely with the H3 + // If the current element is a P that contains only a BR, replace it entirely with the header if (currentElement.tagName === "P") { - currentElement.replaceWith(h3); + currentElement.replaceWith(header); } else { - // For other cases, just clear the innerHTML and set it to the H3 + // For other cases, just clear the innerHTML and set it to the header currentElement.innerHTML = ""; - currentElement.appendChild(h3); + currentElement.appendChild(header); } } else if (contentRef.current) { - // If there's no current element, append the H3 to the contentRef - contentRef.current.appendChild(h3); + // If there's no current element, append the header to the contentRef + contentRef.current.appendChild(header); } - // Set the cursor inside the new h3 element - setCursorAtStartOfElement(h3); + // Set the cursor inside the new header element + setCursorAtStartOfElement(header); } else if (selectedText.trim() === "") { - // No selection and current line is not empty: insert a blank h3 at the end - const h3 = document.createElement("h3"); - h3.innerHTML = "\u200B"; // Zero-width space - contentRef.current?.appendChild(h3); - setCursorAtStartOfElement(h3); + // No selection and current line is not empty: insert a blank header at the end + const header = document.createElement(size); + header.innerHTML = "\u200B"; // Zero-width space + contentRef.current?.appendChild(header); + setCursorAtStartOfElement(header); } else { - // There is selected text: toggle the h3 tag on the selection - toggleStyle("H3"); + // There is selected text: toggle the header tag on the selection + toggleStyle(size); } // Update content @@ -332,9 +355,42 @@ export const RichTextEditor = (props: RichTextEditorProps) => { + + + + )} + )} diff --git a/src/assets/TextIcon.svg b/src/assets/TextIcon.svg new file mode 100644 index 0000000..dbf52cf --- /dev/null +++ b/src/assets/TextIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/index.ts b/src/assets/index.ts index b958cf1..7cc0d72 100644 --- a/src/assets/index.ts +++ b/src/assets/index.ts @@ -6,3 +6,4 @@ export { default as HighlighterIcon } from "./HighlighterIcon.svg"; export { default as HighlighterIconDisabled } from "./HighlighterIconDisabled.svg"; export { default as FontSizeIcon } from "./FontSizeIcon.svg"; export { default as FontSizeIconDisabled } from "./FontSizeIconDisabled.svg"; +export { default as TextIcon } from "./TextIcon.svg"; From 6a573e50861064b988464dcf902bb1368db6c6a6 Mon Sep 17 00:00:00 2001 From: BradyMitch Date: Mon, 20 Nov 2023 16:08:15 -0800 Subject: [PATCH 3/3] Adjusted sizing --- src/RichTextEditor.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/RichTextEditor.tsx b/src/RichTextEditor.tsx index 1ab3578..77e2120 100644 --- a/src/RichTextEditor.tsx +++ b/src/RichTextEditor.tsx @@ -440,6 +440,7 @@ export const RichTextEditor = (props: RichTextEditorProps) => {