";
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
@@ -239,6 +263,54 @@ export const RichTextEditor = (props: RichTextEditorProps) => {
contentRef.current?.focus();
};
+ // Replaces all headers in selection with p tags
+ const removeHeadersFromSelection = () => {
+ const { range } = getSelectionContext();
+ if (!range) return;
+
+ // Get the common ancestor container of the selection range
+ const commonAncestorContainer = range?.commonAncestorContainer;
+
+ if (
+ commonAncestorContainer &&
+ commonAncestorContainer?.nodeType === Node.ELEMENT_NODE
+ ) {
+ // Get all header elements (h1, h2, h3, etc.) within the common ancestor container
+ const headers = (commonAncestorContainer as Element).querySelectorAll(
+ "h1, h2, h3, h4, h5, h6"
+ );
+
+ // Iterate over each header and replace it with a paragraph
+ headers.forEach((header) => {
+ // Check if the header is the only child of a paragraph
+ if (
+ header.parentNode.nodeName === "P" &&
+ header.parentNode.childNodes.length >= 1
+ ) {
+ // Replace the entire paragraph with a new paragraph containing the header's content
+ const newP = document.createElement("p");
+ newP.innerHTML = header.innerHTML;
+ header.parentNode.parentNode.replaceChild(newP, header.parentNode);
+ } else {
+ // If the header is not the only child of a paragraph, just replace the header with a paragraph
+ const p = document.createElement("p");
+ p.innerHTML = header.innerHTML;
+ header.parentNode.replaceChild(p, header);
+ }
+ });
+ } else {
+ // Single line selection where parent element is header
+ const parentElement = getParentElement({ contentRef });
+ if (["H1", "H2", "H3"].includes(parentElement?.nodeName)) {
+ const rangeContents = range.extractContents();
+ parentElement.replaceWith(rangeContents);
+ }
+ }
+
+ // Update content after replacing headers
+ handleChange();
+ };
+
// Handle when a key is pressed.
const handleKeyDown = (e: React.KeyboardEvent) => {
const { currentNode } = getSelectionContext();
@@ -332,9 +404,49 @@ export const RichTextEditor = (props: RichTextEditorProps) => {
+
+
diff --git a/src/assets/FontSizeIconDisabled.svg b/src/assets/FontSizeIconDisabled.svg
new file mode 100644
index 0000000..1a84d48
--- /dev/null
+++ b/src/assets/FontSizeIconDisabled.svg
@@ -0,0 +1,3 @@
+
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 3485d51..7cc0d72 100644
--- a/src/assets/index.ts
+++ b/src/assets/index.ts
@@ -4,3 +4,6 @@ export { default as NumberedListIcon } from "./NumberedListIcon.svg";
export { default as NumberedListIconDisabled } from "./NumberedListIconDisabled.svg";
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";
diff --git a/src/styles.css b/src/styles.css
index 77a2d88..93f526e 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -71,9 +71,22 @@
margin: 0;
}
-.rt-content h3 {
+.rt-content h1, h2, h3 {
margin-top: 0;
margin-bottom: 8px;
+ font-weight: 600;
+}
+
+.rt-content h1 {
+ font-size: xx-large;
+}
+
+.rt-content h2 {
+ font-size: x-large;
+}
+
+.rt-content h3 {
+ font-size: large;
}
.rt-button[disabled] {
@@ -102,3 +115,14 @@
display: inline;
background-color: yellow;
}
+
+.rt-headerPopover {
+ position: relative;
+ display: flex;
+ top: 25%;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ padding: 1px;
+ width: fit-content;
+ background-color: #fff;
+}
diff --git a/src/types.ts b/src/types.ts
index 47a31ec..7829b25 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -6,7 +6,7 @@ export type RichTextEditorProps = {
readOnly?: boolean;
};
-export type HTMLTag = "B" | "I" | "S" | "H3" | "P" | "LI" | "DIV";
+export type HTMLTag = "B" | "I" | "S" | "H1" | "H2" | "H3" | "P" | "LI" | "DIV";
export type SelectionContext = {
currentNode: Node | null;