@panneau/editor 0.0.0-stage → 5.0.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +129 -2
  2. package/dist/LinkDialog.d.ts +18 -0
  3. package/dist/LinkDialog.d.ts.map +1 -0
  4. package/dist/LinkDialog.js +220 -0
  5. package/dist/LinkDialog.js.map +1 -0
  6. package/dist/RichTextField.css +257 -0
  7. package/dist/RichTextField.d.ts +43 -0
  8. package/dist/RichTextField.d.ts.map +1 -0
  9. package/dist/RichTextField.js +361 -0
  10. package/dist/RichTextField.js.map +1 -0
  11. package/dist/RichTextField.module.js +28 -0
  12. package/dist/RichTextField.module.js.map +1 -0
  13. package/dist/Toolbar.d.ts +20 -0
  14. package/dist/Toolbar.d.ts.map +1 -0
  15. package/dist/Toolbar.js +203 -0
  16. package/dist/Toolbar.js.map +1 -0
  17. package/dist/buttons.d.ts +23 -0
  18. package/dist/buttons.d.ts.map +1 -0
  19. package/dist/buttons.js +117 -0
  20. package/dist/buttons.js.map +1 -0
  21. package/dist/extensions/attributes.d.ts +44 -0
  22. package/dist/extensions/attributes.d.ts.map +1 -0
  23. package/dist/extensions/attributes.js +138 -0
  24. package/dist/extensions/attributes.js.map +1 -0
  25. package/dist/extensions/index.d.ts +19 -0
  26. package/dist/extensions/index.d.ts.map +1 -0
  27. package/dist/extensions/index.js +96 -0
  28. package/dist/extensions/index.js.map +1 -0
  29. package/dist/extensions/marks.d.ts +4 -0
  30. package/dist/extensions/marks.d.ts.map +1 -0
  31. package/dist/extensions/marks.js +62 -0
  32. package/dist/extensions/marks.js.map +1 -0
  33. package/dist/extensions/paste.d.ts +32 -0
  34. package/dist/extensions/paste.d.ts.map +1 -0
  35. package/dist/extensions/paste.js +131 -0
  36. package/dist/extensions/paste.js.map +1 -0
  37. package/dist/extensions/raw.d.ts +23 -0
  38. package/dist/extensions/raw.d.ts.map +1 -0
  39. package/dist/extensions/raw.js +269 -0
  40. package/dist/extensions/raw.js.map +1 -0
  41. package/dist/html.d.ts +21 -0
  42. package/dist/html.d.ts.map +1 -0
  43. package/dist/html.js +51 -0
  44. package/dist/html.js.map +1 -0
  45. package/dist/index.d.ts +8 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +7 -0
  48. package/dist/plugin.d.ts +26 -0
  49. package/dist/plugin.d.ts.map +1 -0
  50. package/dist/plugin.js +46 -0
  51. package/dist/plugin.js.map +1 -0
  52. package/dist/settings.d.ts +22 -0
  53. package/dist/settings.d.ts.map +1 -0
  54. package/dist/settings.js +45 -0
  55. package/dist/settings.js.map +1 -0
  56. package/dist/styles.d.ts +16 -0
  57. package/dist/styles.d.ts.map +1 -0
  58. package/dist/styles.js +86 -0
  59. package/dist/styles.js.map +1 -0
  60. package/package.json +71 -5
@@ -0,0 +1,269 @@
1
+ import { CLIPBOARD_SLICE, COPIED_FROM, KEPT_HTML, readAttributes, withoutScriptAttributes } from "./attributes.js";
2
+ import { Mark, Node } from "@tiptap/core";
3
+ //#region src/extensions/raw.ts
4
+ /**
5
+ * Elements the field doesn't edit, kept as they were written: embedded media (`<figure
6
+ * class="media">` of CKEditor 5, iframes, scripts of social posts), tables, images, `<div>`.
7
+ */
8
+ var BLOCK_TAGS = [
9
+ "address",
10
+ "article",
11
+ "aside",
12
+ "center",
13
+ "details",
14
+ "div",
15
+ "dl",
16
+ "fieldset",
17
+ "figure",
18
+ "footer",
19
+ "form",
20
+ "header",
21
+ "nav",
22
+ "oembed",
23
+ "pre",
24
+ "section",
25
+ "style",
26
+ "table"
27
+ ];
28
+ /** Elements kept as they were written, in the text, or as blocks between paragraphs. */
29
+ var INLINE_TAGS = [
30
+ "audio",
31
+ "button",
32
+ "canvas",
33
+ "embed",
34
+ "iframe",
35
+ "img",
36
+ "input",
37
+ "math",
38
+ "noscript",
39
+ "object",
40
+ "picture",
41
+ "script",
42
+ "select",
43
+ "svg",
44
+ "textarea",
45
+ "video"
46
+ ];
47
+ /** Quotes that embed a post: they come with the script of their provider. */
48
+ var EMBED_QUOTES = [
49
+ "blockquote.twitter-tweet",
50
+ "blockquote.twitter-video",
51
+ "blockquote.instagram-media",
52
+ "blockquote.tiktok-embed",
53
+ "blockquote.imgur-embed-pub"
54
+ ];
55
+ /** Elements around text, kept with their attributes: `<span style="…">`, `<abbr title="…">`. */
56
+ var MARK_TAGS = [
57
+ "span",
58
+ "font",
59
+ "small",
60
+ "big",
61
+ "abbr",
62
+ "acronym",
63
+ "cite",
64
+ "q",
65
+ "dfn",
66
+ "time",
67
+ "mark",
68
+ "ins",
69
+ "kbd",
70
+ "samp",
71
+ "var",
72
+ "bdi",
73
+ "bdo",
74
+ "data",
75
+ "tt",
76
+ "a:not([href])"
77
+ ];
78
+ /** The elements of `MARK_TAGS`: what a kept mark can be written as. */
79
+ var MARK_NAMES = new Set(MARK_TAGS.map((tag) => tag.replace(/:.*$/, "")));
80
+ /**
81
+ * Where kept HTML goes when ProseMirror renders a node without its view: an empty element that
82
+ * holds the HTML in an attribute, where nothing runs. ProseMirror does that when the editor
83
+ * unmounts, with the editor still in the page. `restoreKeptHtml()` puts the HTML back, only in
84
+ * what the field writes: the value, and what it copies.
85
+ */
86
+ function placeholder(html, inline) {
87
+ return [inline ? "span" : "div", { [KEPT_HTML]: html }];
88
+ }
89
+ /**
90
+ * Replaces the placeholders of kept HTML with the HTML they hold, in content written outside the
91
+ * page: an inert document, or a fragment of one.
92
+ */
93
+ function restoreKeptHtml(root) {
94
+ root.querySelectorAll(`[${KEPT_HTML}]:empty`).forEach((element) => {
95
+ const template = element.ownerDocument.createElement("template");
96
+ template.innerHTML = element.getAttribute("data-panneau-html") ?? "";
97
+ element.replaceWith(...Array.from(template.content.childNodes));
98
+ });
99
+ }
100
+ /** `figure.media`: the element and its first class. */
101
+ function describe(element) {
102
+ const name = element.tagName.toLowerCase();
103
+ const className = element.classList[0];
104
+ return className !== void 0 ? `${name}.${className}` : name;
105
+ }
106
+ /** What kept HTML points to: the URL of its media or post, or the start of its text. */
107
+ function summarize(element) {
108
+ const source = [element, ...Array.from(element.querySelectorAll("*"))].map((candidate) => candidate.getAttribute("data-oembed-url") ?? candidate.getAttribute("url") ?? candidate.getAttribute("src") ?? candidate.getAttribute("cite") ?? candidate.getAttribute("href")).find((value) => value !== null && value !== "");
109
+ if (source !== void 0 && source !== null) return source;
110
+ const text = (element.textContent ?? "").replace(/\s+/g, " ").trim();
111
+ return text.length > 80 ? `${text.slice(0, 80)}…` : text;
112
+ }
113
+ /**
114
+ * Shows kept HTML in the editor without rendering it: its element and what it points to. Its
115
+ * markup only goes back to the value.
116
+ */
117
+ function createHtmlView(html, options, inline) {
118
+ const template = document.createElement("template");
119
+ template.innerHTML = html;
120
+ const element = template.content.firstElementChild;
121
+ const dom = document.createElement(inline ? "span" : "div");
122
+ dom.className = options.className;
123
+ dom.contentEditable = "false";
124
+ dom.setAttribute("role", "img");
125
+ const name = element !== null ? describe(element) : "";
126
+ const summary = element !== null ? summarize(element) : "";
127
+ dom.setAttribute("aria-label", [
128
+ options.label,
129
+ name,
130
+ summary
131
+ ].filter((part) => part !== "").join(", "));
132
+ const tag = document.createElement("code");
133
+ tag.textContent = name;
134
+ dom.append(tag);
135
+ if (summary !== "") {
136
+ const detail = document.createElement("span");
137
+ detail.textContent = summary;
138
+ dom.append(detail);
139
+ }
140
+ return { dom };
141
+ }
142
+ /** The HTML of an element as it was written, without what copying adds to it. */
143
+ function outerHtml(element) {
144
+ if (!element.hasAttribute("data-pm-slice") && !element.hasAttribute("data-panneau-copy")) return element.outerHTML;
145
+ const copy = element.cloneNode(true);
146
+ copy.removeAttribute(CLIPBOARD_SLICE);
147
+ copy.removeAttribute(COPIED_FROM);
148
+ return copy.outerHTML;
149
+ }
150
+ var htmlAttribute = { html: {
151
+ default: "",
152
+ rendered: false,
153
+ parseHTML: outerHtml
154
+ } };
155
+ /** A block of HTML that the field keeps as it was written. */
156
+ var HtmlBlock = Node.create({
157
+ name: "htmlBlock",
158
+ group: "block",
159
+ atom: true,
160
+ selectable: true,
161
+ draggable: true,
162
+ addOptions() {
163
+ return {
164
+ label: "HTML",
165
+ className: ""
166
+ };
167
+ },
168
+ addAttributes() {
169
+ return htmlAttribute;
170
+ },
171
+ parseHTML() {
172
+ return [
173
+ {
174
+ tag: EMBED_QUOTES.join(", "),
175
+ priority: 60
176
+ },
177
+ {
178
+ tag: BLOCK_TAGS.join(", "),
179
+ priority: 10
180
+ },
181
+ {
182
+ tag: INLINE_TAGS.join(", "),
183
+ priority: 11,
184
+ context: "doc/|blockquote/"
185
+ }
186
+ ];
187
+ },
188
+ renderHTML({ node }) {
189
+ return placeholder(String(node.attrs.html), false);
190
+ },
191
+ addNodeView() {
192
+ return ({ node }) => createHtmlView(String(node.attrs.html), this.options, false);
193
+ }
194
+ });
195
+ /** An element of HTML in the text that the field keeps as it was written: an image, an iframe. */
196
+ var HtmlInline = Node.create({
197
+ name: "htmlInline",
198
+ group: "inline",
199
+ inline: true,
200
+ atom: true,
201
+ selectable: true,
202
+ addOptions() {
203
+ return {
204
+ label: "HTML",
205
+ className: ""
206
+ };
207
+ },
208
+ addAttributes() {
209
+ return htmlAttribute;
210
+ },
211
+ parseHTML() {
212
+ return [{
213
+ tag: INLINE_TAGS.join(", "),
214
+ priority: 10
215
+ }, {
216
+ tag: MARK_TAGS.map((tag) => `${tag}:empty`).join(", "),
217
+ priority: 20
218
+ }];
219
+ },
220
+ renderHTML({ node }) {
221
+ return placeholder(String(node.attrs.html), true);
222
+ },
223
+ addNodeView() {
224
+ return ({ node }) => createHtmlView(String(node.attrs.html), this.options, true);
225
+ }
226
+ });
227
+ /**
228
+ * An element around text that the field keeps with its attributes: `<span style="…">`, `<abbr
229
+ * title="…">`, an anchor without `href`. A `<span>` without attributes is dropped, as browsers
230
+ * render it the same.
231
+ */
232
+ var HtmlMark = Mark.create({
233
+ name: "htmlMark",
234
+ excludes: "",
235
+ priority: 500,
236
+ addAttributes() {
237
+ return {
238
+ tag: {
239
+ default: "span",
240
+ rendered: false,
241
+ parseHTML: (element) => element.tagName.toLowerCase()
242
+ },
243
+ attributes: {
244
+ default: null,
245
+ rendered: false,
246
+ parseHTML: (element) => readAttributes(element)
247
+ }
248
+ };
249
+ },
250
+ parseHTML() {
251
+ return [{
252
+ tag: MARK_TAGS.join(", "),
253
+ priority: 10,
254
+ getAttrs: (element) => element.tagName === "SPAN" && readAttributes(element) === null ? false : null
255
+ }];
256
+ },
257
+ renderHTML({ mark }) {
258
+ const tag = String(mark.attrs.tag);
259
+ return [
260
+ MARK_NAMES.has(tag) ? tag : "span",
261
+ withoutScriptAttributes(mark.attrs.attributes),
262
+ 0
263
+ ];
264
+ }
265
+ });
266
+ //#endregion
267
+ export { HtmlBlock, HtmlInline, HtmlMark, restoreKeptHtml };
268
+
269
+ //# sourceMappingURL=raw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raw.js","names":["Mark","Node","DOMOutputSpec","CLIPBOARD_SLICE","COPIED_FROM","KEPT_HTML","readAttributes","withoutScriptAttributes","BLOCK_TAGS","INLINE_TAGS","EMBED_QUOTES","MARK_TAGS","MARK_NAMES","Set","map","tag","replace","HtmlNodeOptions","label","className","placeholder","html","inline","restoreKeptHtml","root","ParentNode","querySelectorAll","forEach","element","template","ownerDocument","createElement","innerHTML","getAttribute","replaceWith","Array","from","content","childNodes","describe","Element","name","tagName","toLowerCase","classList","undefined","summarize","source","candidate","find","value","text","textContent","trim","length","slice","createHtmlView","options","document","firstElementChild","dom","contentEditable","setAttribute","summary","filter","part","join","append","detail","outerHtml","HTMLElement","hasAttribute","outerHTML","copy","cloneNode","removeAttribute","htmlAttribute","default","rendered","parseHTML","HtmlBlock","create","group","atom","selectable","draggable","addOptions","addAttributes","priority","context","renderHTML","node","String","attrs","addNodeView","HtmlInline","HtmlMark","excludes","attributes","getAttrs","mark","has"],"sources":["../../src/extensions/raw.ts"],"sourcesContent":["import { Mark, Node } from '@tiptap/core';\nimport type { DOMOutputSpec } from '@tiptap/pm/model';\n\nimport {\n CLIPBOARD_SLICE,\n COPIED_FROM,\n KEPT_HTML,\n readAttributes,\n withoutScriptAttributes,\n} from './attributes';\n\n/**\n * Elements the field doesn't edit, kept as they were written: embedded media (`<figure\n * class=\"media\">` of CKEditor 5, iframes, scripts of social posts), tables, images, `<div>`.\n */\nconst BLOCK_TAGS = [\n 'address',\n 'article',\n 'aside',\n 'center',\n 'details',\n 'div',\n 'dl',\n 'fieldset',\n 'figure',\n 'footer',\n 'form',\n 'header',\n 'nav',\n 'oembed',\n 'pre',\n 'section',\n 'style',\n 'table',\n];\n\n/** Elements kept as they were written, in the text, or as blocks between paragraphs. */\nconst INLINE_TAGS = [\n 'audio',\n 'button',\n 'canvas',\n 'embed',\n 'iframe',\n 'img',\n 'input',\n 'math',\n 'noscript',\n 'object',\n 'picture',\n 'script',\n 'select',\n 'svg',\n 'textarea',\n 'video',\n];\n\n/** Quotes that embed a post: they come with the script of their provider. */\nconst EMBED_QUOTES = [\n 'blockquote.twitter-tweet',\n 'blockquote.twitter-video',\n 'blockquote.instagram-media',\n 'blockquote.tiktok-embed',\n 'blockquote.imgur-embed-pub',\n];\n\n/** Elements around text, kept with their attributes: `<span style=\"…\">`, `<abbr title=\"…\">`. */\nconst MARK_TAGS = [\n 'span',\n 'font',\n 'small',\n 'big',\n 'abbr',\n 'acronym',\n 'cite',\n 'q',\n 'dfn',\n 'time',\n 'mark',\n 'ins',\n 'kbd',\n 'samp',\n 'var',\n 'bdi',\n 'bdo',\n 'data',\n 'tt',\n 'a:not([href])',\n];\n\n/** The elements of `MARK_TAGS`: what a kept mark can be written as. */\nconst MARK_NAMES = new Set(MARK_TAGS.map((tag) => tag.replace(/:.*$/, '')));\n\nexport interface HtmlNodeOptions {\n /** Names kept HTML in the editor, for assistive technologies: \"HTML\". */\n label: string;\n /** Class name of the element that stands for kept HTML in the editor. */\n className: string;\n}\n\n/**\n * Where kept HTML goes when ProseMirror renders a node without its view: an empty element that\n * holds the HTML in an attribute, where nothing runs. ProseMirror does that when the editor\n * unmounts, with the editor still in the page. `restoreKeptHtml()` puts the HTML back, only in\n * what the field writes: the value, and what it copies.\n */\nfunction placeholder(html: string, inline: boolean): DOMOutputSpec {\n return [inline ? 'span' : 'div', { [KEPT_HTML]: html }];\n}\n\n/**\n * Replaces the placeholders of kept HTML with the HTML they hold, in content written outside the\n * page: an inert document, or a fragment of one.\n */\nexport function restoreKeptHtml(root: ParentNode): void {\n // Only the empty placeholders the field wrote: content never brings the attribute\n root.querySelectorAll(`[${KEPT_HTML}]:empty`).forEach((element) => {\n const template = element.ownerDocument.createElement('template');\n template.innerHTML = element.getAttribute(KEPT_HTML) ?? '';\n element.replaceWith(...Array.from(template.content.childNodes));\n });\n}\n\n/** `figure.media`: the element and its first class. */\nfunction describe(element: Element): string {\n const name = element.tagName.toLowerCase();\n const className = element.classList[0];\n return className !== undefined ? `${name}.${className}` : name;\n}\n\n/** What kept HTML points to: the URL of its media or post, or the start of its text. */\nfunction summarize(element: Element): string {\n const source = [element, ...Array.from(element.querySelectorAll('*'))]\n .map(\n (candidate) =>\n candidate.getAttribute('data-oembed-url') ??\n candidate.getAttribute('url') ??\n candidate.getAttribute('src') ??\n candidate.getAttribute('cite') ??\n candidate.getAttribute('href'),\n )\n .find((value) => value !== null && value !== '');\n if (source !== undefined && source !== null) {\n return source;\n }\n const text = (element.textContent ?? '').replace(/\\s+/g, ' ').trim();\n return text.length > 80 ? `${text.slice(0, 80)}…` : text;\n}\n\n/**\n * Shows kept HTML in the editor without rendering it: its element and what it points to. Its\n * markup only goes back to the value.\n */\nfunction createHtmlView(html: string, options: HtmlNodeOptions, inline: boolean) {\n const template = document.createElement('template');\n template.innerHTML = html;\n const element = template.content.firstElementChild;\n const dom = document.createElement(inline ? 'span' : 'div');\n dom.className = options.className;\n dom.contentEditable = 'false';\n dom.setAttribute('role', 'img');\n const name = element !== null ? describe(element) : '';\n const summary = element !== null ? summarize(element) : '';\n dom.setAttribute(\n 'aria-label',\n [options.label, name, summary].filter((part) => part !== '').join(', '),\n );\n const tag = document.createElement('code');\n tag.textContent = name;\n dom.append(tag);\n if (summary !== '') {\n const detail = document.createElement('span');\n detail.textContent = summary;\n dom.append(detail);\n }\n return { dom };\n}\n\n/** The HTML of an element as it was written, without what copying adds to it. */\nfunction outerHtml(element: HTMLElement): string {\n if (!element.hasAttribute(CLIPBOARD_SLICE) && !element.hasAttribute(COPIED_FROM)) {\n return element.outerHTML;\n }\n const copy = element.cloneNode(true) as HTMLElement;\n copy.removeAttribute(CLIPBOARD_SLICE);\n copy.removeAttribute(COPIED_FROM);\n return copy.outerHTML;\n}\n\nconst htmlAttribute = {\n html: {\n default: '',\n rendered: false,\n parseHTML: outerHtml,\n },\n};\n\n/** A block of HTML that the field keeps as it was written. */\nexport const HtmlBlock = Node.create<HtmlNodeOptions>({\n name: 'htmlBlock',\n group: 'block',\n atom: true,\n selectable: true,\n draggable: true,\n\n addOptions() {\n return { label: 'HTML', className: '' };\n },\n\n addAttributes() {\n return htmlAttribute;\n },\n\n parseHTML() {\n return [\n // Above the quotes of the field\n { tag: EMBED_QUOTES.join(', '), priority: 60 },\n // Below the elements of the field and of plugins\n { tag: BLOCK_TAGS.join(', '), priority: 10 },\n // An image or a video between paragraphs stays between them\n { tag: INLINE_TAGS.join(', '), priority: 11, context: 'doc/|blockquote/' },\n ];\n },\n\n renderHTML({ node }) {\n return placeholder(String(node.attrs.html), false);\n },\n\n addNodeView() {\n return ({ node }) => createHtmlView(String(node.attrs.html), this.options, false);\n },\n});\n\n/** An element of HTML in the text that the field keeps as it was written: an image, an iframe. */\nexport const HtmlInline = Node.create<HtmlNodeOptions>({\n name: 'htmlInline',\n group: 'inline',\n inline: true,\n atom: true,\n selectable: true,\n\n addOptions() {\n return { label: 'HTML', className: '' };\n },\n\n addAttributes() {\n return htmlAttribute;\n },\n\n parseHTML() {\n return [\n { tag: INLINE_TAGS.join(', '), priority: 10 },\n // Around no text, they can't be marks: an anchor, the logo of a plugin left out.\n { tag: MARK_TAGS.map((tag) => `${tag}:empty`).join(', '), priority: 20 },\n ];\n },\n\n renderHTML({ node }) {\n return placeholder(String(node.attrs.html), true);\n },\n\n addNodeView() {\n return ({ node }) => createHtmlView(String(node.attrs.html), this.options, true);\n },\n});\n\n/**\n * An element around text that the field keeps with its attributes: `<span style=\"…\">`, `<abbr\n * title=\"…\">`, an anchor without `href`. A `<span>` without attributes is dropped, as browsers\n * render it the same.\n */\nexport const HtmlMark = Mark.create({\n name: 'htmlMark',\n // Elements of several kinds nest: `<abbr><span>`.\n excludes: '',\n priority: 500,\n\n addAttributes() {\n return {\n tag: {\n default: 'span',\n rendered: false,\n parseHTML: (element: HTMLElement) => element.tagName.toLowerCase(),\n },\n attributes: {\n default: null,\n rendered: false,\n parseHTML: (element: HTMLElement) => readAttributes(element),\n },\n };\n },\n\n parseHTML() {\n return [\n {\n tag: MARK_TAGS.join(', '),\n priority: 10,\n getAttrs: (element: HTMLElement) =>\n element.tagName === 'SPAN' && readAttributes(element) === null ? false : null,\n },\n ];\n },\n\n renderHTML({ mark }) {\n const tag = String(mark.attrs.tag);\n return [\n MARK_NAMES.has(tag) ? tag : 'span',\n withoutScriptAttributes(mark.attrs.attributes),\n 0,\n ];\n },\n});\n"],"mappings":";;;;;;;AAeA,IAAMQ,aAAa;CACf;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAAO;;AAIX,IAAMC,cAAc;CAChB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAAO;;AAIX,IAAMC,eAAe;CACjB;CACA;CACA;CACA;CACA;AAA4B;;AAIhC,IAAMC,YAAY;CACd;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAAe;;AAInB,IAAMC,aAAa,IAAIC,IAAIF,UAAUG,KAAKC,QAAQA,IAAIC,QAAQ,QAAQ,EAAE,CAAC,CAAC;;;;;;;AAe1E,SAASI,YAAYC,MAAcC,QAAgC;CAC/D,OAAO,CAACA,SAAS,SAAS,OAAO,GAAGjB,YAAYgB,KAAK,CAAC;AAC1D;;;;;AAMA,SAAgBE,gBAAgBC,MAAwB;CAEpDA,KAAKE,iBAAiB,IAAIrB,UAAS,QAAS,CAAC,CAACsB,SAASC,YAAY;EAC/D,MAAMC,WAAWD,QAAQE,cAAcC,cAAc,UAAU;EAC/DF,SAASG,YAAYJ,QAAQK,aAAAA,mBAAsB,KAAK;EACxDL,QAAQM,YAAY,GAAGC,MAAMC,KAAKP,SAASQ,QAAQC,UAAU,CAAC;CAClE,CAAC;AACL;;AAGA,SAASC,SAASX,SAA0B;CACxC,MAAMa,OAAOb,QAAQc,QAAQC,YAAY;CACzC,MAAMxB,YAAYS,QAAQgB,UAAU;CACpC,OAAOzB,cAAc0B,KAAAA,IAAY,GAAGJ,KAAI,GAAItB,cAAcsB;AAC9D;;AAGA,SAASK,UAAUlB,SAA0B;CACzC,MAAMmB,SAAS,CAACnB,SAAS,GAAGO,MAAMC,KAAKR,QAAQF,iBAAiB,GAAG,CAAC,CAAC,CAAC,CACjEZ,KACIkC,cACGA,UAAUf,aAAa,iBAAiB,KACxCe,UAAUf,aAAa,KAAK,KAC5Be,UAAUf,aAAa,KAAK,KAC5Be,UAAUf,aAAa,MAAM,KAC7Be,UAAUf,aAAa,MAAM,CACrC,CAAC,CACAgB,MAAMC,UAAUA,UAAU,QAAQA,UAAU,EAAE;CACnD,IAAIH,WAAWF,KAAAA,KAAaE,WAAW,MACnC,OAAOA;CAEX,MAAMI,QAAQvB,QAAQwB,eAAe,GAAA,CAAIpC,QAAQ,QAAQ,GAAG,CAAC,CAACqC,KAAK;CACnE,OAAOF,KAAKG,SAAS,KAAK,GAAGH,KAAKI,MAAM,GAAG,EAAE,EAAC,KAAMJ;AACxD;;;;;AAMA,SAASK,eAAenC,MAAcoC,SAA0BnC,QAAiB;CAC7E,MAAMO,WAAW6B,SAAS3B,cAAc,UAAU;CAClDF,SAASG,YAAYX;CACrB,MAAMO,UAAUC,SAASQ,QAAQsB;CACjC,MAAMC,MAAMF,SAAS3B,cAAcT,SAAS,SAAS,KAAK;CAC1DsC,IAAIzC,YAAYsC,QAAQtC;CACxByC,IAAIC,kBAAkB;CACtBD,IAAIE,aAAa,QAAQ,KAAK;CAC9B,MAAMrB,OAAOb,YAAY,OAAOW,SAASX,OAAO,IAAI;CACpD,MAAMmC,UAAUnC,YAAY,OAAOkB,UAAUlB,OAAO,IAAI;CACxDgC,IAAIE,aACA,cACA;EAACL,QAAQvC;EAAOuB;EAAMsB;CAAO,CAAC,CAACC,QAAQC,SAASA,SAAS,EAAE,CAAC,CAACC,KAAK,IAAI,CAC1E;CACA,MAAMnD,MAAM2C,SAAS3B,cAAc,MAAM;CACzChB,IAAIqC,cAAcX;CAClBmB,IAAIO,OAAOpD,GAAG;CACd,IAAIgD,YAAY,IAAI;EAChB,MAAMK,SAASV,SAAS3B,cAAc,MAAM;EAC5CqC,OAAOhB,cAAcW;EACrBH,IAAIO,OAAOC,MAAM;CACrB;CACA,OAAO,EAAER,IAAI;AACjB;;AAGA,SAASS,UAAUzC,SAA8B;CAC7C,IAAI,CAACA,QAAQ2C,aAAAA,eAA4B,KAAK,CAAC3C,QAAQ2C,aAAAA,mBAAwB,GAC3E,OAAO3C,QAAQ4C;CAEnB,MAAMC,OAAO7C,QAAQ8C,UAAU,IAAI;CACnCD,KAAKE,gBAAgBxE,eAAe;CACpCsE,KAAKE,gBAAgBvE,WAAW;CAChC,OAAOqE,KAAKD;AAChB;AAEA,IAAMI,gBAAgB,EAClBvD,MAAM;CACFwD,SAAS;CACTC,UAAU;CACVC,WAAWV;AACf,EACJ;;AAGA,IAAaW,YAAY/E,KAAKgF,OAAwB;CAClDxC,MAAM;CACNyC,OAAO;CACPC,MAAM;CACNC,YAAY;CACZC,WAAW;CAEXC,aAAa;EACT,OAAO;GAAEpE,OAAO;GAAQC,WAAW;EAAG;CAC1C;CAEAoE,gBAAgB;EACZ,OAAOX;CACX;CAEAG,YAAY;EACR,OAAO;GAEH;IAAEhE,KAAKL,aAAawD,KAAK,IAAI;IAAGsB,UAAU;GAAG;GAE7C;IAAEzE,KAAKP,WAAW0D,KAAK,IAAI;IAAGsB,UAAU;GAAG;GAE3C;IAAEzE,KAAKN,YAAYyD,KAAK,IAAI;IAAGsB,UAAU;IAAIC,SAAS;GAAmB;EAAC;CAElF;CAEAC,WAAW,EAAEC,QAAQ;EACjB,OAAOvE,YAAYwE,OAAOD,KAAKE,MAAMxE,IAAI,GAAG,KAAK;CACrD;CAEAyE,cAAc;EACV,QAAQ,EAAEH,WAAWnC,eAAeoC,OAAOD,KAAKE,MAAMxE,IAAI,GAAG,KAAKoC,SAAS,KAAK;CACpF;AACJ,CAAC;;AAGD,IAAasC,aAAa9F,KAAKgF,OAAwB;CACnDxC,MAAM;CACNyC,OAAO;CACP5D,QAAQ;CACR6D,MAAM;CACNC,YAAY;CAEZE,aAAa;EACT,OAAO;GAAEpE,OAAO;GAAQC,WAAW;EAAG;CAC1C;CAEAoE,gBAAgB;EACZ,OAAOX;CACX;CAEAG,YAAY;EACR,OAAO,CACH;GAAEhE,KAAKN,YAAYyD,KAAK,IAAI;GAAGsB,UAAU;EAAG,GAE5C;GAAEzE,KAAKJ,UAAUG,KAAKC,QAAQ,GAAGA,IAAG,OAAQ,CAAC,CAACmD,KAAK,IAAI;GAAGsB,UAAU;EAAG,CAAC;CAEhF;CAEAE,WAAW,EAAEC,QAAQ;EACjB,OAAOvE,YAAYwE,OAAOD,KAAKE,MAAMxE,IAAI,GAAG,IAAI;CACpD;CAEAyE,cAAc;EACV,QAAQ,EAAEH,WAAWnC,eAAeoC,OAAOD,KAAKE,MAAMxE,IAAI,GAAG,KAAKoC,SAAS,IAAI;CACnF;AACJ,CAAC;;;;;;AAOD,IAAauC,WAAWhG,KAAKiF,OAAO;CAChCxC,MAAM;CAENwD,UAAU;CACVT,UAAU;CAEVD,gBAAgB;EACZ,OAAO;GACHxE,KAAK;IACD8D,SAAS;IACTC,UAAU;IACVC,YAAYnD,YAAyBA,QAAQc,QAAQC,YAAY;GACrE;GACAuD,YAAY;IACRrB,SAAS;IACTC,UAAU;IACVC,YAAYnD,YAAyBtB,eAAesB,OAAO;GAC/D;EACJ;CACJ;CAEAmD,YAAY;EACR,OAAO,CACH;GACIhE,KAAKJ,UAAUuD,KAAK,IAAI;GACxBsB,UAAU;GACVW,WAAWvE,YACPA,QAAQc,YAAY,UAAUpC,eAAesB,OAAO,MAAM,OAAO,QAAQ;EACjF,CAAC;CAET;CAEA8D,WAAW,EAAEU,QAAQ;EACjB,MAAMrF,MAAM6E,OAAOQ,KAAKP,MAAM9E,GAAG;EACjC,OAAO;GACHH,WAAWyF,IAAItF,GAAG,IAAIA,MAAM;GAC5BR,wBAAwB6F,KAAKP,MAAMK,UAAU;GAC7C;EAAC;CAET;AACJ,CAAC"}
package/dist/html.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { type AnyExtension } from '@tiptap/core';
2
+ import { type Node as ProseMirrorNode, type Schema } from '@tiptap/pm/model';
3
+ /**
4
+ * Reads HTML into a document of the schema. Unlike TipTap's `setContent()`, it keeps the
5
+ * whitespace of the elements it keeps as written (`<pre>`, embeds).
6
+ */
7
+ export declare function parseRichText(html: string | null, schema: Schema): ProseMirrorNode;
8
+ /** Whether a document has no content: one empty paragraph. */
9
+ export declare function isEmptyDocument(doc: ProseMirrorNode): boolean;
10
+ /**
11
+ * Writes a document as HTML, or `null` when it is empty. A list item with one paragraph, that
12
+ * wasn't written as a `<p>`, is written without it: `<li>Text</li>`.
13
+ */
14
+ export declare function serializeRichText(doc: ProseMirrorNode, schema: Schema): string | null;
15
+ /**
16
+ * The HTML that the rich text field saves for this HTML once its content is edited, without
17
+ * editing it: to check that existing content round-trips before a migration. `extensions` are
18
+ * those of the project's plugins. Needs a DOM (a browser, or jsdom).
19
+ */
20
+ export declare function normalizeRichText(html: string | null, extensions?: readonly AnyExtension[]): string | null;
21
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAa,MAAM,cAAc,CAAC;AAC5D,OAAO,EAGH,KAAK,IAAI,IAAI,eAAe,EAC5B,KAAK,MAAM,EACd,MAAM,kBAAkB,CAAC;AAM1B;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CAMlF;AAED,8DAA8D;AAC9D,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAQ7D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0BrF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,UAAU,GAAE,SAAS,YAAY,EAAO,GACzC,MAAM,GAAG,IAAI,CAGf"}
package/dist/html.js ADDED
@@ -0,0 +1,51 @@
1
+ import { IMPLICIT_PARAGRAPH } from "./extensions/attributes.js";
2
+ import { restoreKeptHtml } from "./extensions/raw.js";
3
+ import { createRichTextExtensions } from "./extensions/index.js";
4
+ import { getSchema } from "@tiptap/core";
5
+ import { DOMParser, DOMSerializer } from "@tiptap/pm/model";
6
+ //#region src/html.ts
7
+ /**
8
+ * Reads HTML into a document of the schema. Unlike TipTap's `setContent()`, it keeps the
9
+ * whitespace of the elements it keeps as written (`<pre>`, embeds).
10
+ */
11
+ function parseRichText(html, schema) {
12
+ const body = new window.DOMParser().parseFromString(`<!doctype html><body>${html ?? ""}</body>`, "text/html").body;
13
+ return DOMParser.fromSchema(schema).parse(body);
14
+ }
15
+ /** Whether a document has no content: one empty paragraph. */
16
+ function isEmptyDocument(doc) {
17
+ const first = doc.firstChild;
18
+ return doc.childCount === 1 && first !== null && first.type.name === "paragraph" && first.content.size === 0;
19
+ }
20
+ /**
21
+ * Writes a document as HTML, or `null` when it is empty. A list item with one paragraph, that
22
+ * wasn't written as a `<p>`, is written without it: `<li>Text</li>`.
23
+ */
24
+ function serializeRichText(doc, schema) {
25
+ if (isEmptyDocument(doc)) return null;
26
+ const inert = document.implementation.createHTMLDocument();
27
+ const container = inert.createElement("div");
28
+ container.append(DOMSerializer.fromSchema(schema).serializeFragment(doc.content, { document: inert }));
29
+ restoreKeptHtml(container);
30
+ container.querySelectorAll("li").forEach((item) => {
31
+ const [first, ...others] = Array.from(item.children);
32
+ if (first?.tagName === "P" && first.hasAttribute("data-panneau-implicit") && first.attributes.length === 1 && others.every((other) => other.tagName === "UL" || other.tagName === "OL")) first.replaceWith(...Array.from(first.childNodes));
33
+ });
34
+ container.querySelectorAll(`[${IMPLICIT_PARAGRAPH}]`).forEach((paragraph) => {
35
+ paragraph.removeAttribute(IMPLICIT_PARAGRAPH);
36
+ });
37
+ return container.innerHTML;
38
+ }
39
+ /**
40
+ * The HTML that the rich text field saves for this HTML once its content is edited, without
41
+ * editing it: to check that existing content round-trips before a migration. `extensions` are
42
+ * those of the project's plugins. Needs a DOM (a browser, or jsdom).
43
+ */
44
+ function normalizeRichText(html, extensions = []) {
45
+ const schema = getSchema(createRichTextExtensions({ extensions }));
46
+ return serializeRichText(parseRichText(html, schema), schema);
47
+ }
48
+ //#endregion
49
+ export { isEmptyDocument, normalizeRichText, parseRichText, serializeRichText };
50
+
51
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","names":["AnyExtension","getSchema","DOMParser","DOMSerializer","Node","ProseMirrorNode","Schema","IMPLICIT_PARAGRAPH","createRichTextExtensions","restoreKeptHtml","parseRichText","html","schema","body","window","parseFromString","fromSchema","parse","isEmptyDocument","doc","first","firstChild","childCount","type","name","content","size","serializeRichText","inert","document","implementation","createHTMLDocument","container","createElement","append","serializeFragment","querySelectorAll","forEach","item","others","Array","from","children","tagName","hasAttribute","attributes","length","every","other","replaceWith","childNodes","paragraph","removeAttribute","innerHTML","normalizeRichText","extensions"],"sources":["../src/html.ts"],"sourcesContent":["import { type AnyExtension, getSchema } from '@tiptap/core';\nimport {\n DOMParser,\n DOMSerializer,\n type Node as ProseMirrorNode,\n type Schema,\n} from '@tiptap/pm/model';\n\nimport { IMPLICIT_PARAGRAPH } from './extensions/attributes';\nimport { createRichTextExtensions } from './extensions/index';\nimport { restoreKeptHtml } from './extensions/raw';\n\n/**\n * Reads HTML into a document of the schema. Unlike TipTap's `setContent()`, it keeps the\n * whitespace of the elements it keeps as written (`<pre>`, embeds).\n */\nexport function parseRichText(html: string | null, schema: Schema): ProseMirrorNode {\n const body = new window.DOMParser().parseFromString(\n `<!doctype html><body>${html ?? ''}</body>`,\n 'text/html',\n ).body;\n return DOMParser.fromSchema(schema).parse(body);\n}\n\n/** Whether a document has no content: one empty paragraph. */\nexport function isEmptyDocument(doc: ProseMirrorNode): boolean {\n const first = doc.firstChild;\n return (\n doc.childCount === 1 &&\n first !== null &&\n first.type.name === 'paragraph' &&\n first.content.size === 0\n );\n}\n\n/**\n * Writes a document as HTML, or `null` when it is empty. A list item with one paragraph, that\n * wasn't written as a `<p>`, is written without it: `<li>Text</li>`.\n */\nexport function serializeRichText(doc: ProseMirrorNode, schema: Schema): string | null {\n if (isEmptyDocument(doc)) {\n return null;\n }\n // An inert document: images and iframes of the content don't load.\n const inert = document.implementation.createHTMLDocument();\n const container = inert.createElement('div');\n container.append(\n DOMSerializer.fromSchema(schema).serializeFragment(doc.content, { document: inert }),\n );\n restoreKeptHtml(container);\n container.querySelectorAll('li').forEach((item) => {\n const [first, ...others] = Array.from(item.children);\n if (\n first?.tagName === 'P' &&\n first.hasAttribute(IMPLICIT_PARAGRAPH) &&\n first.attributes.length === 1 &&\n others.every((other) => other.tagName === 'UL' || other.tagName === 'OL')\n ) {\n first.replaceWith(...Array.from(first.childNodes));\n }\n });\n container.querySelectorAll(`[${IMPLICIT_PARAGRAPH}]`).forEach((paragraph) => {\n paragraph.removeAttribute(IMPLICIT_PARAGRAPH);\n });\n return container.innerHTML;\n}\n\n/**\n * The HTML that the rich text field saves for this HTML once its content is edited, without\n * editing it: to check that existing content round-trips before a migration. `extensions` are\n * those of the project's plugins. Needs a DOM (a browser, or jsdom).\n */\nexport function normalizeRichText(\n html: string | null,\n extensions: readonly AnyExtension[] = [],\n): string | null {\n const schema = getSchema(createRichTextExtensions({ extensions }));\n return serializeRichText(parseRichText(html, schema), schema);\n}\n"],"mappings":";;;;;;;;;;AAgBA,SAAgBU,cAAcC,MAAqBC,QAAiC;CAChF,MAAMC,OAAO,IAAIC,OAAOZ,UAAU,CAAC,CAACa,gBAChC,wBAAwBJ,QAAQ,GAAE,UAClC,WACJ,CAAC,CAACE;CACF,OAAOX,UAAUc,WAAWJ,MAAM,CAAC,CAACK,MAAMJ,IAAI;AAClD;;AAGA,SAAgBK,gBAAgBC,KAA+B;CAC3D,MAAMC,QAAQD,IAAIE;CAClB,OACIF,IAAIG,eAAe,KACnBF,UAAU,QACVA,MAAMG,KAAKC,SAAS,eACpBJ,MAAMK,QAAQC,SAAS;AAE/B;;;;;AAMA,SAAgBC,kBAAkBR,KAAsBP,QAA+B;CACnF,IAAIM,gBAAgBC,GAAG,GACnB,OAAO;CAGX,MAAMS,QAAQC,SAASC,eAAeC,mBAAmB;CACzD,MAAMC,YAAYJ,MAAMK,cAAc,KAAK;CAC3CD,UAAUE,OACN/B,cAAca,WAAWJ,MAAM,CAAC,CAACuB,kBAAkBhB,IAAIM,SAAS,EAAEI,UAAUD,MAAM,CAAC,CACvF;CACAnB,gBAAgBuB,SAAS;CACzBA,UAAUI,iBAAiB,IAAI,CAAC,CAACC,SAASC,SAAS;EAC/C,MAAM,CAAClB,OAAO,GAAGmB,UAAUC,MAAMC,KAAKH,KAAKI,QAAQ;EACnD,IACItB,OAAOuB,YAAY,OACnBvB,MAAMwB,aAAAA,uBAA+B,KACrCxB,MAAMyB,WAAWC,WAAW,KAC5BP,OAAOQ,OAAOC,UAAUA,MAAML,YAAY,QAAQK,MAAML,YAAY,IAAI,GAExEvB,MAAM6B,YAAY,GAAGT,MAAMC,KAAKrB,MAAM8B,UAAU,CAAC;CAEzD,CAAC;CACDlB,UAAUI,iBAAiB,IAAI7B,mBAAkB,EAAG,CAAC,CAAC8B,SAASc,cAAc;EACzEA,UAAUC,gBAAgB7C,kBAAkB;CAChD,CAAC;CACD,OAAOyB,UAAUqB;AACrB;;;;;;AAOA,SAAgBC,kBACZ3C,MACA4C,aAAsC,CAAA,GACzB;CACb,MAAM3C,SAASX,UAAUO,yBAAyB,EAAE+C,WAAW,CAAC,CAAC;CACjE,OAAO5B,kBAAkBjB,cAAcC,MAAMC,MAAM,GAAGA,MAAM;AAChE"}
@@ -0,0 +1,8 @@
1
+ export { RichTextField, RichTextFieldEntry, type RichTextFieldProps } from './RichTextField.js';
2
+ export { type RichTextButton, builtInButtons } from './buttons.js';
3
+ export { type RichTextExtensionsOptions, createRichTextExtensions } from './extensions/index.js';
4
+ export type { HtmlNodeOptions } from './extensions/raw.js';
5
+ export { normalizeRichText } from './html.js';
6
+ export { type EditorPlugin, type EditorPluginOptions, createEditorPlugin, editorPlugin, } from './plugin.js';
7
+ export { DEFAULT_HEADINGS, DEFAULT_TOOLBAR, type NewTabLinks, type RichTextOptions, type RichTextSettings, type RichTextStyle, getRichTextOptions, } from './settings.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,KAAK,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,KAAK,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9F,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EACH,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,kBAAkB,EAClB,YAAY,GACf,MAAM,UAAU,CAAC;AAClB,OAAO,EACH,gBAAgB,EAChB,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,kBAAkB,GACrB,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import { builtInButtons } from "./buttons.js";
2
+ import { createRichTextExtensions } from "./extensions/index.js";
3
+ import { normalizeRichText } from "./html.js";
4
+ import { DEFAULT_HEADINGS, DEFAULT_TOOLBAR, getRichTextOptions } from "./settings.js";
5
+ import { RichTextField, RichTextFieldEntry } from "./RichTextField.js";
6
+ import { createEditorPlugin, editorPlugin } from "./plugin.js";
7
+ export { DEFAULT_HEADINGS, DEFAULT_TOOLBAR, RichTextField, RichTextFieldEntry, builtInButtons, createEditorPlugin, createRichTextExtensions, editorPlugin, getRichTextOptions, normalizeRichText };
@@ -0,0 +1,26 @@
1
+ import type { AnyExtension } from '@tiptap/core';
2
+ import type { FieldEntry } from '@panneau/fields';
3
+ import type { RichTextButton } from './buttons.js';
4
+ export interface EditorPluginOptions {
5
+ /**
6
+ * TipTap extensions of the project, in every rich text field: custom nodes, such as an inline
7
+ * logo. One with the name of a built-in extension (`link`) replaces it.
8
+ */
9
+ extensions?: readonly AnyExtension[];
10
+ /** Toolbar buttons of the project, that the `toolbar` setting of fields names. */
11
+ buttons?: readonly RichTextButton[];
12
+ }
13
+ /** The `richtext` field type as a `@panneau/core` plugin. */
14
+ export interface EditorPlugin {
15
+ id: string;
16
+ fields: Readonly<Record<string, FieldEntry>>;
17
+ }
18
+ /**
19
+ * The `richtext` field type as a `@panneau/core` plugin, with the extensions and buttons of a
20
+ * project: `createPanneauApp({ plugins: [fieldsPlugin, createEditorPlugin({ extensions })] })`.
21
+ * A project gives all its extensions to one plugin: a later plugin replaces the field.
22
+ */
23
+ export declare function createEditorPlugin({ extensions, buttons, }?: EditorPluginOptions): EditorPlugin;
24
+ /** The `richtext` field type as a `@panneau/core` plugin, without project extensions. */
25
+ export declare const editorPlugin: EditorPlugin;
26
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,iBAAiB,CAAC;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,MAAM,WAAW,mBAAmB;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACrC,kFAAkF;IAClF,OAAO,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CACvC;AAED,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;CAChD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EAC/B,UAAU,EACV,OAAO,GACV,GAAE,mBAAwB,GAAG,YAAY,CAmBzC;AAED,yFAAyF;AACzF,eAAO,MAAM,YAAY,EAAE,YAAmC,CAAC"}
package/dist/plugin.js ADDED
@@ -0,0 +1,46 @@
1
+ import { RichTextFieldEntry } from "./RichTextField.js";
2
+ import { c } from "react/compiler-runtime";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/plugin.tsx
5
+ /**
6
+ * The `richtext` field type as a `@panneau/core` plugin, with the extensions and buttons of a
7
+ * project: `createPanneauApp({ plugins: [fieldsPlugin, createEditorPlugin({ extensions })] })`.
8
+ * A project gives all its extensions to one plugin: a later plugin replaces the field.
9
+ */
10
+ function createEditorPlugin({ extensions, buttons } = {}) {
11
+ function RichTextFieldWithExtensions(props) {
12
+ const $ = c(4);
13
+ let t0;
14
+ let t1;
15
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
16
+ t0 = extensions !== void 0 ? { extensions } : {};
17
+ t1 = buttons !== void 0 ? { buttons } : {};
18
+ $[0] = t0;
19
+ $[1] = t1;
20
+ } else {
21
+ t0 = $[0];
22
+ t1 = $[1];
23
+ }
24
+ let t2;
25
+ if ($[2] !== props) {
26
+ t2 = /* @__PURE__ */ jsx(RichTextFieldEntry, {
27
+ ...props,
28
+ ...t0,
29
+ ...t1
30
+ });
31
+ $[2] = props;
32
+ $[3] = t2;
33
+ } else t2 = $[3];
34
+ return t2;
35
+ }
36
+ return {
37
+ id: "panneau-editor",
38
+ fields: { richtext: extensions === void 0 && buttons === void 0 ? RichTextFieldEntry : RichTextFieldWithExtensions }
39
+ };
40
+ }
41
+ /** The `richtext` field type as a `@panneau/core` plugin, without project extensions. */
42
+ var editorPlugin = createEditorPlugin();
43
+ //#endregion
44
+ export { createEditorPlugin, editorPlugin };
45
+
46
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","names":["AnyExtension","FieldEntry","FieldEntryProps","RichTextFieldEntry","RichTextButton","EditorPluginOptions","extensions","buttons","EditorPlugin","id","fields","Readonly","Record","createEditorPlugin","RichTextFieldWithExtensions","props","$","_c","t0","t1","Symbol","for","undefined","t2","richtext","editorPlugin","_c"],"sources":["../src/plugin.tsx"],"sourcesContent":["import type { AnyExtension } from '@tiptap/core';\n\nimport type { FieldEntry, FieldEntryProps } from '@panneau/fields';\n\nimport { RichTextFieldEntry } from './RichTextField';\nimport type { RichTextButton } from './buttons';\n\nexport interface EditorPluginOptions {\n /**\n * TipTap extensions of the project, in every rich text field: custom nodes, such as an inline\n * logo. One with the name of a built-in extension (`link`) replaces it.\n */\n extensions?: readonly AnyExtension[];\n /** Toolbar buttons of the project, that the `toolbar` setting of fields names. */\n buttons?: readonly RichTextButton[];\n}\n\n/** The `richtext` field type as a `@panneau/core` plugin. */\nexport interface EditorPlugin {\n id: string;\n fields: Readonly<Record<string, FieldEntry>>;\n}\n\n/**\n * The `richtext` field type as a `@panneau/core` plugin, with the extensions and buttons of a\n * project: `createPanneauApp({ plugins: [fieldsPlugin, createEditorPlugin({ extensions })] })`.\n * A project gives all its extensions to one plugin: a later plugin replaces the field.\n */\nexport function createEditorPlugin({\n extensions,\n buttons,\n}: EditorPluginOptions = {}): EditorPlugin {\n function RichTextFieldWithExtensions(props: FieldEntryProps) {\n return (\n <RichTextFieldEntry\n {...props}\n {...(extensions !== undefined ? { extensions } : {})}\n {...(buttons !== undefined ? { buttons } : {})}\n />\n );\n }\n return {\n id: 'panneau-editor',\n fields: {\n richtext:\n extensions === undefined && buttons === undefined\n ? RichTextFieldEntry\n : RichTextFieldWithExtensions,\n },\n };\n}\n\n/** The `richtext` field type as a `@panneau/core` plugin, without project extensions. */\nexport const editorPlugin: EditorPlugin = createEditorPlugin();\n"],"mappings":";;;;;;;;;AA4BA,SAAgBa,mBAAmB,EAC/BP,YACAC,YACqB,CAAC,GAAiB;CACvC,SAAAO,4BAAAC,OAAA;EAAA,MAAAC,IAAAC,EAAA,CAAA;EAAA,IAAAC;EAAA,IAAAC;EAAA,IAAAH,EAAA,OAAAI,OAAAC,IAAA,2BAAA,GAAA;GAIiBH,KAAAZ,eAAegB,KAAAA,IAAf,EAAAhB,WAA6C,IAA7C,CAA6C;GAC7Ca,KAAAZ,YAAYe,KAAAA,IAAZ,EAAAf,QAAuC,IAAvC,CAAuC;GAACS,EAAA,KAAAE;GAAAF,EAAA,KAAAG;EAAA,OAAA;GAAAD,KAAAF,EAAA;GAAAG,KAAAH,EAAA;EAAA;EAAA,IAAAO;EAAA,IAAAP,EAAA,OAAAD,OAAA;GAHjDQ,KAAA,oBAAC,oBAAD;IAAmB,GACXR;IAAK,GACJG;IAA8C,GAC9CC;GAAwC,CAAA;GAC/CH,EAAA,KAAAD;GAAAC,EAAA,KAAAO;EAAA,OAAAA,KAAAP,EAAA;EAAA,OAJFO;CAIE;CAGV,OAAO;EACHd,IAAI;EACJC,QAAQ,EACJc,UACIlB,eAAegB,KAAAA,KAAaf,YAAYe,KAAAA,IAClCnB,qBACAW,4BACd;CACJ;AACJ;;AAGA,IAAaW,eAA6BZ,mBAAmB"}
@@ -0,0 +1,22 @@
1
+ import type { Field, RichTextSettings, RichTextStyle } from '@panneau/protocol';
2
+ export type { RichTextSettings, RichTextStyle };
3
+ /** Which links open in a new tab: as the user chooses, every link to another site, or none. */
4
+ export type NewTabLinks = 'choose' | 'external' | 'never';
5
+ /** The toolbar of a field without a `toolbar` setting. `style` only shows with styles. */
6
+ export declare const DEFAULT_TOOLBAR: readonly string[];
7
+ /** The heading levels of a field without a `headings` setting. */
8
+ export declare const DEFAULT_HEADINGS: readonly number[];
9
+ /** The options of a rich text field, read from the `settings` of its definition. */
10
+ export interface RichTextOptions {
11
+ toolbar: readonly string[];
12
+ headings: readonly number[];
13
+ styles: readonly RichTextStyle[];
14
+ newTab: NewTabLinks;
15
+ }
16
+ /**
17
+ * The options of a `richtext` field from its `settings` (`RichTextSettings` of the protocol).
18
+ * Settings of the wrong type fall back to the defaults, and invalid entries are skipped, so that
19
+ * a field always renders.
20
+ */
21
+ export declare function getRichTextOptions(field: Pick<Field, 'settings'>): RichTextOptions;
22
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGhF,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAC;AAEhD,+FAA+F;AAC/F,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAE1D,0FAA0F;AAC1F,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAU5C,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAAc,CAAC;AAE7D,oFAAoF;AACpF,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IACjC,MAAM,EAAE,WAAW,CAAC;CACvB;AAqBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,eAAe,CAalF"}
@@ -0,0 +1,45 @@
1
+ import { RICH_TEXT_STYLE_ELEMENTS } from "@panneau/protocol/runtime";
2
+ //#region src/settings.ts
3
+ /** The toolbar of a field without a `toolbar` setting. `style` only shows with styles. */
4
+ var DEFAULT_TOOLBAR = [
5
+ "heading",
6
+ "style",
7
+ "|",
8
+ "bold",
9
+ "italic",
10
+ "link",
11
+ "|",
12
+ "bulletList",
13
+ "orderedList"
14
+ ];
15
+ /** The heading levels of a field without a `headings` setting. */
16
+ var DEFAULT_HEADINGS = [
17
+ 2,
18
+ 3,
19
+ 4
20
+ ];
21
+ var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
22
+ var isString = (value) => typeof value === "string";
23
+ var isLevel = (value) => typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 6;
24
+ function isStyle(value) {
25
+ return isRecord(value) && isString(value.label) && isString(value.class) && value.class.trim() !== "" && (value.element === void 0 || RICH_TEXT_STYLE_ELEMENTS.includes(value.element));
26
+ }
27
+ /**
28
+ * The options of a `richtext` field from its `settings` (`RichTextSettings` of the protocol).
29
+ * Settings of the wrong type fall back to the defaults, and invalid entries are skipped, so that
30
+ * a field always renders.
31
+ */
32
+ function getRichTextOptions(field) {
33
+ const settings = field.settings ?? {};
34
+ const links = isRecord(settings.links) ? settings.links : {};
35
+ return {
36
+ toolbar: Array.isArray(settings.toolbar) ? settings.toolbar.filter(isString) : DEFAULT_TOOLBAR,
37
+ headings: Array.isArray(settings.headings) ? settings.headings.filter(isLevel) : DEFAULT_HEADINGS,
38
+ styles: Array.isArray(settings.styles) ? settings.styles.filter(isStyle) : [],
39
+ newTab: links.newTab === "external" || links.newTab === "never" ? links.newTab : "choose"
40
+ };
41
+ }
42
+ //#endregion
43
+ export { DEFAULT_HEADINGS, DEFAULT_TOOLBAR, getRichTextOptions };
44
+
45
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","names":["Field","RichTextSettings","RichTextStyle","RICH_TEXT_STYLE_ELEMENTS","NewTabLinks","DEFAULT_TOOLBAR","DEFAULT_HEADINGS","RichTextOptions","toolbar","headings","styles","newTab","isRecord","value","Record","Array","isArray","isString","isLevel","Number","isInteger","isStyle","label","class","trim","element","undefined","includes","getRichTextOptions","field","Pick","settings","links","filter"],"sources":["../src/settings.ts"],"sourcesContent":["import type { Field, RichTextSettings, RichTextStyle } from '@panneau/protocol';\nimport { RICH_TEXT_STYLE_ELEMENTS } from '@panneau/protocol/runtime';\n\nexport type { RichTextSettings, RichTextStyle };\n\n/** Which links open in a new tab: as the user chooses, every link to another site, or none. */\nexport type NewTabLinks = 'choose' | 'external' | 'never';\n\n/** The toolbar of a field without a `toolbar` setting. `style` only shows with styles. */\nexport const DEFAULT_TOOLBAR: readonly string[] = [\n 'heading',\n 'style',\n '|',\n 'bold',\n 'italic',\n 'link',\n '|',\n 'bulletList',\n 'orderedList',\n];\n\n/** The heading levels of a field without a `headings` setting. */\nexport const DEFAULT_HEADINGS: readonly number[] = [2, 3, 4];\n\n/** The options of a rich text field, read from the `settings` of its definition. */\nexport interface RichTextOptions {\n toolbar: readonly string[];\n headings: readonly number[];\n styles: readonly RichTextStyle[];\n newTab: NewTabLinks;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === 'object' && !Array.isArray(value);\n\nconst isString = (value: unknown): value is string => typeof value === 'string';\n\nconst isLevel = (value: unknown): value is number =>\n typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= 6;\n\nfunction isStyle(value: unknown): value is RichTextStyle {\n return (\n isRecord(value) &&\n isString(value.label) &&\n isString(value.class) &&\n value.class.trim() !== '' &&\n (value.element === undefined ||\n (RICH_TEXT_STYLE_ELEMENTS as readonly unknown[]).includes(value.element))\n );\n}\n\n/**\n * The options of a `richtext` field from its `settings` (`RichTextSettings` of the protocol).\n * Settings of the wrong type fall back to the defaults, and invalid entries are skipped, so that\n * a field always renders.\n */\nexport function getRichTextOptions(field: Pick<Field, 'settings'>): RichTextOptions {\n const settings = field.settings ?? {};\n const links = isRecord(settings.links) ? settings.links : {};\n return {\n toolbar: Array.isArray(settings.toolbar)\n ? settings.toolbar.filter(isString)\n : DEFAULT_TOOLBAR,\n headings: Array.isArray(settings.headings)\n ? settings.headings.filter(isLevel)\n : DEFAULT_HEADINGS,\n styles: Array.isArray(settings.styles) ? settings.styles.filter(isStyle) : [],\n newTab: links.newTab === 'external' || links.newTab === 'never' ? links.newTab : 'choose',\n };\n}\n"],"mappings":";;;AASA,IAAaK,kBAAqC;CAC9C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAAa;;AAIjB,IAAaC,mBAAsC;CAAC;CAAG;CAAG;AAAC;AAU3D,IAAMM,YAAYC,UACdA,UAAU,QAAQ,OAAOA,UAAU,YAAY,CAACE,MAAMC,QAAQH,KAAK;AAEvE,IAAMI,YAAYJ,UAAoC,OAAOA,UAAU;AAEvE,IAAMK,WAAWL,UACb,OAAOA,UAAU,YAAYM,OAAOC,UAAUP,KAAK,KAAKA,SAAS,KAAKA,SAAS;AAEnF,SAASQ,QAAQR,OAAwC;CACrD,OACID,SAASC,KAAK,KACdI,SAASJ,MAAMS,KAAK,KACpBL,SAASJ,MAAMU,KAAK,KACpBV,MAAMU,MAAMC,KAAK,MAAM,OACtBX,MAAMY,YAAYC,KAAAA,KACdvB,yBAAgDwB,SAASd,MAAMY,OAAO;AAEnF;;;;;;AAOA,SAAgBG,mBAAmBC,OAAiD;CAChF,MAAME,WAAWF,MAAME,YAAY,CAAC;CACpC,MAAMC,QAAQpB,SAASmB,SAASC,KAAK,IAAID,SAASC,QAAQ,CAAC;CAC3D,OAAO;EACHxB,SAASO,MAAMC,QAAQe,SAASvB,OAAO,IACjCuB,SAASvB,QAAQyB,OAAOhB,QAAQ,IAChCZ;EACNI,UAAUM,MAAMC,QAAQe,SAAStB,QAAQ,IACnCsB,SAAStB,SAASwB,OAAOf,OAAO,IAChCZ;EACNI,QAAQK,MAAMC,QAAQe,SAASrB,MAAM,IAAIqB,SAASrB,OAAOuB,OAAOZ,OAAO,IAAI,CAAA;EAC3EV,QAAQqB,MAAMrB,WAAW,cAAcqB,MAAMrB,WAAW,UAAUqB,MAAMrB,SAAS;CACrF;AACJ"}
@@ -0,0 +1,16 @@
1
+ import type { Editor } from '@tiptap/core';
2
+ import type { Node as ProseMirrorNode } from '@tiptap/pm/model';
3
+ import type { EditorState } from '@tiptap/pm/state';
4
+ import type { RichTextStyle } from '@panneau/protocol';
5
+ /** The element a node is written as: `p`, `h2`, `li`. */
6
+ export declare function elementOf(node: ProseMirrorNode): string | null;
7
+ /** Whether a style can apply to the selection. */
8
+ export declare function canApplyStyle(state: EditorState, style: RichTextStyle): boolean;
9
+ /** The style of the selection: the first one that all its targets have. */
10
+ export declare function getActiveStyle(state: EditorState, styles: readonly RichTextStyle[]): RichTextStyle | null;
11
+ /**
12
+ * Puts a style on the selection, in place of the other styles of its element, or removes the
13
+ * styles of the paragraphs and headings of the selection with `null`. Other classes stay.
14
+ */
15
+ export declare function applyStyle(editor: Editor, style: RichTextStyle | null, styles: readonly RichTextStyle[]): void;
16
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,yDAAyD;AACzD,wBAAgB,SAAS,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAiB9D;AA0CD,kDAAkD;AAClD,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAE/E;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAC1B,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,SAAS,aAAa,EAAE,GACjC,aAAa,GAAG,IAAI,CAOtB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACtB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,aAAa,GAAG,IAAI,EAC3B,MAAM,EAAE,SAAS,aAAa,EAAE,GACjC,IAAI,CAkCN"}