JSFiddle - React, Tailwind, and code Playground
by gryzzly
HTML
<body>
<div>
<prosemirror-editor
change-event="foo"
html="
<p>Lorem ipsum dolor sit amet, <a href='/this'>consectetur</a> adipiscing elit. Etiam efficitur, diam eu hendrerit dapibus, turpis lorem convallis felis, nec volutpat elit enim nec orci. Aliquam eget cursus nulla. Maecenas quis mauris turpis. Nullam id faucibus ex. Nulla cursus velit nibh, sit amet maximus dolor pulvinar non. Quisque ut efficitur odio. Phasellus vitae justo a purus semper imperdiet.</p>
<p>Nunc <a href='/this'>cursus</a>, odio nec egestas eleifend, dui enim porttitor dui, quis bibendum arcu felis eget ante. Donec vulputate massa velit, nec tempus dolor pulvinar non. Sed ultrices velit et tempus fermentum. Morbi interdum id est sit amet efficitur. Suspendisse venenatis in augue nec commodo. Ut vehicula eros vitae leo commodo cursus. Mauris viverra sodales massa, a bibendum nisl malesuada ultrices. Morbi pulvinar urna nec justo pharetra eleifend. Nunc aliquet diam malesuada dui placerat venenatis. Sed at libero rutrum, aliquam turpis eget, imperdiet nisl.</p>
</p>"
></prosemirror-editor>
</div>
</body>
<script>
document.querySelector("prosemirror-editor").addEventListener("foo", (e) => {
console.log(e.detail.html);
console.log(e.detail.json);
});
</script>
<script async type="module" src="https://esm.sh/[email protected]"></script>
<script type="importmap">
{
"imports": {
"prosemirror-model": "https://esm.sh/*[email protected]",
"orderedmap": "https://esm.sh/*[email protected]",
"prosemirror-schema-basic": "https://esm.sh/*[email protected]",
"prosemirror-state": "https://esm.sh/*[email protected]",
"prosemirror-transform": "https://esm.sh/*[email protected]",
"prosemirror-view": "https://esm.sh/*[email protected]",
"rope-sequence": "https://esm.sh/*[email protected]",
"prosemirror-history":...
CSS
/* Own
*/
* {
margin: 0;
padding: 0;
}
p {
margin-bottom: 0.5em;
}
/* ProseMirror
*/
.ProseMirror {
position: relative;
}
.ProseMirror {
word-wrap: break-word;
white-space: pre-wrap;
white-space: break-spaces;
-webkit-font-variant-ligatures: none;
font-variant-ligatures: none;
font-feature-settings: "liga" 0; /* the above doesn't seem to work in Edge */
}
.ProseMirror pre {
white-space: pre-wrap;
}
.ProseMirror li {
position: relative;
}
.ProseMirror-hideselection *::selection {
background: transparent;
}
.ProseMirror-hideselection *::-moz-selection {
background: transparent;
}
.ProseMirror-hideselection {
caret-color: transparent;
}
/* See https://github.com/ProseMirror/prosemirror/issues/1421#issuecomment-1759320191 */
.ProseMirror [draggable][contenteditable="false"] {
user-select: text;
}
.ProseMirror-selectednode {
outline: 2px solid #8cf;
}
/* Make sure li selections wrap around markers */
li.ProseMirror-selectednode {
outline: none;
}
li.ProseMirror-selectednode:after {
content: "";
position: absolute;
left: -32px;
right: -2px;
top: -2px;
bottom: -2px;
border: 2px solid #8cf;
pointer-events: none;
}
/* Protect against generic img rules */
img.ProseMirror-separator {
display: inline !important;
border: none !important;
margin: 0 !important;
}
.ProseMirror.virtual-cursor-enabled {
/* Hide the native cursor */
caret-color: transparent;
}
.ProseMirror-focused {
/* Color of the virtual cursor */
--prosemirror-virtual-cursor-color: blue;
}
.ProseMirror .prosemirror-virtual-cursor {
position: absolute;
cursor: text;
pointer-events: none;
transform: translate(-1px);
user-select: none;
-webkit-user-select: none;
border-left: 3px solid var(--prosemirror-virtual-cursor-color);
}
.ProseMirror...
JavaScript
// There is a name collision between prosemirror-model export `DOMParser` and
// the native constructor that we use to parse `html` attribute string value
// to DOM, so we import the prosemirror parser as an alias "PMDOMParser"
import {
Schema,
DOMParser as PMDOMParser,
DOMSerializer,
} from "prosemirror-model";
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { undo, redo, history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { baseKeymap } from "prosemirror-commands";
const schema = new Schema({
nodes: {
doc: { content: "block+" },
paragraph: {
content: "text*",
group: "block",
parseDOM: [{ tag: "p" }],
toDOM() {
return ["p", 0];
},
},
text: { group: "inline" },
},
marks: {
link: {
attrs: {
href: {},
title: { default: null },
},
inclusive: false,
parseDOM: [
{
tag: "a[href]",
getAttrs(dom) {
return {
href: dom.getAttribute("href"),
title: dom.getAttribute("title"),
};
},
},
],
toDOM(node) {
return ["a", node.attrs, 0];
},
},
},
});
class ProseMirrorEditor extends HTMLElement {
constructor() {
super();
this.editorView = null;
}
connectedCallback() {
this.innerHTML = `<div id="editor"></div>`;
const editorElement = this.querySelector("#editor");
const initialContent = this.getAttribute("html");
const customChangeEventName = this.getAttribute("change-event");
// default empty paragraph
let doc = schema.node("doc", null, [schema.node("paragraph")]);
if (initialContent) {
// html -> DOM
const contentElement = new DOMParser().parseFromString(
initialContent,
"text/html"
).body;
// DOM -> Prosemirror doc
doc =...