a tool for shared writing and social publishing
1import { AtUri } from "@atproto/api";
2import { Schema, Node, MarkSpec, NodeSpec } from "prosemirror-model";
3import { marks } from "prosemirror-schema-basic";
4import { theme } from "tailwind.config";
5import {
6 isDocumentCollection,
7 isPublicationCollection,
8} from "src/utils/collectionHelpers";
9
10let baseSchema = {
11 marks: {
12 strong: marks.strong,
13 em: marks.em,
14 code: {
15 parseDOM: [
16 {
17 tag: "code",
18 },
19 ],
20
21 toDOM() {
22 return ["code", { class: "inline-code" }, 0];
23 },
24 } as MarkSpec,
25 underline: {
26 parseDOM: [
27 { tag: "u" },
28 {
29 style: "text-decoration=underline",
30 },
31 {
32 style: "text-decoration=none",
33 clearMark: (m) => m.type.name == "underline",
34 },
35 ],
36 toDOM() {
37 return ["u", { class: "underline" }, 0];
38 },
39 } as MarkSpec,
40 strikethrough: {
41 parseDOM: [
42 { tag: "s" },
43 { tag: "del" },
44 {
45 style: `text-decoration=line-through`,
46 },
47 {
48 style: "text-decoration=none",
49 clearMark: (m) => m.type.name == "strikethrough",
50 },
51 ],
52 toDOM() {
53 return [
54 "s",
55 {
56 style: `text-decoration-color: ${theme.colors.tertiary};`,
57 },
58 0,
59 ];
60 },
61 } as MarkSpec,
62 highlight: {
63 attrs: {
64 color: {
65 default: "1",
66 },
67 },
68 parseDOM: [
69 {
70 tag: "span.highlight",
71 getAttrs(dom: HTMLElement) {
72 return {
73 color: dom.getAttribute("data-color"),
74 };
75 },
76 },
77 ],
78 toDOM(node) {
79 let { color } = node.attrs;
80 return [
81 "span",
82 {
83 class: "highlight",
84 "data-color": color,
85 style: `background-color: ${color === "1" ? theme.colors["highlight-1"] : color === "2" ? theme.colors["highlight-2"] : theme.colors["highlight-3"]}`,
86 },
87 0,
88 ];
89 },
90 } as MarkSpec,
91 link: {
92 attrs: {
93 href: {},
94 },
95 inclusive: false,
96 parseDOM: [
97 {
98 tag: "a[href]",
99 getAttrs(dom: HTMLElement) {
100 return {
101 href: dom.getAttribute("href"),
102 };
103 },
104 },
105 ],
106 toDOM(node) {
107 let { href } = node.attrs;
108 return ["a", { href, target: "_blank" }, 0];
109 },
110 } as MarkSpec,
111 },
112 nodes: {
113 doc: { content: "block" },
114 paragraph: {
115 content: "inline*",
116 group: "block",
117 parseDOM: [{ tag: "p" }],
118 toDOM: () => ["p", 0] as const,
119 },
120 text: {
121 group: "inline",
122 },
123 hard_break: {
124 group: "inline",
125 inline: true,
126 selectable: false,
127 parseDOM: [{ tag: "br" }],
128 toDOM: () => ["br"] as const,
129 },
130 atMention: {
131 attrs: {
132 atURI: {},
133 text: { default: "" },
134 },
135 group: "inline",
136 inline: true,
137 atom: true,
138 selectable: true,
139 draggable: true,
140 parseDOM: [
141 {
142 tag: "span.atMention",
143 getAttrs(dom: HTMLElement) {
144 return {
145 atURI: dom.getAttribute("data-at-uri"),
146 text: dom.textContent || "",
147 };
148 },
149 },
150 ],
151 toDOM(node) {
152 // NOTE: This rendering should match the AtMentionLink component in
153 // components/AtMentionLink.tsx. If you update one, update the other.
154 let className = "atMention mention";
155 let aturi = new AtUri(node.attrs.atURI);
156 if (isPublicationCollection(aturi.collection))
157 className += " font-bold";
158 if (isDocumentCollection(aturi.collection)) className += " italic";
159
160 // For publications and documents, show icon
161 if (
162 isPublicationCollection(aturi.collection) ||
163 isDocumentCollection(aturi.collection)
164 ) {
165 return [
166 "span",
167 {
168 class: className,
169 "data-at-uri": node.attrs.atURI,
170 },
171 [
172 "img",
173 {
174 src: `/api/pub_icon?at_uri=${encodeURIComponent(node.attrs.atURI)}`,
175 class:
176 "inline-block w-4 h-4 rounded-full mt-[3px] mr-1 align-text-top",
177 alt: "",
178 width: "16",
179 height: "16",
180 loading: "lazy",
181 },
182 ],
183 node.attrs.text,
184 ];
185 }
186
187 return [
188 "span",
189 {
190 class: className,
191 "data-at-uri": node.attrs.atURI,
192 },
193 node.attrs.text,
194 ];
195 },
196 } as NodeSpec,
197 didMention: {
198 attrs: {
199 did: {},
200 text: { default: "" },
201 },
202 group: "inline",
203 inline: true,
204 atom: true,
205 selectable: true,
206 draggable: true,
207 parseDOM: [
208 {
209 tag: "span.didMention",
210 getAttrs(dom: HTMLElement) {
211 return {
212 did: dom.getAttribute("data-did"),
213 text: dom.textContent || "",
214 };
215 },
216 },
217 ],
218 toDOM(node) {
219 return [
220 "span",
221 {
222 class: "didMention mention",
223 "data-did": node.attrs.did,
224 },
225 node.attrs.text,
226 ];
227 },
228 } as NodeSpec,
229 },
230};
231export const schema = new Schema(baseSchema);
232
233export const multiBlockSchema = new Schema({
234 marks: baseSchema.marks,
235 nodes: { ...baseSchema.nodes, doc: { content: "block+" } },
236});