diff --git a/demo/Menu.tsx b/demo/Menu.tsx
new file mode 100644
index 00000000..2c719d5d
--- /dev/null
+++ b/demo/Menu.tsx
@@ -0,0 +1,73 @@
+import { toggleMark } from "prosemirror-commands";
+import type { MarkType } from "prosemirror-model";
+import type { EditorState } from "prosemirror-state";
+import React, { ReactNode } from "react";
+
+import { useEditorEventCallback, useEditorState } from "../src/index.js";
+
+// lifted from:
+// https://github.com/ProseMirror/prosemirror-example-setup/blob/master/src/menu.ts#L58
+function isMarkActive(mark: MarkType, state: EditorState): boolean {
+ const { from, $from, to, empty } = state.selection;
+ return empty
+ ? !!mark.isInSet(state.storedMarks || $from.marks())
+ : state.doc.rangeHasMark(from, to, mark);
+}
+
+export function Button(props: {
+ className?: string;
+ children?: ReactNode;
+ isActive: boolean;
+ title: string;
+ onClick: () => void;
+}) {
+ return (
+
+ );
+}
+
+export default function Menu() {
+ const state = useEditorState();
+
+ const toggleBold = useEditorEventCallback((view) => {
+ if (!view) return;
+ const toggleBoldMark = toggleMark(view.state.schema.marks["strong"]);
+ toggleBoldMark(view.state, view.dispatch, view);
+ });
+
+ const toggleItalic = useEditorEventCallback((view) => {
+ if (!view) return;
+ const toggleItalicMark = toggleMark(view.state.schema.marks["em"]);
+ toggleItalicMark(view.state, view.dispatch, view);
+ });
+
+ return (
+
+
+
+
+ );
+}
diff --git a/demo/main.css b/demo/main.css
index f419ecc3..fc18c35e 100644
--- a/demo/main.css
+++ b/demo/main.css
@@ -1,5 +1,5 @@
.ProseMirror {
- border: thin solid black;
+ border: thin solid #ccc;
border-radius: 0.25rem;
padding: 1rem;
outline: none;
@@ -11,3 +11,46 @@ main {
width: 80%;
max-width: 700px;
}
+
+.menu {
+ display: flex;
+ margin-bottom: 5px;
+}
+
+.button {
+ cursor: pointer;
+ width: 36px;
+ height: 36px;
+ margin-right: 5px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: none;
+ border: thin solid #ccc;
+ border-radius: 0.25rem;
+ color: black;
+ background-color: white;
+
+ &[aria-pressed="true"] {
+ background-color: #ddd;
+ color: blue;
+ }
+}
+
+.button.bold {
+ font-weight: 700;
+}
+
+.button.italic {
+ font-style: italic;
+}
+
+.visually-hidden {
+ clip: rect(0 0 0 0);
+ clip-path: inset(50%);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+}
diff --git a/demo/main.tsx b/demo/main.tsx
index 10d94ad9..34ff93c1 100644
--- a/demo/main.tsx
+++ b/demo/main.tsx
@@ -5,6 +5,7 @@ import {
liftEmptyBlock,
newlineInCode,
splitBlock,
+ toggleMark,
} from "prosemirror-commands";
import { keymap } from "prosemirror-keymap";
import { Schema } from "prosemirror-model";
@@ -22,6 +23,7 @@ import {
import { ReactNodeViewConstructor } from "../src/nodeViews/createReactNodeViewConstructor.js";
import { react } from "../src/plugins/react.js";
+import Menu from "./Menu.js";
import "./main.css";
const schema = new Schema({
@@ -32,6 +34,20 @@ const schema = new Schema({
list_item: { content: "paragraph+", toDOM: () => ["li", 0] },
text: { group: "inline" },
},
+ marks: {
+ em: {
+ parseDOM: [{ tag: "em" }],
+ toDOM() {
+ return ["em", 0];
+ },
+ },
+ strong: {
+ parseDOM: [{ tag: "strong" }],
+ toDOM() {
+ return ["strong", 0];
+ },
+ },
+ },
});
const editorState = EditorState.create({
@@ -54,6 +70,8 @@ const editorState = EditorState.create({
),
"Shift-Enter": baseKeymap.Enter,
"Shift-Tab": liftListItem(schema.nodes.list_item),
+ "Mod-b": toggleMark(schema.marks["strong"]),
+ "Mod-i": toggleMark(schema.marks["em"]),
}),
react(),
],
@@ -101,13 +119,13 @@ function DemoEditor() {
return (
- React ProseMirror Demo
+
{renderNodeViews()}