I’m working on a block editor for fairly straightfoward email templates. The templates have a restricted set of elements. As a starting point I’m creating a custom schema. The initial state of a document should be a template node with a child row node and the row should have a column node. Fow now I’m allowing the column node to contain block+, and the only node in the block group is the paragraph. It appears that the nodes are all created as expected:
Inspecting the dom however shows only the template div rendered out as a tag. A cursor does appear in the editor but when typing no character is displayed, and the dom is not updated.
I need the nodes to all be rendered out to the DOM so that the user can interact with them. Additionally is there a way to dump the node structure that is less painful that using the debug tools inspector?
Any help or insight would be appreciated.
==schema.ts==
import { AstTransformer, NotExpr } from "@angular/compiler";
import {Schema, NodeSpec, MarkSpec, DOMOutputSpec} from "prosemirror-model"
const pDOM: DOMOutputSpec = ["p", 0], blockquoteDOM: DOMOutputSpec = ["blockquote", 0],
hrDOM: DOMOutputSpec = ["hr"], brDOM: DOMOutputSpec = ["br"];
export type textAlignValues="center"|"left"|"right"|"justify";
const templateNode:NodeSpec={
attrs:{
style:{default:"width:100%;min-height:200px;display:flex;justify-content:center;background-color:#FAFAFA;"},
class:{default:"wcTemplate"}
},
content:"row+",//At least one row must be present
group:"template",
toDOM:node=>{
const attrs = {
style:node.attrs.style,
class:node.attrs.class
};
return ['div',attrs]
},
parseDOM:[
{
tag:'div.wcTemplate',
getAttrs:(dom:HTMLElement|string)=>{
if(typeof(dom) === "string"){
return false;
};
return {
style:dom.getAttribute('style'),
class:dom.getAttribute('class')
};
}
}
]
};
const rowNode:NodeSpec={
attrs:{
rowtype:{default:0},
style:{default:"display:flex;justify-content:center;width:500px;padding:5px;background-color:red;flex-direction:row;flex-wrap:wrap;"},
class:{default:"wcLayoutRow"}
},
content:"column+",
group:"row",
toDom:node=>{
const attrs = {
style:node.attrs.style,
class:node.attrs.class
};
return ['div',attrs];
},
parseDOM:[
{
tag:"div.wcLayoutRow",
getAttrs:(dom:HTMLElement|string)=>{
if(typeof(dom)==="string"){
return false;
}
return {
style:dom.getAttribute("style"),
class:dom.getAttribute("class")
}
}
}
]
};
const colNode:NodeSpec={
attrs:{
colwidth:{default:1},
style:{default:"display:flex;justify-content:center;width:500px;padding:5px;background-color:blue;flex-direction:column;flex:1;"},
class:{default:"wcLayoutColumn"}
},
content:"block+",
group:"column",
toDom:node=>{
const attrs = {
style:node.attrs.style,
class:node.attrs.class
};
return ['div',attrs];
},
parseDOM:[
{
tag:"div.wcLayoutColumn",
getAttrs:(dom:HTMLElement|string)=>{
if(typeof(dom)==="string"){
return false;
}
return {
style:dom.getAttribute("style"),
class:dom.getAttribute("class")
}
}
}
]
}
/// A plain paragraph textblock. Represented in the DOM
/// as a `<p>` element.
const paragraph:NodeSpec= {
content: "inline*",
group: "block",
parseDOM: [{tag: "p"}],
toDOM() { return pDOM }
};
/// The text node.
const text:NodeSpec ={
group: "inline"
};
const docNode:NodeSpec={
content:"template"
}
const nodes = {
doc:docNode,
templateNode,
rowNode,
colNode,
paragraph,
text
};
const marks = {};//schema.spec.marks;
export const wcSchema = new Schema({
nodes,
marks
});
===emaileditor.component.ts=======
import { AfterViewInit, Component, ViewChild,OnInit, Input } from '@angular/core';
import { EditorState,Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { Schema, DOMParser,DOMSerializer } from 'prosemirror-model';
import { schema } from 'prosemirror-schema-basic';
import { addListNodes } from 'prosemirror-schema-list';
import { exampleSetup } from 'prosemirror-example-setup';
import {wcSchema} from './schema';
@Component({
selector: 'app-emaileditor',
templateUrl: './emaileditor.component.html',
styleUrls: ['./emaileditor.component.css']
})
export class EmaileditorComponent implements OnInit,AfterViewInit {
@ViewChild('editor') editor;
@ViewChild('content') content;
@ViewChild('display') display:HTMLParagraphElement|undefined;
public docvalue:string|null="<p>test</p>";
constructor() { }
private view:EditorView|undefined;
ngOnInit(): void {
}
ngAfterViewInit(): void {
// Mix the nodes from prosemirror-schema-list into the basic schema to
// create a schema with list support.
this.view = new EditorView(this.editor.nativeElement, {
state: EditorState.create({doc: DOMParser.fromSchema(wcSchema).parse(this.content.nativeElement), plugins: exampleSetup({ schema: wcSchema })}),
dispatchTransaction:(tr:Transaction)=>{
console.log("Document size went from ", tr.before.content.size," to ",tr.doc.content.size);
if(this.view){
let newState = this.view?.state.apply(tr);
this.view.updateState(newState);
}
this.docvalue=this.toHTML()??'';
}
});
//const row=wcSchema.nodes.rowNode.create();
//const tr = this.view.state.tr.insert(this.view.state.selection.from, wcSchema.node("rowNode"));
//const rowstate = this.view.state.apply(tr);
//this.view.updateState(rowstate);
}
//Utilies
public toHTML():string|null{
let ret:string|null=null;
if(this.view)
{
var node = this.view.state.doc;//.nodeAt(0);
if(node){
const fragment = DOMSerializer.fromSchema(wcSchema).serializeFragment(node.content);
const div = document.createElement('div');
div.appendChild(fragment);
ret=div.innerHTML;
}
}
return ret;
}
}