@firecms/core
Version:
Awesome Firebase/Firestore-based headless open-source CMS
241 lines (237 loc) • 8.5 kB
text/typescript
import { Schema, NodeSpec, MarkSpec } from "prosemirror-model";
import { tableNodes } from "prosemirror-tables";
const marks: { [key: string]: MarkSpec } = {
link: {
attrs: {
href: {},
title: { default: null },
target: { default: "_blank" },
},
inclusive: false,
parseDOM: [
{
tag: "a[href]",
getAttrs(dom: HTMLElement | string) {
if (typeof dom === "string") return false;
return {
href: dom.getAttribute("href"),
title: dom.getAttribute("title"),
target: dom.getAttribute("target") || "_blank",
};
},
},
],
toDOM(node) {
let { href, title, target } = node.attrs;
return ["a", { href, title, target, class: "text-surface-700 dark:text-surface-accent-200 underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer" }, 0];
},
},
bold: {
parseDOM: [
{ tag: "strong" },
{ tag: "b", getAttrs: (node: HTMLElement | string) => typeof node !== "string" && node.style.fontWeight !== "normal" && null },
{ style: "font-weight=400", clearMark: m => m.type.name === "bold" },
{ style: "font-weight", getAttrs: (value: string | HTMLElement) => typeof value === "string" && /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null },
],
toDOM() { return ["strong", 0]; },
},
italic: {
parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }],
toDOM() { return ["em", 0]; },
},
strike: {
parseDOM: [{ tag: "s" }, { tag: "del" }, { tag: "strike" }, { style: "text-decoration=line-through" }],
toDOM() { return ["s", 0]; },
},
underline: {
parseDOM: [{ tag: "u" }, { style: "text-decoration=underline" }],
toDOM() { return ["u", 0]; },
},
code: {
parseDOM: [{ tag: "code" }],
toDOM() { return ["code", { class: "rounded-md bg-surface-accent-50 dark:bg-surface-700 px-1.5 py-1 font-mono font-medium", spellcheck: "false" }, 0]; },
},
textStyle: {
attrs: { color: { default: null } },
parseDOM: [
{
style: "color",
getAttrs: (value: string | HTMLElement) => {
if (typeof value === "string") return { color: value };
return false;
},
},
],
toDOM(mark) {
let style = "";
if (mark.attrs.color) style += `color: ${mark.attrs.color};`;
return ["span", { style }, 0];
},
},
highlight: {
attrs: { color: { default: null } },
parseDOM: [
{
tag: "mark",
getAttrs: (dom: HTMLElement | string) => {
if (typeof dom === "string") return false;
return { color: dom.style.backgroundColor || dom.getAttribute("data-color") };
}
},
],
toDOM(mark) {
return ["mark", mark.attrs.color ? { style: `background-color: ${mark.attrs.color}; color: inherit;`, "data-color": mark.attrs.color } : {}, 0];
},
},
};
const nodes: { [key: string]: NodeSpec } = {
doc: {
content: "block+",
},
paragraph: {
content: "inline*",
group: "block",
parseDOM: [{ tag: "p" }],
toDOM() { return ["p", 0]; },
},
text: {
group: "inline",
},
blockquote: {
content: "block+",
group: "block",
defining: true,
parseDOM: [{ tag: "blockquote" }],
toDOM() { return ["blockquote", { class: "border-l-4 border-primary" }, 0]; },
},
heading: {
attrs: { level: { default: 1 } },
content: "inline*",
group: "block",
defining: true,
parseDOM: [
{ tag: "h1", attrs: { level: 1 } },
{ tag: "h2", attrs: { level: 2 } },
{ tag: "h3", attrs: { level: 3 } },
{ tag: "h4", attrs: { level: 4 } },
{ tag: "h5", attrs: { level: 5 } },
{ tag: "h6", attrs: { level: 6 } },
],
toDOM(node) { return ["h" + node.attrs.level, 0]; },
},
horizontal_rule: {
group: "block",
parseDOM: [{ tag: "hr" }],
toDOM() { return ["hr", { class: "mt-4 mb-6 border-t border-solid border-gray-200 dark:border-gray-800" }]; },
},
code_block: {
content: "text*",
marks: "",
group: "block",
code: true,
defining: true,
attrs: { language: { default: null } },
parseDOM: [
{ tag: "pre", preserveWhitespace: "full" },
],
toDOM(node) { return ["pre", { class: "rounded bg-blue-50 dark:bg-surface-700 border border-solid border-gray-200 dark:border-gray-800 p-5 font-mono font-medium" }, ["code", 0]]; },
},
image: {
inline: false,
group: "block",
attrs: {
src: {},
alt: { default: null },
title: { default: null },
},
draggable: true,
parseDOM: [
{
tag: "img[src]",
getAttrs(dom: HTMLElement | string) {
if (typeof dom === "string") return false;
return {
src: dom.getAttribute("src"),
title: dom.getAttribute("title"),
alt: dom.getAttribute("alt"),
};
},
},
],
toDOM(node) {
let { src, alt, title } = node.attrs;
return ["img", { src, alt, title, class: "rounded-lg max-w-full !m-0" }];
},
},
bullet_list: {
content: "list_item+",
group: "block",
parseDOM: [{ tag: "ul" }],
toDOM() { return ["ul", { class: "list-disc list-outside leading-3 -mt-2" }, 0]; },
},
ordered_list: {
content: "list_item+",
group: "block",
attrs: { order: { default: 1 } },
parseDOM: [
{
tag: "ol",
getAttrs(dom: HTMLElement | string) {
if (typeof dom === "string") return false;
return { order: dom.hasAttribute("start") ? +dom.getAttribute("start")! : 1 };
},
},
],
toDOM(node) {
return node.attrs.order === 1 ? ["ol", { class: "list-decimal list-outside leading-3 -mt-2" }, 0] : ["ol", { start: node.attrs.order, class: "list-decimal list-outside leading-3 -mt-2" }, 0];
},
},
list_item: {
content: "paragraph block*",
parseDOM: [{ tag: "li" }],
toDOM() { return ["li", { class: "leading-normal -mb-2" }, 0]; },
defining: true,
},
task_list: {
group: "block",
content: "task_item+",
parseDOM: [{ tag: 'ul[data-type="taskList"]' }],
toDOM() { return ["ul", { "data-type": "taskList", class: "not-prose" }, 0]; },
},
task_item: {
content: "paragraph block*",
defining: true,
attrs: { checked: { default: false } },
parseDOM: [
{
tag: 'li[data-type="taskItem"]',
getAttrs(dom: HTMLElement | string) {
if (typeof dom === "string") return false;
return { checked: dom.getAttribute("data-checked") === "true" };
},
},
],
toDOM(node) {
return ["li", { "data-type": "taskItem", "data-checked": node.attrs.checked ? "true" : "false", class: "flex items-start my-4" }, 0];
},
},
hard_break: {
inline: true,
group: "inline",
selectable: false,
parseDOM: [{ tag: "br" }],
toDOM() { return ["br"]; },
},
...tableNodes({
tableGroup: "block",
cellContent: "block+",
cellAttributes: {
background: {
default: null,
getFromDOM(dom: HTMLElement) { return dom.style.backgroundColor || null },
setDOMAttr(value: any, attrs: any) { if (value) attrs.style = (attrs.style || "") + `background-color: ${value};` }
}
}
})
};
export const schema = new Schema({ nodes, marks });