diff --git a/package.json b/package.json
index c4d005d..98574c8 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.53.0",
+ "react-icons": "^5.3.0",
"react-intersection-observer": "^9.13.1",
"rehype-external-links": "^3.0.0",
"rehype-parse": "^9.0.0",
diff --git a/src/app/(site)/(editor)/write/ToolbarPlugin/index.tsx b/src/app/(site)/(editor)/write/ToolbarPlugin/index.tsx
new file mode 100644
index 0000000..6bde2b8
--- /dev/null
+++ b/src/app/(site)/(editor)/write/ToolbarPlugin/index.tsx
@@ -0,0 +1,153 @@
+import React, { useCallback, useEffect, useRef, useState } from "react";
+
+import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
+import { mergeRegister } from "@lexical/utils";
+import {
+ $getSelection,
+ $isRangeSelection,
+ CAN_REDO_COMMAND,
+ CAN_UNDO_COMMAND,
+ FORMAT_ELEMENT_COMMAND,
+ FORMAT_TEXT_COMMAND,
+ REDO_COMMAND,
+ SELECTION_CHANGE_COMMAND,
+ UNDO_COMMAND,
+} from "lexical";
+import {
+ AiOutlineAlignCenter,
+ AiOutlineAlignLeft,
+ AiOutlineAlignRight,
+ AiOutlineBold,
+ AiOutlineItalic,
+ AiOutlineRedo,
+ AiOutlineStrikethrough,
+ AiOutlineUnderline,
+ AiOutlineUndo,
+} from "react-icons/ai";
+
+const LowPriority = 1;
+
+export const ToolbarPlugin: React.FC = () => {
+ const [editor] = useLexicalComposerContext();
+ const toolbarRef = useRef(null);
+ const [canUndo, setCanUndo] = useState(false);
+ const [canRedo, setCanRedo] = useState(false);
+ const [isBold, setIsBold] = useState(false);
+ const [isItalic, setIsItalic] = useState(false);
+ const [isUnderline, setIsUnderline] = useState(false);
+ const [isStrikethrough, setIsStrikethrough] = useState(false);
+ const [align, setAlign] = useState<"left" | "center" | "right" | "">("");
+
+ const $updateToolbar = useCallback(() => {
+ const selection = $getSelection();
+ if ($isRangeSelection(selection)) {
+ // Update text format
+ setIsBold(selection.hasFormat("bold"));
+ setIsItalic(selection.hasFormat("italic"));
+ setIsUnderline(selection.hasFormat("underline"));
+ setIsStrikethrough(selection.hasFormat("strikethrough"));
+ }
+ }, []);
+
+ useEffect(() => {
+ return mergeRegister(
+ editor.registerUpdateListener(({ editorState }) => {
+ editorState.read(() => {
+ $updateToolbar();
+ });
+ }),
+ editor.registerCommand(
+ SELECTION_CHANGE_COMMAND,
+ (_payload, _newEditor) => {
+ $updateToolbar();
+ return false;
+ },
+ LowPriority,
+ ),
+ editor.registerCommand(
+ CAN_UNDO_COMMAND,
+ (payload) => {
+ setCanUndo(payload);
+ return false;
+ },
+ LowPriority,
+ ),
+ editor.registerCommand(
+ CAN_REDO_COMMAND,
+ (payload) => {
+ setCanRedo(payload);
+ return false;
+ },
+ LowPriority,
+ ),
+ );
+ }, [editor, $updateToolbar]);
+ return (
+
+
{
+ editor.dispatchCommand(UNDO_COMMAND, undefined);
+ }}
+ />
+ {
+ editor.dispatchCommand(REDO_COMMAND, undefined);
+ }}
+ />
+ {
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold");
+ }}
+ />
+ {
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic");
+ }}
+ />
+ {
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "underline");
+ }}
+ />
+ {
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough");
+ }}
+ />
+ {
+ editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "left");
+ }}
+ />
+ {
+ editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "center");
+ }}
+ />
+ {
+ editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "right");
+ }}
+ />
+
+ );
+};
diff --git a/src/app/(site)/(editor)/write/page.tsx b/src/app/(site)/(editor)/write/page.tsx
index 219904d..9731f82 100644
--- a/src/app/(site)/(editor)/write/page.tsx
+++ b/src/app/(site)/(editor)/write/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useEffect } from "react";
+import React, { useEffect, useState } from "react";
import { CodeNode } from "@lexical/code";
import { AutoLinkNode, LinkNode } from "@lexical/link";
@@ -27,6 +27,10 @@ import {
} from "lexical";
import { type Link, type Root } from "mdast";
+import { ToolbarPlugin } from "./ToolbarPlugin";
+
+import "./style.css";
+
// LexicalOnChangePlugin!
function onChange(editorState: EditorState) {
editorState.read(() => {
@@ -61,45 +65,69 @@ function onError(error: unknown) {
}
export default function Editor() {
+ const [isClient, setIsClient] = useState(false);
+ const [showToolbar, setShowToolbar] = useState(false);
+
+ useEffect(() => {
+ setIsClient(true);
+ }, []);
+
+ if (!isClient) {
+ return null;
+ }
+
const initialConfig: InitialConfigType = {
namespace: "NoteStackEditor",
// theme: getTheme("dark"),
editorState: () =>
- $convertFromMarkdownString(
- "# Don't write anything here yet!",
- TRANSFORMERS,
- ),
+ $convertFromMarkdownString("# Start writing!", TRANSFORMERS),
nodes: [
HorizontalRuleNode,
- // // BannerNode,
+ // BannerNode,
HeadingNode,
// ImageNode,
- // QuoteNode,
- // CodeNode,
- // ListNode,
- // ListItemNode,
- // LinkNode,
- // AutoLinkNode,
+ QuoteNode,
+ CodeNode,
+ ListNode,
+ ListItemNode,
+ LinkNode,
+ AutoLinkNode,
],
onError,
};
return (
-
-
-
- }
- ErrorBoundary={LexicalErrorBoundary}
- />
-
-
- {/* */}
-
-
-
-
-
+
+
+
+ {/* Toolbar Toggle Button */}
+
+
+ {/* Rich Text Editor */}
+
+
}
+ placeholder={
Enter some text...
}
+ ErrorBoundary={LexicalErrorBoundary}
+ />
+
+
+ {/* Toolbar Plugin */}
+ {showToolbar &&
}
+
+ {/* Plugins */}
+
+ {/*
*/}
+
+
+
+
+
+
);
}
diff --git a/src/app/(site)/(editor)/write/style.css b/src/app/(site)/(editor)/write/style.css
new file mode 100644
index 0000000..715b1c0
--- /dev/null
+++ b/src/app/(site)/(editor)/write/style.css
@@ -0,0 +1,7 @@
+ .editor-placeholder {
+ color: #888;
+ position: absolute;
+ top: 0.5rem;
+ left: 0.5rem;
+ pointer-events: none;
+ }
\ No newline at end of file