@phenix1024/editor 0.1.0 → 0.2.0

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.
@@ -1,538 +1,506 @@
1
- import { Editor as e, Extension as t, Mark as n, Node as r } from "@tiptap/core";
2
- import i from "@tiptap/starter-kit";
3
- import a from "@tiptap/extension-underline";
4
- import o from "@tiptap/extension-link";
5
- import s from "@tiptap/extension-image";
1
+ import { Editor as e, Extension as t, Mark as n, Node as r, getMarkRange as i } from "@tiptap/core";
2
+ import a from "@tiptap/starter-kit";
3
+ import o from "@tiptap/extension-underline";
4
+ import s from "@tiptap/extension-link";
6
5
  import c from "@tiptap/extension-placeholder";
7
6
  import { Table as l } from "@tiptap/extension-table";
8
7
  import { TableRow as u } from "@tiptap/extension-table-row";
9
8
  import { TableHeader as d } from "@tiptap/extension-table-header";
10
9
  import { TableCell as f } from "@tiptap/extension-table-cell";
11
- import p from "@tiptap/extension-heading";
12
- import { Plugin as m, PluginKey as h } from "@tiptap/pm/state";
13
- import { Decoration as g, DecorationSet as _ } from "@tiptap/pm/view";
14
- import v from "jszip";
15
- // src/editor/extensions.ts
16
- var y = n.create({
17
- name: "label",
18
- inclusive: !1,
19
- addAttributes() {
20
- return { label: {
21
- default: "highlight",
22
- parseHTML: (e) => e.dataset.label ?? "highlight",
23
- renderHTML: (e) => ({ "data-label": e.label })
24
- } };
10
+ import p from "jszip";
11
+ import m from "@tiptap/extension-heading";
12
+ import h from "@tiptap/extension-image";
13
+ import { Plugin as g, PluginKey as _ } from "@tiptap/pm/state";
14
+ import { Decoration as v, DecorationSet as y } from "@tiptap/pm/view";
15
+ // src/editor/toolbar.ts
16
+ var ee = [
17
+ {
18
+ action: "bold",
19
+ label: "Ж",
20
+ title: "Жирный (⌘/Ctrl+B)"
25
21
  },
26
- parseHTML() {
27
- return [{ tag: "mark[data-label]" }, { tag: "mark" }];
22
+ {
23
+ action: "italic",
24
+ label: "К",
25
+ title: "Курсив (⌘/Ctrl+I)"
28
26
  },
29
- renderHTML({ HTMLAttributes: e }) {
30
- return [
31
- "mark",
32
- e,
33
- 0
34
- ];
35
- }
36
- });
37
- function b() {
38
- return {
39
- fieldId: {
40
- default: "",
41
- parseHTML: (e) => e.getAttribute("data-ophx-field") ?? "",
42
- renderHTML: (e) => ({ "data-ophx-field": e.fieldId ?? "" })
43
- },
44
- fieldLabel: {
45
- default: null,
46
- parseHTML: (e) => e.getAttribute("data-ophx-label"),
47
- renderHTML: (e) => e.fieldLabel ? { "data-ophx-label": e.fieldLabel } : {}
48
- },
49
- required: {
50
- default: !1,
51
- parseHTML: (e) => e.getAttribute("data-ophx-required") === "true",
52
- renderHTML: (e) => e.required ? { "data-ophx-required": "true" } : {}
53
- }
54
- };
55
- }
56
- var x = /^[A-Za-z0-9._-]+$/, S = r.create({
57
- name: "ophxInlineField",
58
- priority: 1e3,
59
- group: "inline",
60
- inline: !0,
61
- content: "text*",
62
- defining: !0,
63
- addAttributes: b,
64
- parseHTML() {
65
- return [{
66
- tag: "span.ophx-field[contenteditable=\"true\"][data-ophx-field]",
67
- priority: 1e3
68
- }];
27
+ {
28
+ action: "underline",
29
+ label: "Ч",
30
+ title: "Подчёркнутый (⌘/Ctrl+U)"
69
31
  },
70
- renderHTML({ HTMLAttributes: e }) {
71
- return [
72
- "span",
73
- {
74
- ...e,
75
- class: "ophx-field",
76
- contenteditable: "true"
77
- },
78
- 0
79
- ];
80
- }
81
- }), C = r.create({
82
- name: "ophxBlockField",
83
- priority: 1e3,
84
- group: "block",
85
- content: "text*",
86
- defining: !0,
87
- addAttributes() {
88
- return {
89
- ...b(),
90
- tagName: {
91
- default: "div",
92
- parseHTML: (e) => e.tagName.toLowerCase(),
93
- renderHTML: () => ({})
94
- }
95
- };
32
+ {
33
+ action: "strike",
34
+ label: "З",
35
+ title: "Зачёркнутый"
96
36
  },
97
- parseHTML() {
98
- return [{
99
- tag: "p.ophx-field[contenteditable=\"true\"][data-ophx-field]",
100
- priority: 1e3
101
- }, {
102
- tag: "div.ophx-field[contenteditable=\"true\"][data-ophx-field]",
103
- priority: 1e3
104
- }];
37
+ {
38
+ action: "bullet",
39
+ label: "<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"7\" cy=\"6\" r=\"1.3\"/><circle cx=\"7\" cy=\"12\" r=\"1.3\"/><circle cx=\"7\" cy=\"18\" r=\"1.3\"/><path d=\"M11 6h8M11 12h8M11 18h8\"/></svg>",
40
+ title: "Маркированный список"
105
41
  },
106
- renderHTML({ node: e, HTMLAttributes: t }) {
107
- return [
108
- e.attrs.tagName === "p" ? "p" : "div",
109
- {
110
- ...t,
111
- class: "ophx-field",
112
- contenteditable: "true"
113
- },
114
- 0
115
- ];
116
- }
117
- }), w = n.create({
118
- name: "ophxReadonlyInline",
119
- inclusive: !1,
120
- addAttributes() {
121
- return { className: {
122
- default: null,
123
- parseHTML: (e) => e.getAttribute("class"),
124
- renderHTML: (e) => e.className ? { class: e.className } : {}
125
- } };
42
+ {
43
+ action: "ordered",
44
+ label: "<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M10 6h10M10 12h10M10 18h10\"/><path d=\"M4 4.8 5.2 4v4.2M3.8 8.2h2.8M3.8 10.8c.2-.5.6-.8 1.2-.8.8 0 1.2.4 1.2 1 0 .4-.3.8-.7 1.2l-1.6 1.5h2.5M3.8 16.2c.2-.5.6-.8 1.2-.8.8 0 1.2.4 1.2.9 0 .5-.4.8-1 .8.7 0 1.1.3 1.1.9 0 .6-.5 1-1.3 1-.6 0-1-.2-1.3-.7\"/></svg>",
45
+ title: "Нумерованный список"
126
46
  },
127
- parseHTML() {
128
- return [{
129
- tag: "span[contenteditable=\"false\"]",
130
- priority: 100
131
- }];
47
+ {
48
+ action: "outdent",
49
+ label: "<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 6h14M5 12h10M5 18h14\"/><path d=\"M9 9l-4 3 4 3\"/></svg>",
50
+ title: "Уменьшить отступ"
132
51
  },
133
- renderHTML({ HTMLAttributes: e }) {
134
- return [
135
- "span",
136
- {
137
- ...e,
138
- contenteditable: "false"
139
- },
140
- 0
141
- ];
142
- }
143
- }), T = r.create({
144
- name: "ophxReadonlyBlock",
145
- priority: 1e3,
146
- group: "block",
147
- content: "block*",
148
- defining: !0,
149
- isolating: !0,
150
- addAttributes() {
151
- return {
152
- id: {
153
- default: null,
154
- parseHTML: (e) => e.getAttribute("id"),
155
- renderHTML: (e) => e.id ? { id: e.id } : {}
156
- },
157
- className: {
158
- default: null,
159
- parseHTML: (e) => e.getAttribute("class"),
160
- renderHTML: (e) => e.className ? { class: e.className } : {}
161
- },
162
- title: {
163
- default: null,
164
- parseHTML: (e) => e.getAttribute("title"),
165
- renderHTML: (e) => e.title ? { title: e.title } : {}
166
- }
167
- };
52
+ {
53
+ action: "indent",
54
+ label: "<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 6h14M9 12h10M5 18h14\"/><path d=\"M15 9l4 3-4 3\"/></svg>",
55
+ title: "Увеличить отступ"
168
56
  },
169
- parseHTML() {
170
- return [{
171
- tag: "div[contenteditable=\"false\"]",
172
- priority: 1e3
173
- }];
57
+ {
58
+ action: "link",
59
+ label: "<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M10 13.5a4.5 4.5 0 0 0 6.4 0l3-3a4.5 4.5 0 0 0-6.4-6.4l-1.7 1.7\"/><path d=\"M14 10.5a4.5 4.5 0 0 0-6.4 0l-3 3A4.5 4.5 0 0 0 11 19.9l1.7-1.7\"/></svg>",
60
+ title: "Добавить ссылку"
174
61
  },
175
- renderHTML({ HTMLAttributes: e }) {
176
- return [
177
- "div",
178
- {
179
- ...e,
180
- contenteditable: "false"
181
- },
182
- 0
183
- ];
184
- }
185
- }), E = new h("ophxHeadingCollapse"), D = p.extend({
186
- addAttributes() {
187
- return {
188
- ...this.parent?.(),
189
- collapsed: {
190
- default: !1,
191
- parseHTML: (e) => e.dataset.collapsed === "true",
192
- renderHTML: (e) => e.collapsed ? { "data-collapsed": "true" } : {}
193
- }
194
- };
62
+ {
63
+ action: "image",
64
+ label: "<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"/><circle cx=\"8.5\" cy=\"9\" r=\"1.5\"/><path d=\"m21 15-5-5L5 20\"/></svg>",
65
+ title: "Вставить изображение"
195
66
  },
196
- addProseMirrorPlugins() {
197
- return [new m({
198
- key: E,
199
- props: { decorations(e) {
200
- let t = [], n = null;
201
- return e.doc.forEach((e, r) => {
202
- if (e.type.name === "heading") {
203
- let i = Number(e.attrs.level);
204
- n !== null && i <= n && (n = null), n !== null && t.push(g.node(r, r + e.nodeSize, { class: "ophx-collapsed-content" })), e.attrs.collapsed && (n = i);
205
- } else n !== null && t.push(g.node(r, r + e.nodeSize, { class: "ophx-collapsed-content" }));
206
- }), _.create(e.doc, t);
207
- } }
208
- })];
209
- }
210
- }), O = new h("ophxTextSearch"), k = t.create({
211
- name: "ophxSearch",
212
- addStorage() {
213
- return { query: "" };
67
+ {
68
+ action: "table",
69
+ label: "<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"1.5\"/><path d=\"M3 10h18M9 4v16M15 4v16\"/></svg>",
70
+ title: "Вставить таблицу"
214
71
  },
215
- addCommands() {
216
- return { setSearchQuery: (e) => ({ tr: t, dispatch: n }) => (this.storage.query = e, n?.(t.setMeta(O, e)), !0) };
72
+ {
73
+ action: "label",
74
+ label: "Метка",
75
+ title: "Выделить текст меткой"
217
76
  },
218
- addProseMirrorPlugins() {
219
- let e = this;
220
- return [new m({
221
- key: O,
222
- state: {
223
- init: (t, n) => A(n.doc, e.storage.query),
224
- apply: (t, n, r, i) => {
225
- let a = t.getMeta(O);
226
- return a !== void 0 && (e.storage.query = a), !t.docChanged && a === void 0 ? n : A(i.doc, e.storage.query);
227
- }
228
- },
229
- props: { decorations(e) {
230
- return O.getState(e);
231
- } }
232
- })];
77
+ {
78
+ action: "toc",
79
+ label: "Содержание",
80
+ title: "Содержание документа"
81
+ },
82
+ {
83
+ action: "undo",
84
+ label: "↶",
85
+ title: "Отменить (⌘/Ctrl+Z)"
86
+ },
87
+ {
88
+ action: "redo",
89
+ label: "↷",
90
+ title: "Повторить (⌘/Ctrl+Shift+Z)"
233
91
  }
234
- });
235
- function A(e, t) {
236
- if (!t.trim()) return _.empty;
237
- let n = [], r = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), i = new RegExp(r, "gi");
238
- return e.descendants((e, t) => {
239
- if (e.isText && e.text) {
240
- i.lastIndex = 0;
241
- for (let r of e.text.matchAll(i)) r.index !== void 0 && n.push(g.inline(t + r.index, t + r.index + r[0].length, { class: "ophx-search-match" }));
92
+ ];
93
+ function b(e, t, n = {}) {
94
+ e.classList.add("ophx-toolbar"), e.replaceChildren();
95
+ let r = (e, t) => {
96
+ e.style.display = t ? "" : "none", e.hidden = !t;
97
+ }, a = () => {
98
+ f = !1, r(l, !1), s.setAttribute("aria-expanded", "false"), r(P, !1), h.get("toc")?.setAttribute("aria-expanded", "false");
99
+ }, o = document.createElement("div");
100
+ o.className = "ophx-toolbar__style-wrap";
101
+ let s = document.createElement("button");
102
+ s.type = "button", s.className = "ophx-toolbar__style", s.setAttribute("aria-label", "Стиль текста"), s.setAttribute("aria-haspopup", "listbox"), s.setAttribute("aria-expanded", "false");
103
+ let c = document.createElement("span");
104
+ c.className = "ophx-toolbar__style-label", s.append(c);
105
+ let l = document.createElement("div");
106
+ l.className = "ophx-toolbar__style-menu", r(l, !1);
107
+ let u = [
108
+ {
109
+ value: "p",
110
+ label: "Обычный текст",
111
+ className: "ophx-toolbar__style-preview--p"
112
+ },
113
+ {
114
+ value: "1",
115
+ label: "Заголовок 1",
116
+ className: "ophx-toolbar__style-preview--h1"
117
+ },
118
+ {
119
+ value: "2",
120
+ label: "Заголовок 2",
121
+ className: "ophx-toolbar__style-preview--h2"
122
+ },
123
+ {
124
+ value: "3",
125
+ label: "Заголовок 3",
126
+ className: "ophx-toolbar__style-preview--h3"
242
127
  }
243
- }), _.create(e, n);
244
- }
245
- var j = t.create({
246
- name: "ophxWysiwymRules",
247
- addProseMirrorPlugins() {
248
- return [new m({ props: {
249
- handleTextInput(e, t, n, r) {
250
- return r === " " && /\s/.test(e.state.doc.textBetween(Math.max(0, t - 1), t));
251
- },
252
- handleKeyDown(e, t) {
253
- if (t.key !== "Enter" || t.shiftKey) return !1;
254
- let { $from: n } = e.state.selection;
255
- if (!n.parent.isTextblock || n.parent.content.size !== 0 || n.depth === 0) return !1;
256
- let r = n.index(n.depth - 1), i = n.node(n.depth - 1);
257
- return [i.maybeChild(r - 1), i.maybeChild(r + 1)].some((e) => e?.isTextblock && e.content.size === 0);
128
+ ], d = (e) => {
129
+ let n = Number(e), r = t.chain().focus();
130
+ e === "p" ? r.setParagraph().run() : r.setHeading({ level: n }).run(), m();
131
+ };
132
+ for (let e of u) {
133
+ let t = document.createElement("button");
134
+ t.type = "button", t.className = "ophx-toolbar__style-option", t.dataset.value = e.value, t.setAttribute("role", "option"), t.setAttribute("aria-selected", "false"), t.innerHTML = `<span class="ophx-toolbar__style-preview ${e.className}">${e.label}</span>`, t.addEventListener("mousedown", (e) => {
135
+ e.preventDefault(), e.stopPropagation();
136
+ }), t.addEventListener("click", () => d(e.value)), l.append(t);
137
+ }
138
+ let f = !1, p = () => {
139
+ f = !0, r(l, !0), s.setAttribute("aria-expanded", "true");
140
+ }, m = () => {
141
+ f = !1, r(l, !1), s.setAttribute("aria-expanded", "false");
142
+ };
143
+ s.addEventListener("click", (e) => {
144
+ e.stopPropagation(), f ? m() : (r(P, !1), p());
145
+ }), s.addEventListener("keydown", (e) => {
146
+ e.key === "Escape" && m();
147
+ }), document.addEventListener("click", (e) => {
148
+ let t = e.target, n = o.contains(t), r = P.contains(t);
149
+ !n && !r && a();
150
+ }), o.append(s, l), e.append(o);
151
+ let h = /* @__PURE__ */ new Map();
152
+ for (let n of ee) {
153
+ let r = document.createElement("button");
154
+ r.type = "button", r.className = "ophx-toolbar__button", r.dataset.action = n.action, r.innerHTML = n.label, r.title = n.title, r.setAttribute("aria-label", n.title), r.addEventListener("mousedown", (e) => e.preventDefault()), r.addEventListener("click", (e) => {
155
+ n.action === "toc" ? e.stopPropagation() : n.action === "link" ? U() : n.action === "table" ? se() : n.action === "label" ? (e.stopPropagation(), Te()) : t.isEditable && ie(n.action, t, _, P);
156
+ }), h.set(n.action, r), e.append(r);
157
+ }
158
+ let g = document.createElement("input");
159
+ g.type = "search", g.className = "ophx-toolbar__search", g.placeholder = "Поиск…", g.setAttribute("aria-label", "Поиск по тексту"), g.addEventListener("input", () => t.commands.setSearchQuery(g.value)), g.addEventListener("keydown", (e) => {
160
+ e.key === "Escape" && (g.value = "", t.commands.setSearchQuery(""), t.commands.focus());
161
+ }), e.append(g);
162
+ let _ = document.createElement("input");
163
+ _.type = "file", _.accept = "image/*", _.hidden = !0, _.addEventListener("change", () => {
164
+ let e = _.files?.[0];
165
+ if (e) {
166
+ if (n.onInsertImage) n.onInsertImage(e);
167
+ else {
168
+ let n = new FileReader();
169
+ n.addEventListener("load", () => {
170
+ typeof n.result == "string" && t.chain().focus().setImage({
171
+ src: n.result,
172
+ alt: e.name
173
+ }).run();
174
+ }), n.readAsDataURL(e);
258
175
  }
259
- } })];
176
+ }
177
+ _.value = "";
178
+ }), e.append(_);
179
+ let v = document.createElement("form");
180
+ v.className = "ophx-table-popover", v.setAttribute("aria-label", "Создать таблицу"), v.hidden = !0, v.innerHTML = "\n <label class=\"ophx-table-popover__label\">Строки<input name=\"rows\" type=\"number\" min=\"1\" max=\"30\" value=\"3\" required></label>\n <label class=\"ophx-table-popover__label\">Столбцы<input name=\"cols\" type=\"number\" min=\"1\" max=\"20\" value=\"3\" required></label>\n <label class=\"ophx-table-popover__header\"><input name=\"header\" type=\"checkbox\" checked> Заголовок таблицы</label>\n <button class=\"ophx-table-popover__insert\" type=\"submit\">Вставить таблицу</button>\n ", document.body.append(v);
181
+ let y = h.get("table"), b = v.querySelector("input[name=\"rows\"]"), ae = v.querySelector("input[name=\"cols\"]"), oe = v.querySelector("input[name=\"header\"]"), x = () => {
182
+ if (v.hidden) return;
183
+ let e = y.getBoundingClientRect(), t = v.offsetWidth, n = Math.max(8, Math.min(e.left, window.innerWidth - t - 8));
184
+ v.style.left = `${n}px`, v.style.top = `${Math.min(e.bottom + 8, window.innerHeight - v.offsetHeight - 8)}px`;
185
+ };
186
+ function S() {
187
+ v.hidden = !0, y.setAttribute("aria-expanded", "false"), window.removeEventListener("resize", x), document.removeEventListener("scroll", x, !0);
260
188
  }
261
- }), M = t.create({
262
- name: "ophxIndentation",
263
- addGlobalAttributes() {
264
- return [{
265
- types: ["paragraph", "heading"],
266
- attributes: { indent: {
267
- default: 0,
268
- parseHTML: (e) => Number(e.dataset.indent ?? 0),
269
- renderHTML: (e) => e.indent ? { "data-indent": String(e.indent) } : {}
270
- } }
271
- }];
189
+ function se() {
190
+ t.isEditable && (v.hidden ? (v.hidden = !1, y.setAttribute("aria-expanded", "true"), x(), window.addEventListener("resize", x), document.addEventListener("scroll", x, !0), b.focus(), b.select()) : S());
272
191
  }
273
- }), N = t.create({
274
- name: "ophxEditability",
275
- addGlobalAttributes() {
276
- return [{
277
- types: [
278
- "paragraph",
279
- "heading",
280
- "blockquote",
281
- "codeBlock",
282
- "bulletList",
283
- "orderedList",
284
- "listItem",
285
- "horizontalRule",
286
- "image",
287
- "table",
288
- "tableRow",
289
- "tableHeader",
290
- "tableCell",
291
- "bold",
292
- "italic",
293
- "underline",
294
- "strike",
295
- "link",
296
- "label"
297
- ],
298
- attributes: { contenteditable: {
299
- default: null,
300
- parseHTML: (e) => {
301
- let t = e.getAttribute("contenteditable");
302
- return t === "true" || t === "false" ? t : null;
303
- },
304
- renderHTML: (e) => e.contenteditable ? { contenteditable: e.contenteditable } : {}
305
- } }
306
- }];
192
+ y.setAttribute("aria-haspopup", "dialog"), y.setAttribute("aria-expanded", "false"), v.addEventListener("mousedown", (e) => e.stopPropagation()), v.addEventListener("submit", (e) => {
193
+ e.preventDefault();
194
+ let n = Math.max(1, Math.min(30, Number(b.value) || 1)), r = Math.max(1, Math.min(20, Number(ae.value) || 1));
195
+ b.value = String(n), ae.value = String(r), t.chain().focus().insertTable({
196
+ rows: n,
197
+ cols: r,
198
+ withHeaderRow: oe.checked
199
+ }).run(), S();
200
+ }), v.addEventListener("keydown", (e) => {
201
+ e.key === "Escape" && (e.preventDefault(), S(), y.focus());
202
+ });
203
+ let ce = (e) => {
204
+ let t = e.target;
205
+ !v.contains(t) && !y.contains(t) && S();
206
+ };
207
+ document.addEventListener("click", ce);
208
+ let C = document.createElement("div");
209
+ C.className = "ophx-table-context", C.setAttribute("role", "toolbar"), C.setAttribute("aria-label", "Действия с таблицей"), C.hidden = !0, C.innerHTML = "\n <button type=\"button\" data-command=\"addRow\" aria-label=\"Добавить строку ниже\" title=\"Добавить строку ниже\"><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M4 4h16v12H4zM4 10h16M10 4v12M12 20v-4M10 18h4\"/></svg></button>\n <button type=\"button\" data-command=\"deleteRow\" aria-label=\"Удалить строку\" title=\"Удалить строку\"><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M4 4h16v12H4zM4 10h16M10 4v12M10 20h4\"/></svg></button>\n <span class=\"ophx-table-context__divider\" aria-hidden=\"true\"></span>\n <button type=\"button\" data-command=\"addColumn\" aria-label=\"Добавить столбец справа\" title=\"Добавить столбец справа\"><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M4 4h12v16H4zM10 4v16M20 12h-4M18 10v4\"/></svg></button>\n <button type=\"button\" data-command=\"deleteColumn\" aria-label=\"Удалить столбец\" title=\"Удалить столбец\"><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M4 4h12v16H4zM10 4v16M18 10v4\"/></svg></button>\n ", document.body.append(C);
210
+ let le = () => {
211
+ let { $from: e } = t.state.selection;
212
+ for (let n = e.depth; n > 0; --n) {
213
+ if (e.node(n).type.name !== "table") continue;
214
+ let r = t.view.nodeDOM(e.before(n));
215
+ return r instanceof HTMLTableElement ? r : r instanceof HTMLElement ? r.querySelector("table") : null;
216
+ }
217
+ return null;
218
+ }, w = () => {
219
+ if (C.hidden) return;
220
+ let e = le();
221
+ if (!e) return;
222
+ let t = e.getBoundingClientRect(), n = C.offsetWidth, r = C.offsetHeight, i = Math.max(8, Math.min(t.left, window.innerWidth - n - 8)), a = t.top - r - 8 >= 8 ? t.top - r - 8 : Math.min(t.bottom + 8, window.innerHeight - r - 8);
223
+ C.style.left = `${i}px`, C.style.top = `${Math.max(8, a)}px`;
224
+ }, T = () => {
225
+ !t.isEditable || !t.isActive("table") ? (C.hidden = !0, window.removeEventListener("resize", w), document.removeEventListener("scroll", w, !0)) : (C.hidden = !1, w(), window.addEventListener("resize", w), document.addEventListener("scroll", w, !0));
226
+ };
227
+ C.addEventListener("mousedown", (e) => e.preventDefault()), C.addEventListener("click", (e) => {
228
+ let n = e.target.closest("[data-command]");
229
+ if (!n) return;
230
+ let r = n.dataset.command, i = t.chain().focus();
231
+ r === "addRow" ? i.addRowAfter().run() : r === "deleteRow" ? i.deleteRow().run() : r === "addColumn" ? i.addColumnAfter().run() : r === "deleteColumn" && i.deleteColumn().run();
232
+ });
233
+ let E = (e) => {
234
+ if (C.hidden) return;
235
+ let t = e.target, n = le();
236
+ !C.contains(t) && !n?.contains(t) && (C.hidden = !0);
237
+ };
238
+ document.addEventListener("click", E);
239
+ let D = document.createElement("div");
240
+ D.className = "ophx-image-popover", D.setAttribute("role", "dialog"), D.setAttribute("aria-label", "Размер и обтекание изображения"), D.hidden = !0, D.innerHTML = "\n <span class=\"ophx-image-popover__arrow\" aria-hidden=\"true\"></span>\n <div class=\"ophx-image-popover__heading\">Обтекание текстом</div>\n <div class=\"ophx-image-popover__choices\" role=\"group\" aria-label=\"Обтекание текстом\">\n <button class=\"ophx-image-popover__choice\" type=\"button\" data-wrap=\"none\" aria-label=\"Без обтекания\" title=\"Без обтекания\"><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><rect x=\"3\" y=\"4\" width=\"8\" height=\"7\" rx=\"1\"/><path d=\"M14 5h7M14 8h7M3 14h18M3 18h18\"/></svg></button>\n <button class=\"ophx-image-popover__choice\" type=\"button\" data-wrap=\"left\" aria-label=\"Изображение слева, текст справа\" title=\"Изображение слева, текст справа\"><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><rect x=\"3\" y=\"4\" width=\"8\" height=\"10\" rx=\"1\"/><path d=\"M14 5h7M14 8h7M14 11h7M3 18h18\"/></svg></button>\n <button class=\"ophx-image-popover__choice\" type=\"button\" data-wrap=\"right\" aria-label=\"Изображение справа, текст слева\" title=\"Изображение справа, текст слева\"><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><rect x=\"13\" y=\"4\" width=\"8\" height=\"10\" rx=\"1\"/><path d=\"M3 5h7M3 8h7M3 11h7M3 18h18\"/></svg></button>\n </div>\n <div class=\"ophx-image-popover__heading\">Выравнивание</div>\n <div class=\"ophx-image-popover__choices\" role=\"group\" aria-label=\"Выравнивание изображения\">\n <button class=\"ophx-image-popover__choice\" type=\"button\" data-align=\"left\" aria-label=\"Прижать влево\" title=\"Прижать влево\"><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M4 3v18M8 6h12v5H8zM8 15h12\"/></svg></button>\n <button class=\"ophx-image-popover__choice\" type=\"button\" data-align=\"center\" aria-label=\"По центру\" title=\"По центру\"><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M12 3v18M4 6h16v5H4zM4 15h16\"/></svg></button>\n <button class=\"ophx-image-popover__choice\" type=\"button\" data-align=\"right\" aria-label=\"Прижать вправо\" title=\"Прижать вправо\"><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M20 3v18M4 6h12v5H4zM4 15h12\"/></svg></button>\n </div>\n <div class=\"ophx-image-popover__size\" aria-live=\"polite\"></div>\n ", document.body.append(D);
241
+ let ue = D.querySelector(".ophx-image-popover__size"), O = null, k = () => {
242
+ if (!t.isActive("image")) return null;
243
+ let e = t.view.nodeDOM(t.state.selection.from);
244
+ return e instanceof HTMLElement ? e.querySelector("img") : null;
245
+ }, A = () => {
246
+ let e = k();
247
+ if (D.hidden || !e) return;
248
+ let t = e.getBoundingClientRect(), n = t.left + t.width / 2, r = D.offsetWidth, i = D.offsetHeight, a = Math.max(8, Math.min(n - r / 2, window.innerWidth - r - 8)), o = t.bottom + i + 12 <= window.innerHeight - 8;
249
+ D.style.left = `${a}px`, D.style.top = `${o ? t.bottom + 10 : Math.max(8, t.top - i - 10)}px`, D.dataset.placement = o ? "bottom" : "top", D.style.setProperty("--ophx-image-arrow-left", `${Math.max(14, Math.min(n - a, r - 14))}px`);
250
+ };
251
+ function j() {
252
+ D.hidden = !0, window.removeEventListener("resize", A), document.removeEventListener("scroll", A, !0);
307
253
  }
308
- });
309
- function P(e) {
310
- return x.test(e);
311
- }
312
- // src/editor/ophx-editor.ts
313
- var F = class {
314
- editor;
315
- constructor(t) {
316
- this.editor = new e({
317
- element: t.element,
318
- content: t.content ?? "<p></p>",
319
- editable: t.editable ?? !0,
320
- extensions: [
321
- i.configure({
322
- heading: !1,
323
- underline: !1,
324
- link: !1
325
- }),
326
- D.configure({ levels: [
327
- 1,
328
- 2,
329
- 3,
330
- 4,
331
- 5,
332
- 6
333
- ] }),
334
- a,
335
- o.configure({
336
- openOnClick: !1,
337
- autolink: !0,
338
- linkOnPaste: !0
339
- }),
340
- s.configure({
341
- allowBase64: !0,
342
- inline: !1
343
- }),
344
- l.configure({ resizable: !0 }),
345
- u,
346
- d,
347
- f,
348
- S,
349
- C,
350
- T,
351
- w,
352
- y,
353
- M,
354
- N,
355
- k,
356
- j,
357
- c.configure({ placeholder: t.placeholder ?? "Начните писать…" }),
358
- ...t.extensions ?? []
359
- ],
360
- editorProps: { attributes: {
361
- class: "ophx-editor-surface",
362
- spellcheck: "true",
363
- lang: "ru",
364
- "aria-label": "Редактор документа"
365
- } },
366
- onUpdate: t.onUpdate
367
- });
254
+ function de() {
255
+ if (!t.isEditable || !D.hidden) return;
256
+ let e = k();
257
+ e && (O = t.state.selection.from, D.hidden = !1, N(), A(), e.complete || e.addEventListener("load", () => {
258
+ N(), A();
259
+ }, { once: !0 }), window.addEventListener("resize", A), document.addEventListener("scroll", A, !0));
368
260
  }
369
- getHTML() {
370
- return this.editor.getHTML();
261
+ let M = (e) => {
262
+ O !== null && t.chain().focus().setNodeSelection(O).updateAttributes("image", e).run();
263
+ }, N = () => {
264
+ if (D.hidden || !t.isActive("image")) return;
265
+ let e = t.getAttributes("image"), n = k();
266
+ n && (ue.textContent = `${Math.round(n.getBoundingClientRect().width)} px · потяните за боковой маркер, чтобы изменить размер`);
267
+ for (let t of D.querySelectorAll("[data-wrap]")) {
268
+ let n = t.dataset.wrap === (e.textWrap ?? "none");
269
+ t.classList.toggle("is-selected", n), t.setAttribute("aria-pressed", String(n));
270
+ }
271
+ for (let t of D.querySelectorAll("[data-align]")) {
272
+ let n = t.dataset.align === (e.alignment ?? "left");
273
+ t.classList.toggle("is-selected", n), t.setAttribute("aria-pressed", String(n));
274
+ }
275
+ };
276
+ D.addEventListener("mousedown", (e) => e.stopPropagation()), D.addEventListener("click", (e) => {
277
+ let t = e.target.closest(".ophx-image-popover__choice");
278
+ t && ((t.dataset.wrap === "none" || t.dataset.wrap === "left" || t.dataset.wrap === "right") && M({ textWrap: t.dataset.wrap }), (t.dataset.align === "left" || t.dataset.align === "center" || t.dataset.align === "right") && M({
279
+ alignment: t.dataset.align,
280
+ textWrap: "none"
281
+ }));
282
+ }), D.addEventListener("keydown", (e) => {
283
+ e.key === "Escape" && (e.preventDefault(), j());
284
+ });
285
+ let fe = (e) => {
286
+ let n = e.target, r = e.target.closest?.(".ophx-image-node"), i = !!(r && t.view.dom.contains(r));
287
+ !D.contains(n) && !i && j();
288
+ };
289
+ document.addEventListener("click", fe);
290
+ let P = document.createElement("nav");
291
+ P.className = "ophx-toc", P.setAttribute("aria-label", "Содержание документа"), r(P, !1), e.append(P);
292
+ let F = h.get("toc");
293
+ F && (F.setAttribute("aria-haspopup", "true"), F.setAttribute("aria-expanded", "false"), F.addEventListener("click", () => {
294
+ P.hidden || P.style.display === "none" ? (m(), r(P, !0), F.setAttribute("aria-expanded", "true")) : a();
295
+ }));
296
+ let I = document.createElement("form");
297
+ I.className = "ophx-link-popover", I.setAttribute("role", "dialog"), I.setAttribute("aria-label", "Добавить ссылку"), I.hidden = !0, I.innerHTML = "\n <span class=\"ophx-link-popover__arrow\" aria-hidden=\"true\"></span>\n <label class=\"ophx-link-popover__label\" for=\"ophx-link-url\">Адрес ссылки</label>\n <div class=\"ophx-link-popover__row\">\n <input class=\"ophx-link-popover__input\" id=\"ophx-link-url\" type=\"url\" placeholder=\"https://example.com\" autocomplete=\"url\">\n </div>\n <div class=\"ophx-link-popover__actions\">\n <button class=\"ophx-link-popover__go\" type=\"button\">Перейти</button>\n <button class=\"ophx-link-popover__remove\" type=\"button\">Удалить ссылку</button>\n <button class=\"ophx-link-popover__submit\" type=\"submit\">ОК</button>\n </div>\n ", document.body.append(I);
298
+ let L = I.querySelector(".ophx-link-popover__input"), R = I.querySelector(".ophx-link-popover__go"), pe = I.querySelector(".ophx-link-popover__remove"), z = h.get("link"), B = null, V = () => {
299
+ if (!I.hidden && B) try {
300
+ let e = t.view.coordsAtPos(B.from), n = t.view.coordsAtPos(B.to), r = (e.left + n.right) / 2, i = I.offsetWidth, a = I.offsetHeight, o = Math.max(8, Math.min(r - i / 2, window.innerWidth - i - 8)), s = n.bottom + a + 14 <= window.innerHeight - 8, c = s ? n.bottom + 12 : Math.max(8, e.top - a - 12);
301
+ I.style.left = `${o}px`, I.style.top = `${c}px`, I.dataset.placement = s ? "bottom" : "top", I.style.setProperty("--ophx-link-arrow-left", `${Math.max(14, Math.min(r - o, i - 14))}px`);
302
+ } catch {
303
+ H();
304
+ }
305
+ };
306
+ function H() {
307
+ I.hidden = !0, z.setAttribute("aria-expanded", "false"), window.removeEventListener("resize", V), document.removeEventListener("scroll", V, !0);
371
308
  }
372
- getJSON() {
373
- return this.editor.getJSON();
309
+ function U(e = !0) {
310
+ if (!t.isEditable) return;
311
+ if (!I.hidden && e) {
312
+ H();
313
+ return;
314
+ }
315
+ t.state.selection.empty && t.isActive("link") && t.chain().extendMarkRange("link").run();
316
+ let n = t.state.selection;
317
+ n.empty || (B = {
318
+ from: n.from,
319
+ to: n.to
320
+ }, L.value = t.getAttributes("link").href ?? "", pe.hidden = !t.isActive("link"), R.hidden = !L.value, I.hidden = !1, z.setAttribute("aria-expanded", "true"), V(), window.addEventListener("resize", V), document.addEventListener("scroll", V, !0), L.focus(), L.select());
374
321
  }
375
- setContent(e) {
376
- this.editor.commands.setContent(e, { emitUpdate: !1 });
322
+ z.setAttribute("aria-haspopup", "dialog"), z.setAttribute("aria-expanded", "false");
323
+ let W = (e) => {
324
+ let n = e.target.closest("a[href]");
325
+ n && t.view.dom.contains(n) && (n.title = "⌘/Ctrl+клик — открыть ссылку; клик — выделить для редактирования через кнопку «Ссылка»");
326
+ }, G = (e) => {
327
+ let n = e.target.closest("a[href]");
328
+ if (!n || !t.view.dom.contains(n)) return;
329
+ if (e.preventDefault(), e.stopPropagation(), e.metaKey || e.ctrlKey) {
330
+ ne(n.href);
331
+ return;
332
+ }
333
+ let r = t.view.posAtCoords({
334
+ left: e.clientX,
335
+ top: e.clientY
336
+ });
337
+ r && (t.chain().focus().setTextSelection(r.pos).extendMarkRange("link").run(), U(!1));
338
+ };
339
+ t.view.dom.addEventListener("pointerover", W), t.view.dom.addEventListener("click", G), I.addEventListener("mousedown", (e) => e.stopPropagation()), I.addEventListener("submit", (e) => {
340
+ if (e.preventDefault(), !B) return;
341
+ let n = L.value.trim();
342
+ n ? (t.chain().focus().setTextSelection(B).setLink({ href: n }).run(), H()) : L.focus();
343
+ }), R.addEventListener("click", () => ne(L.value)), L.addEventListener("input", () => {
344
+ R.hidden = !L.value.trim();
345
+ }), pe.addEventListener("click", () => {
346
+ B && (t.chain().focus().setTextSelection(B).extendMarkRange("link").unsetLink().run(), H());
347
+ }), I.addEventListener("keydown", (e) => {
348
+ e.key === "Escape" && (e.preventDefault(), H(), z.focus());
349
+ });
350
+ let K = (e) => {
351
+ let t = e.target;
352
+ !I.contains(t) && !z.contains(t) && H();
353
+ };
354
+ document.addEventListener("click", K);
355
+ let me = ["важно", "идея"], q = document.createElement("form");
356
+ q.className = "ophx-link-popover ophx-label-popover", q.setAttribute("role", "dialog"), q.hidden = !0, q.innerHTML = "\n <span class=\"ophx-link-popover__arrow\" aria-hidden=\"true\"></span>\n <div class=\"ophx-label-popover__view\">\n <span class=\"ophx-link-popover__label\">Метка</span>\n <div class=\"ophx-label-popover__name\"><mark class=\"ophx-label-popover__chip\"></mark></div>\n <div class=\"ophx-link-popover__actions ophx-label-popover__view-actions\">\n <button class=\"ophx-link-popover__remove\" type=\"button\" data-label-remove>Удалить метку</button>\n <button class=\"ophx-link-popover__submit\" type=\"button\" data-label-edit>Изменить</button>\n </div>\n </div>\n <div class=\"ophx-label-popover__edit\">\n <label class=\"ophx-link-popover__label\" for=\"ophx-label-name\">Название метки</label>\n <div class=\"ophx-link-popover__row\">\n <input class=\"ophx-link-popover__input\" id=\"ophx-label-name\" type=\"text\" placeholder=\"Например, важно\" autocomplete=\"off\" maxlength=\"60\">\n </div>\n <div class=\"ophx-label-popover__presets\" role=\"group\" aria-label=\"Быстрый выбор метки\"></div>\n <div class=\"ophx-link-popover__actions\">\n <button class=\"ophx-link-popover__remove\" type=\"button\" data-label-remove>Удалить метку</button>\n <button class=\"ophx-link-popover__submit\" type=\"submit\">ОК</button>\n </div>\n </div>\n ", document.body.append(q);
357
+ let J = q.querySelector("#ophx-label-name"), he = q.querySelector(".ophx-label-popover__chip"), ge = q.querySelector(".ophx-label-popover__presets"), _e = q.querySelector("[data-label-edit]"), ve = q.querySelectorAll("[data-label-remove]"), ye = q.querySelector(".ophx-label-popover__view-actions"), Y = h.get("label"), X = null, be = "edit", xe = !1, Z = () => {
358
+ !q.hidden && X && (te(t, q, X) || Q());
359
+ };
360
+ function Q() {
361
+ q.hidden = !0, Y.setAttribute("aria-expanded", "false"), window.removeEventListener("resize", Z), document.removeEventListener("scroll", Z, !0);
377
362
  }
378
- setSearchQuery(e) {
379
- this.editor.commands.setSearchQuery(e);
363
+ function Se() {
364
+ let e = new Set(me);
365
+ return t.state.doc.descendants((t) => {
366
+ for (let n of t.marks) n.type.name === "label" && n.attrs.label && e.add(String(n.attrs.label));
367
+ }), [...e];
380
368
  }
381
- getFields() {
382
- let e = Array.from(this.editor.view.dom.querySelectorAll("[data-ophx-field]")).filter((e) => {
383
- let t = e.getAttribute("data-ophx-field") ?? "";
384
- return e.classList.contains("ophx-field") && e.getAttribute("contenteditable") === "true" && P(t);
385
- }), t = /* @__PURE__ */ new Map();
386
- for (let n of e) {
387
- let e = n.getAttribute("data-ophx-field");
388
- t.set(e, (t.get(e) ?? 0) + 1);
369
+ function Ce(e) {
370
+ ge.replaceChildren();
371
+ for (let t of Se()) {
372
+ let n = document.createElement("button");
373
+ n.type = "button", n.className = "ophx-label-popover__preset", n.classList.toggle("is-selected", t === e);
374
+ let r = document.createElement("mark");
375
+ r.dataset.label = t, r.textContent = t, n.append(r), n.addEventListener("click", () => Ee(t)), ge.append(n);
389
376
  }
390
- return e.filter((e) => t.get(e.getAttribute("data-ophx-field")) === 1).map((e) => ({
391
- id: e.getAttribute("data-ophx-field"),
392
- label: e.getAttribute("data-ophx-label"),
393
- required: e.getAttribute("data-ophx-required") === "true",
394
- value: e.textContent ?? ""
395
- }));
396
377
  }
397
- getMissingRequiredFields() {
398
- return this.getFields().filter((e) => e.required && !e.value.trim());
378
+ function we(e, n, r) {
379
+ X = e, be = r, q.dataset.mode = r, q.setAttribute("aria-label", r === "view" ? `Метка «${n}»` : "Установить метку"), he.textContent = n, he.dataset.label = n, ye.hidden = !t.isEditable, ve.forEach((e) => {
380
+ e.hidden = !xe;
381
+ }), J.value = n, Ce(n), q.hidden = !1, Y.setAttribute("aria-expanded", String(r === "edit")), Z(), window.addEventListener("resize", Z), document.addEventListener("scroll", Z, !0), r === "edit" && (J.focus(), J.select());
399
382
  }
400
- destroy() {
401
- this.editor.destroy();
383
+ function Te() {
384
+ if (!t.isEditable) return;
385
+ if (!q.hidden && be === "edit") {
386
+ Q();
387
+ return;
388
+ }
389
+ t.state.selection.empty && t.isActive("label") && t.chain().extendMarkRange("label").run();
390
+ let { from: e, to: n, empty: r } = t.state.selection;
391
+ if (r) return;
392
+ xe = t.isActive("label");
393
+ let i = xe ? String(t.getAttributes("label").label ?? "") : "";
394
+ we({
395
+ from: e,
396
+ to: n
397
+ }, i, "edit");
402
398
  }
403
- }, I = [
404
- {
405
- action: "bold",
406
- label: "B",
407
- title: "Жирный (⌘/Ctrl+B)"
408
- },
409
- {
410
- action: "italic",
411
- label: "I",
412
- title: "Курсив (⌘/Ctrl+I)"
413
- },
414
- {
415
- action: "underline",
416
- label: "U",
417
- title: "Подчёркнутый (⌘/Ctrl+U)"
418
- },
419
- {
420
- action: "strike",
421
- label: "S̶",
422
- title: "Зачёркнутый"
423
- },
424
- {
425
- action: "bullet",
426
- label: "• Список",
427
- title: "Маркированный список"
428
- },
429
- {
430
- action: "ordered",
431
- label: "1. Список",
432
- title: "Нумерованный список"
433
- },
434
- {
435
- action: "outdent",
436
- label: "← Отступ",
437
- title: "Уменьшить отступ"
438
- },
439
- {
440
- action: "indent",
441
- label: "Отступ →",
442
- title: "Увеличить отступ"
443
- },
444
- {
445
- action: "link",
446
- label: "Ссылка",
447
- title: "Добавить ссылку"
448
- },
449
- {
450
- action: "image",
451
- label: "Изображение",
452
- title: "Вставить изображение"
453
- },
454
- {
455
- action: "table",
456
- label: "Таблица",
457
- title: "Вставить таблицу"
458
- },
459
- {
460
- action: "collapse",
461
- label: "Свернуть",
462
- title: "Свернуть/развернуть раздел"
463
- },
464
- {
465
- action: "label",
466
- label: "Метка",
467
- title: "Выделить текст меткой"
468
- },
469
- {
470
- action: "toc",
471
- label: "Содержание",
472
- title: "Содержание документа"
473
- },
474
- {
475
- action: "undo",
476
- label: "↶",
477
- title: "Отменить (⌘/Ctrl+Z)"
478
- },
479
- {
480
- action: "redo",
481
- label: "↷",
482
- title: "Повторить (⌘/Ctrl+Shift+Z)"
399
+ function Ee(e) {
400
+ let n = e.trim();
401
+ X && (n ? (t.chain().focus().setTextSelection(X).setMark("label", { label: n }).run(), Q()) : J.focus());
483
402
  }
484
- ];
485
- function L(e, t, n = {}) {
486
- e.classList.add("ophx-toolbar"), e.replaceChildren();
487
- let r = document.createElement("select");
488
- r.className = "ophx-toolbar__style", r.setAttribute("aria-label", "Стиль текста"), r.innerHTML = "<option value=\"p\">Обычный текст</option><option value=\"1\">Заголовок 1</option><option value=\"2\">Заголовок 2</option><option value=\"3\">Заголовок 3</option>", r.addEventListener("change", () => {
489
- let e = Number(r.value), n = t.chain().focus();
490
- r.value === "p" ? n.setParagraph().run() : n.setHeading({ level: e }).run();
491
- }), e.append(r);
492
- let i = /* @__PURE__ */ new Map();
493
- for (let n of I) {
494
- let r = document.createElement("button");
495
- r.type = "button", r.className = "ophx-toolbar__button", r.dataset.action = n.action, r.textContent = n.label, r.title = n.title, r.setAttribute("aria-label", n.title), r.addEventListener("mousedown", (e) => e.preventDefault()), r.addEventListener("click", () => R(n.action, t, o, s)), i.set(n.action, r), e.append(r);
496
- }
497
- let a = document.createElement("input");
498
- a.type = "search", a.className = "ophx-toolbar__search", a.placeholder = "Поиск…", a.setAttribute("aria-label", "Поиск по тексту"), a.addEventListener("input", () => t.commands.setSearchQuery(a.value)), a.addEventListener("keydown", (e) => {
499
- e.key === "Escape" && (a.value = "", t.commands.setSearchQuery(""), t.commands.focus());
500
- }), e.append(a);
501
- let o = document.createElement("input");
502
- o.type = "file", o.accept = "image/*", o.hidden = !0, o.addEventListener("change", () => {
503
- let e = o.files?.[0];
504
- if (e) {
505
- if (n.onInsertImage) n.onInsertImage(e);
506
- else {
507
- let n = new FileReader();
508
- n.addEventListener("load", () => {
509
- typeof n.result == "string" && t.chain().focus().setImage({
510
- src: n.result,
511
- alt: e.name
512
- }).run();
513
- }), n.readAsDataURL(e);
514
- }
403
+ Y.setAttribute("aria-haspopup", "dialog"), Y.setAttribute("aria-expanded", "false");
404
+ let De = (e) => {
405
+ let n = e.target, r = n.closest("mark");
406
+ if (!r || !t.view.dom.contains(r) || n.closest("a[href]")) return null;
407
+ let a = t.view.posAtCoords({
408
+ left: e.clientX,
409
+ top: e.clientY
410
+ }), o = t.schema.marks.label;
411
+ if (!a || !o) return null;
412
+ let s = t.state.doc.resolve(a.pos), c = i(s, o) ?? (a.pos > 0 ? i(t.state.doc.resolve(a.pos - 1), o) : void 0);
413
+ if (!c) return null;
414
+ let l = r.dataset.label ?? "";
415
+ return t.state.doc.nodesBetween(c.from, c.to, (e) => {
416
+ let t = e.marks.find((e) => e.type === o);
417
+ t && (l = String(t.attrs.label ?? l));
418
+ }), {
419
+ range: c,
420
+ name: l
421
+ };
422
+ }, Oe = (e) => {
423
+ let n = e.target.closest("mark[data-label]");
424
+ n && t.view.dom.contains(n) && (n.title = `Метка: ${n.dataset.label}`);
425
+ }, ke = (e) => {
426
+ if (e.metaKey || e.ctrlKey || e.shiftKey) return;
427
+ let n = De(e);
428
+ n && (t.state.selection.empty || !t.isEditable) && (e.stopPropagation(), xe = !0, we(n.range, n.name, "view"));
429
+ };
430
+ t.view.dom.addEventListener("pointerover", Oe), t.view.dom.addEventListener("click", ke), q.addEventListener("mousedown", (e) => e.stopPropagation()), q.addEventListener("submit", (e) => {
431
+ e.preventDefault(), Ee(J.value);
432
+ }), _e.addEventListener("click", () => {
433
+ X && t.isEditable && we(X, he.textContent ?? "", "edit");
434
+ }), ve.forEach((e) => e.addEventListener("click", () => {
435
+ X && (t.chain().focus().setTextSelection(X).unsetMark("label").run(), Q());
436
+ })), q.addEventListener("keydown", (e) => {
437
+ e.key === "Escape" && (e.preventDefault(), Q(), t.commands.focus());
438
+ });
439
+ let Ae = (e) => {
440
+ let t = e.target;
441
+ !q.contains(t) && !Y.contains(t) && Q();
442
+ };
443
+ document.addEventListener("click", Ae);
444
+ let $ = () => {
445
+ let e = t.isEditable;
446
+ s.disabled = !e;
447
+ for (let [t, n] of h) t !== "toc" && (n.disabled = !e);
448
+ h.get("undo").disabled = !e || !t.can().undo(), h.get("redo").disabled = !e || !t.can().redo(), T(), !e || !t.isActive("image") ? j() : D.hidden ? de() : (N(), A()), h.get("bold").classList.toggle("is-active", t.isActive("bold")), h.get("italic").classList.toggle("is-active", t.isActive("italic")), h.get("underline").classList.toggle("is-active", t.isActive("underline")), h.get("strike").classList.toggle("is-active", t.isActive("strike")), h.get("label").classList.toggle("is-active", t.isActive("label")), e || H(), !e && be === "edit" ? Q() : q.hidden || Z();
449
+ let n = t.getAttributes("heading").level, r = n ? String(n) : (t.isActive("paragraph"), "p"), i = n ? `Заголовок ${n}` : "Обычный текст";
450
+ c.textContent = i;
451
+ for (let e of l.querySelectorAll(".ophx-toolbar__style-option")) {
452
+ let t = e.dataset.value === r;
453
+ e.setAttribute("aria-selected", String(t)), e.classList.toggle("is-selected", t);
515
454
  }
516
- o.value = "";
517
- }), e.append(o);
518
- let s = document.createElement("nav");
519
- s.className = "ophx-toc", s.setAttribute("aria-label", "Содержание документа"), s.hidden = !0, e.append(s);
520
- let c = () => {
521
- i.get("undo").disabled = !t.can().undo(), i.get("redo").disabled = !t.can().redo(), i.get("bold").classList.toggle("is-active", t.isActive("bold")), i.get("italic").classList.toggle("is-active", t.isActive("italic")), i.get("underline").classList.toggle("is-active", t.isActive("underline")), i.get("strike").classList.toggle("is-active", t.isActive("strike"));
522
- let e = t.getAttributes("heading").level;
523
- r.value = e ? String(e) : (t.isActive("paragraph"), "p"), s.replaceChildren(), t.state.doc.descendants((e, n) => {
455
+ P.replaceChildren(), t.state.doc.descendants((e, n) => {
524
456
  if (e.type.name !== "heading") return;
525
457
  let r = document.createElement("button");
526
458
  r.type = "button", r.className = "ophx-toc__item", r.style.paddingInlineStart = `${Math.max(0, Number(e.attrs.level) - 1) * 12}px`, r.textContent = e.textContent || "Без названия", r.addEventListener("mousedown", (e) => e.preventDefault()), r.addEventListener("click", () => {
527
459
  t.commands.setTextSelection(n + 1), t.commands.focus(), t.commands.scrollIntoView();
528
- }), s.append(r);
460
+ }), P.append(r);
529
461
  });
462
+ }, je = ({ transaction: e }) => {
463
+ if ($(), !e.docChanged) return;
464
+ let n = re(e.before, e.doc);
465
+ n !== null && t.commands.setNodeSelection(n);
466
+ }, Me = () => $();
467
+ return t.on("transaction", je), t.on("selectionUpdate", Me), t.view.dom.addEventListener("ophx:editablechange", $), $(), () => {
468
+ t.off("transaction", je), t.off("selectionUpdate", Me), t.view.dom.removeEventListener("ophx:editablechange", $), document.removeEventListener("click", ce), S(), v.remove(), document.removeEventListener("click", E), window.removeEventListener("resize", w), document.removeEventListener("scroll", w, !0), C.remove(), document.removeEventListener("click", fe), j(), D.remove(), t.view.dom.removeEventListener("pointerover", W), t.view.dom.removeEventListener("click", G), document.removeEventListener("click", K), H(), I.remove(), t.view.dom.removeEventListener("pointerover", Oe), t.view.dom.removeEventListener("click", ke), document.removeEventListener("click", Ae), Q(), q.remove(), e.replaceChildren(), e.classList.remove("ophx-toolbar");
530
469
  };
531
- return t.on("transaction", c), t.on("selectionUpdate", c), c(), () => {
532
- t.off("transaction", c), t.off("selectionUpdate", c), e.replaceChildren(), e.classList.remove("ophx-toolbar");
533
- };
534
470
  }
535
- function R(e, t, n, r) {
471
+ function te(e, t, n) {
472
+ try {
473
+ let r = e.view.coordsAtPos(n.from), i = e.view.coordsAtPos(n.to), a = (r.left + i.right) / 2, o = t.offsetWidth, s = t.offsetHeight, c = Math.max(8, Math.min(a - o / 2, window.innerWidth - o - 8)), l = i.bottom + s + 14 <= window.innerHeight - 8, u = l ? i.bottom + 12 : Math.max(8, r.top - s - 12);
474
+ return t.style.left = `${c}px`, t.style.top = `${u}px`, t.dataset.placement = l ? "bottom" : "top", t.style.setProperty("--ophx-link-arrow-left", `${Math.max(14, Math.min(a - c, o - 14))}px`), !0;
475
+ } catch {
476
+ return !1;
477
+ }
478
+ }
479
+ function ne(e) {
480
+ try {
481
+ let t = new URL(e.trim(), window.location.href);
482
+ [
483
+ "http:",
484
+ "https:",
485
+ "mailto:"
486
+ ].includes(t.protocol) && window.open(t.href, "_blank", "noopener,noreferrer");
487
+ } catch {}
488
+ }
489
+ function re(e, t) {
490
+ let n = /* @__PURE__ */ new Map();
491
+ e.descendants((e) => {
492
+ if (e.type.name !== "image") return;
493
+ let t = String(e.attrs.src ?? "");
494
+ n.set(t, (n.get(t) ?? 0) + 1);
495
+ });
496
+ let r = null;
497
+ return t.descendants((e, t) => {
498
+ if (e.type.name !== "image" || r !== null) return;
499
+ let i = String(e.attrs.src ?? ""), a = n.get(i) ?? 0;
500
+ a > 0 ? n.set(i, a - 1) : r = t;
501
+ }), r;
502
+ }
503
+ function ie(e, t, n, r) {
536
504
  let i = t.chain().focus();
537
505
  switch (e) {
538
506
  case "bold":
@@ -554,38 +522,14 @@ function R(e, t, n, r) {
554
522
  i.toggleOrderedList().run();
555
523
  break;
556
524
  case "indent":
557
- z(t, 1);
525
+ ae(t, 1);
558
526
  break;
559
527
  case "outdent":
560
- z(t, -1);
528
+ ae(t, -1);
561
529
  break;
562
- case "link": {
563
- let e = t.getAttributes("link").href, n = window.prompt("Адрес ссылки", e ?? "https://");
564
- if (n === null) return;
565
- n.trim() ? i.extendMarkRange("link").setLink({ href: n.trim() }).run() : i.extendMarkRange("link").unsetLink().run();
566
- break;
567
- }
568
530
  case "image":
569
531
  n.click();
570
532
  break;
571
- case "table":
572
- i.insertTable({
573
- rows: 3,
574
- cols: 3,
575
- withHeaderRow: !0
576
- }).run();
577
- break;
578
- case "collapse": {
579
- if (!t.isActive("heading")) return;
580
- let e = !!t.getAttributes("heading").collapsed;
581
- i.updateAttributes("heading", { collapsed: !e }).run();
582
- break;
583
- }
584
- case "label": {
585
- let e = window.prompt("Название метки", "важно");
586
- e?.trim() && i.setMark("label", { label: e.trim() }).run();
587
- break;
588
- }
589
533
  case "toc":
590
534
  r.hidden = !r.hidden;
591
535
  break;
@@ -595,7 +539,7 @@ function R(e, t, n, r) {
595
539
  case "redo": i.redo().run();
596
540
  }
597
541
  }
598
- function z(e, t) {
542
+ function ae(e, t) {
599
543
  let { $from: n } = e.state.selection;
600
544
  if (n.parent.type.name === "listItem") {
601
545
  let n = e.chain().focus();
@@ -608,7 +552,7 @@ function z(e, t) {
608
552
  e.chain().focus().updateAttributes(r, { indent: Math.max(0, Math.min(8, i + t)) }).run();
609
553
  }
610
554
  // src/editor/ophx-archive.ts
611
- var B = {
555
+ var oe = {
612
556
  avif: "image/avif",
613
557
  bmp: "image/bmp",
614
558
  gif: "image/gif",
@@ -621,10 +565,10 @@ var B = {
621
565
  tiff: "image/tiff",
622
566
  webp: "image/webp"
623
567
  };
624
- async function V(e, t = {}) {
625
- let n = new v(), r = new DOMParser().parseFromString(e, "text/html");
626
- G(r);
627
- let i = K(t.filename ?? t.title ?? "Документ"), a = t.title ?? r.title ?? i, o = t.assets ?? {}, s = new Set(Object.keys(o).map(J)), c = 0;
568
+ async function x(e, t = {}) {
569
+ let n = new p(), r = new DOMParser().parseFromString(e, "text/html");
570
+ C(r);
571
+ let i = le(t.filename ?? t.title ?? "Документ"), a = t.title ?? r.title ?? i, o = t.assets ?? {}, s = new Set(Object.keys(o).map(T)), c = 0;
628
572
  for (let e of Array.from(r.querySelectorAll("img[src]"))) {
629
573
  let t = e.getAttribute("src")?.trim();
630
574
  if (!t || /^https?:\/\//i.test(t) || /^data:(?!image\/)/i.test(t)) continue;
@@ -634,35 +578,35 @@ async function V(e, t = {}) {
634
578
  if (!r.ok) throw Error(`Не удалось прочитать изображение: ${t}`);
635
579
  let i = await r.blob();
636
580
  if (!i.type.startsWith("image/")) throw Error("Вложение с data/blob URL не является изображением");
637
- let a = `image-${++c}.${Z(i.type, t)}`;
638
- for (; s.has(a);) a = `image-${++c}.${Z(i.type, t)}`;
581
+ let a = `image-${++c}.${ue(i.type, t)}`;
582
+ for (; s.has(a);) a = `image-${++c}.${ue(i.type, t)}`;
639
583
  n.file(a, i), s.add(a), e.setAttribute("src", encodeURIComponent(a));
640
584
  } catch (e) {
641
585
  throw Error("Не удалось добавить изображение в архив ophx", { cause: e });
642
586
  }
643
587
  continue;
644
588
  }
645
- let r = J(t);
646
- if (!q(r)) throw Error(`Путь изображения должен быть простым именем файла: ${t}`);
589
+ let r = T(t);
590
+ if (!w(r)) throw Error(`Путь изображения должен быть простым именем файла: ${t}`);
647
591
  let i = o[r];
648
592
  if (!i && !s.has(r)) throw Error(`Для локального изображения «${r}» не передан файл media`);
649
593
  i && n.file(r, i), e.setAttribute("src", encodeURIComponent(r));
650
594
  }
651
- let l = W(t.styles ?? {});
595
+ let l = ce(t.styles ?? {});
652
596
  for (let [e, t] of Object.entries(l)) n.file(e, t);
653
- let u = Object.keys(l).map((e) => `<link rel="stylesheet" href="${$(encodeURIComponent(e))}">`).join(""), d = r.documentElement.lang || "ru", f = t.bodyContentEditable ? ` contenteditable="${t.bodyContentEditable}"` : "", p = `<!doctype html>\n<html lang="${$(d)}"><head><meta charset="utf-8"><title>${$(a)}</title>${u}</head><body${f}>${r.body.innerHTML}</body></html>`;
654
- return n.file(`${i}.html`, p), n.generateAsync({
597
+ let u = Object.keys(l).map((e) => `<link rel="stylesheet" href="${k(encodeURIComponent(e))}">`).join(""), d = r.documentElement.lang || "ru", f = t.bodyContentEditable ? ` contenteditable="${t.bodyContentEditable}"` : "", m = `<!doctype html>\n<html lang="${k(d)}"><head><meta charset="utf-8"><title>${k(a)}</title>${u}</head><body${f}>${r.body.innerHTML}</body></html>`;
598
+ return n.file(`${i}.html`, m), n.generateAsync({
655
599
  type: "blob",
656
600
  mimeType: "application/zip",
657
601
  compression: "DEFLATE",
658
602
  compressionOptions: { level: 6 }
659
603
  });
660
604
  }
661
- async function H(e) {
662
- await X(e);
605
+ async function S(e) {
606
+ await D(e);
663
607
  let t;
664
608
  try {
665
- t = await v.loadAsync(e);
609
+ t = await p.loadAsync(e);
666
610
  } catch (e) {
667
611
  throw Error("Не удалось прочитать ZIP-архив .ophx (шифрование и неподдерживаемые ZIP-режимы не поддерживаются)", { cause: e });
668
612
  }
@@ -679,26 +623,26 @@ async function H(e) {
679
623
  for (let e of Array.from(c.querySelectorAll("head link[rel~=\"stylesheet\"][href]"))) {
680
624
  let n = e.getAttribute("href")?.trim();
681
625
  if (!n) continue;
682
- if (Y(n)) throw Error(`CSS должен быть встроенным плоским файлом архива: ${n}`);
683
- let r = J(n);
684
- if (!q(r) || !r.toLowerCase().endsWith(".css")) throw Error(`Ссылка на стиль должна указывать на плоский .css-файл: ${n}`);
626
+ if (E(n)) throw Error(`CSS должен быть встроенным плоским файлом архива: ${n}`);
627
+ let r = T(n);
628
+ if (!w(r) || !r.toLowerCase().endsWith(".css")) throw Error(`Ссылка на стиль должна указывать на плоский .css-файл: ${n}`);
685
629
  let i = t.file(r);
686
630
  if (!i) throw Error(`Файл стиля «${r}» отсутствует в архиве`);
687
631
  l[r] = await i.async("string");
688
632
  }
689
633
  for (let e of Array.from(c.querySelectorAll("img[src]"))) {
690
634
  let n = e.getAttribute("src")?.trim();
691
- if (!n || Y(n)) continue;
692
- let r = J(n);
693
- if (!q(r)) throw Error(`Ссылка на изображение должна быть простым именем файла: ${n}`);
635
+ if (!n || E(n)) continue;
636
+ let r = T(n);
637
+ if (!w(r)) throw Error(`Ссылка на изображение должна быть простым именем файла: ${n}`);
694
638
  let i = t.file(r);
695
639
  if (!i) throw Error(`Файл изображения «${r}» отсутствует в архиве`);
696
- let a = await i.async("base64"), o = r.split(".").pop()?.toLowerCase() ?? "", s = B[o] ?? (o ? `image/${o}` : "application/octet-stream");
640
+ let a = await i.async("base64"), o = r.split(".").pop()?.toLowerCase() ?? "", s = oe[o] ?? (o ? `image/${o}` : "application/octet-stream");
697
641
  e.setAttribute("src", `data:${s};base64,${a}`);
698
642
  }
699
643
  let u = c.body.getAttribute("contenteditable");
700
644
  if (u === "false") for (let e of Array.from(c.body.children)) e.hasAttribute("contenteditable") || e.setAttribute("contenteditable", "false");
701
- Q(c);
645
+ O(c);
702
646
  let d = Object.entries(l);
703
647
  return {
704
648
  html: c.body.innerHTML,
@@ -709,21 +653,21 @@ async function H(e) {
709
653
  bodyContentEditable: u === "true" || u === "false" ? u : void 0
710
654
  };
711
655
  }
712
- function U(e, t) {
656
+ function se(e, t) {
713
657
  let n = URL.createObjectURL(e), r = window.document.createElement("a");
714
658
  r.href = n, r.download = t, r.click(), window.setTimeout(() => URL.revokeObjectURL(n), 0);
715
659
  }
716
- function W(e) {
660
+ function ce(e) {
717
661
  let t = {};
718
662
  for (let [n, r] of Object.entries(e)) {
719
- let e = J(n);
720
- if (!q(e) || !e.toLowerCase().endsWith(".css")) throw Error(`Имя файла стиля должно быть плоским и оканчиваться на .css: ${n}`);
663
+ let e = T(n);
664
+ if (!w(e) || !e.toLowerCase().endsWith(".css")) throw Error(`Имя файла стиля должно быть плоским и оканчиваться на .css: ${n}`);
721
665
  if (Object.hasOwn(t, e)) throw Error(`Повторяется имя файла стиля: ${e}`);
722
666
  t[e] = r;
723
667
  }
724
668
  return t;
725
669
  }
726
- function G(e) {
670
+ function C(e) {
727
671
  let t = Array.from(e.querySelectorAll("[data-ophx-field], .ophx-field")), n = /* @__PURE__ */ new Set();
728
672
  for (let e of t) {
729
673
  let t = e.getAttribute("data-ophx-field") ?? "", r = e.tagName.toLowerCase();
@@ -738,42 +682,42 @@ function G(e) {
738
682
  n.add(t);
739
683
  }
740
684
  }
741
- function K(e) {
685
+ function le(e) {
742
686
  let t = e.trim().replace(/\.(?:ophx|html)$/i, "");
743
687
  if (!t) return "Документ";
744
- if (!q(t) || /[\u0000-\u001f]/.test(t)) throw Error("Имя .ophx должно быть простым именем файла без каталогов");
688
+ if (!w(t) || /[\u0000-\u001f]/.test(t)) throw Error("Имя .ophx должно быть простым именем файла без каталогов");
745
689
  return t;
746
690
  }
747
- function q(e) {
691
+ function w(e) {
748
692
  return !!e && e !== "." && e !== ".." && !/[\\/]/.test(e);
749
693
  }
750
- function J(e) {
694
+ function T(e) {
751
695
  try {
752
696
  return decodeURIComponent(e);
753
697
  } catch {
754
698
  throw Error(`Некорректное percent-экранирование в имени ресурса: ${e}`);
755
699
  }
756
700
  }
757
- function Y(e) {
701
+ function E(e) {
758
702
  return /^(?:data:|https?:\/\/)/i.test(e);
759
703
  }
760
- async function X(e) {
704
+ async function D(e) {
761
705
  if (e.size < 2) throw Error("Файл не является ZIP-архивом .ophx (возможен устаревший формат)");
762
706
  let t = new Uint8Array(await e.slice(0, 4).arrayBuffer());
763
707
  if (t[0] !== 80 || t[1] !== 75) throw Error("Неподдерживаемый устаревший ophx: ожидается ZIP-архив версии v2");
764
708
  }
765
- function Z(e, t) {
766
- let n = Object.entries(B).find(([, t]) => t === e)?.[0];
709
+ function ue(e, t) {
710
+ let n = Object.entries(oe).find(([, t]) => t === e)?.[0];
767
711
  if (n) return n;
768
712
  let r = t.match(/\.([a-z0-9]+)(?:[?#]|$)/i)?.[1]?.toLowerCase();
769
713
  return r && /^[a-z0-9]{1,6}$/.test(r) ? r : "bin";
770
714
  }
771
- function Q(e) {
715
+ function O(e) {
772
716
  e.querySelectorAll("script, iframe, object, embed, style, link").forEach((e) => e.remove()), e.querySelectorAll("*").forEach((e) => {
773
717
  for (let t of Array.from(e.attributes)) /^on/i.test(t.name) && e.removeAttribute(t.name), /^(href|src|xlink:href)$/i.test(t.name) && /^\s*(javascript|vbscript):/i.test(t.value) && e.removeAttribute(t.name), /^(src|xlink:href)$/i.test(t.name) && /^\s*data:(?!image\/)/i.test(t.value) && e.removeAttribute(t.name);
774
718
  });
775
719
  }
776
- function $(e) {
720
+ function k(e) {
777
721
  return e.replace(/[&<>"']/g, (e) => ({
778
722
  "&": "&amp;",
779
723
  "<": "&lt;",
@@ -782,8 +726,611 @@ function $(e) {
782
726
  "'": "&#39;"
783
727
  })[e]);
784
728
  }
729
+ // src/editor/extensions.ts
730
+ var A = n.create({
731
+ name: "label",
732
+ inclusive: !1,
733
+ addAttributes() {
734
+ return { label: {
735
+ default: "highlight",
736
+ parseHTML: (e) => e.dataset.label ?? "highlight",
737
+ renderHTML: (e) => ({ "data-label": e.label })
738
+ } };
739
+ },
740
+ parseHTML() {
741
+ return [{ tag: "mark[data-label]" }, { tag: "mark" }];
742
+ },
743
+ renderHTML({ HTMLAttributes: e }) {
744
+ return [
745
+ "mark",
746
+ e,
747
+ 0
748
+ ];
749
+ }
750
+ });
751
+ function j() {
752
+ return {
753
+ fieldId: {
754
+ default: "",
755
+ parseHTML: (e) => e.getAttribute("data-ophx-field") ?? "",
756
+ renderHTML: (e) => ({ "data-ophx-field": e.fieldId ?? "" })
757
+ },
758
+ fieldLabel: {
759
+ default: null,
760
+ parseHTML: (e) => e.getAttribute("data-ophx-label"),
761
+ renderHTML: (e) => e.fieldLabel ? { "data-ophx-label": e.fieldLabel } : {}
762
+ },
763
+ required: {
764
+ default: !1,
765
+ parseHTML: (e) => e.getAttribute("data-ophx-required") === "true",
766
+ renderHTML: (e) => e.required ? { "data-ophx-required": "true" } : {}
767
+ }
768
+ };
769
+ }
770
+ var de = /^[A-Za-z0-9._-]+$/, M = 50, N = 1200;
771
+ function fe(e) {
772
+ if (!e || !/^\d+$/.test(e)) return null;
773
+ let t = Number(e);
774
+ return Number.isInteger(t) && t >= M && t <= N ? t : null;
775
+ }
776
+ var P = h.extend({
777
+ addAttributes() {
778
+ return {
779
+ ...this.parent?.(),
780
+ width: {
781
+ default: null,
782
+ parseHTML: (e) => fe(e.getAttribute("width")),
783
+ renderHTML: (e) => {
784
+ let t = e.width;
785
+ return Number.isInteger(t) && Number(t) >= M && Number(t) <= N ? { width: String(t) } : {};
786
+ }
787
+ },
788
+ textWrap: {
789
+ default: "none",
790
+ parseHTML: (e) => {
791
+ let t = e.getAttribute("data-ophx-wrap");
792
+ return t === "left" || t === "right" ? t : "none";
793
+ },
794
+ renderHTML: (e) => ({ "data-ophx-wrap": e.textWrap === "left" || e.textWrap === "right" ? e.textWrap : "none" })
795
+ },
796
+ alignment: {
797
+ default: "left",
798
+ parseHTML: (e) => {
799
+ let t = e.getAttribute("data-ophx-align");
800
+ return t === "center" || t === "right" ? t : "left";
801
+ },
802
+ renderHTML: (e) => ({ "data-ophx-align": e.alignment === "center" || e.alignment === "right" ? e.alignment : "left" })
803
+ }
804
+ };
805
+ },
806
+ addNodeView() {
807
+ return ({ node: e, getPos: t, editor: n }) => {
808
+ let r = e, i = 0, a = 0, o = 0, s = document.createElement("div");
809
+ s.className = "ophx-image-node", s.contentEditable = "false";
810
+ let c = document.createElement("img"), l = document.createElement("button");
811
+ l.type = "button", l.className = "ophx-image-node__resize-handle", l.setAttribute("role", "slider"), l.setAttribute("aria-label", "Изменить размер изображения перетаскиванием"), l.setAttribute("aria-valuemin", String(M)), l.setAttribute("aria-valuemax", String(N)), l.title = "Потяните влево или вправо, чтобы изменить размер", l.innerHTML = "<svg viewBox=\"0 0 12 28\" aria-hidden=\"true\"><path d=\"M4 3v22M8 3v22\"/></svg>", s.append(c, l);
812
+ let u = () => {
813
+ let e = String(r.attrs.src ?? "");
814
+ c.getAttribute("src") !== e && c.setAttribute("src", e), c.alt = r.attrs.alt ?? "", r.attrs.title ? c.title = r.attrs.title : c.removeAttribute("title"), r.attrs.width ? c.width = Number(r.attrs.width) : c.removeAttribute("width");
815
+ let t = Number(r.attrs.width) || Math.round(c.getBoundingClientRect().width);
816
+ l.setAttribute("aria-valuenow", String(t)), l.setAttribute("aria-valuetext", `${t} пикселей`), s.dataset.ophxWrap = r.attrs.textWrap ?? "none", s.dataset.ophxAlign = r.attrs.alignment ?? "left";
817
+ }, d = (e) => {
818
+ if (!n.isEditable) return;
819
+ let r = typeof t == "function" ? t() : void 0;
820
+ if (typeof r != "number") return;
821
+ let i = n.state.doc.nodeAt(r);
822
+ if (!i || i.type.name !== "image") return;
823
+ let a = getComputedStyle(n.view.dom), o = Number.parseFloat(a.paddingLeft) + Number.parseFloat(a.paddingRight), s = Math.max(M, n.view.dom.clientWidth - o), c = Math.min(e, N, Math.floor(s));
824
+ n.view.dispatch(n.state.tr.setNodeMarkup(r, void 0, {
825
+ ...i.attrs,
826
+ width: c
827
+ }));
828
+ }, f = (e) => {
829
+ if (!l.hasPointerCapture(e.pointerId)) return;
830
+ let t = Math.max(50, Math.round((a + e.clientX - i) / 5) * 5);
831
+ t !== o && (o = t, d(t));
832
+ }, p = (e) => {
833
+ l.hasPointerCapture(e.pointerId) && l.releasePointerCapture(e.pointerId);
834
+ };
835
+ return l.addEventListener("pointerdown", (e) => {
836
+ n.isEditable && (e.preventDefault(), e.stopPropagation(), i = e.clientX, a = c.getBoundingClientRect().width, o = Math.round(a), l.setPointerCapture(e.pointerId));
837
+ }), l.addEventListener("pointermove", f), l.addEventListener("pointerup", p), l.addEventListener("pointercancel", p), l.addEventListener("keydown", (e) => {
838
+ if (!n.isEditable || e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
839
+ e.preventDefault();
840
+ let t = Number(r.attrs.width) || Math.round(c.getBoundingClientRect().width), i = e.key === "ArrowRight" ? 10 : -10;
841
+ d(Math.max(M, t + i));
842
+ }), c.addEventListener("load", () => {
843
+ if (r.attrs.width) return;
844
+ let e = Math.round(c.getBoundingClientRect().width);
845
+ l.setAttribute("aria-valuenow", String(e)), l.setAttribute("aria-valuetext", `${e} пикселей`);
846
+ }), u(), {
847
+ dom: s,
848
+ update(e) {
849
+ return e.type === r.type && (r = e, u(), !0);
850
+ },
851
+ stopEvent(e) {
852
+ return l.contains(e.target);
853
+ },
854
+ ignoreMutation() {
855
+ return !0;
856
+ }
857
+ };
858
+ };
859
+ }
860
+ }), F = r.create({
861
+ name: "ophxInlineField",
862
+ priority: 1e3,
863
+ group: "inline",
864
+ inline: !0,
865
+ content: "text*",
866
+ defining: !0,
867
+ addAttributes: j,
868
+ parseHTML() {
869
+ return [{
870
+ tag: "span.ophx-field[contenteditable=\"true\"][data-ophx-field]",
871
+ priority: 1e3
872
+ }];
873
+ },
874
+ renderHTML({ HTMLAttributes: e }) {
875
+ return [
876
+ "span",
877
+ {
878
+ ...e,
879
+ class: "ophx-field",
880
+ contenteditable: "true"
881
+ },
882
+ 0
883
+ ];
884
+ }
885
+ }), I = r.create({
886
+ name: "ophxBlockField",
887
+ priority: 1e3,
888
+ group: "block",
889
+ content: "text*",
890
+ defining: !0,
891
+ addAttributes() {
892
+ return {
893
+ ...j(),
894
+ tagName: {
895
+ default: "div",
896
+ parseHTML: (e) => e.tagName.toLowerCase(),
897
+ renderHTML: () => ({})
898
+ }
899
+ };
900
+ },
901
+ parseHTML() {
902
+ return [{
903
+ tag: "p.ophx-field[contenteditable=\"true\"][data-ophx-field]",
904
+ priority: 1e3
905
+ }, {
906
+ tag: "div.ophx-field[contenteditable=\"true\"][data-ophx-field]",
907
+ priority: 1e3
908
+ }];
909
+ },
910
+ renderHTML({ node: e, HTMLAttributes: t }) {
911
+ return [
912
+ e.attrs.tagName === "p" ? "p" : "div",
913
+ {
914
+ ...t,
915
+ class: "ophx-field",
916
+ contenteditable: "true"
917
+ },
918
+ 0
919
+ ];
920
+ }
921
+ }), L = n.create({
922
+ name: "ophxReadonlyInline",
923
+ inclusive: !1,
924
+ addAttributes() {
925
+ return { className: {
926
+ default: null,
927
+ parseHTML: (e) => e.getAttribute("class"),
928
+ renderHTML: (e) => e.className ? { class: e.className } : {}
929
+ } };
930
+ },
931
+ parseHTML() {
932
+ return [{
933
+ tag: "span[contenteditable=\"false\"]",
934
+ priority: 100
935
+ }];
936
+ },
937
+ renderHTML({ HTMLAttributes: e }) {
938
+ return [
939
+ "span",
940
+ {
941
+ ...e,
942
+ contenteditable: "false"
943
+ },
944
+ 0
945
+ ];
946
+ }
947
+ }), R = r.create({
948
+ name: "ophxReadonlyBlock",
949
+ priority: 1e3,
950
+ group: "block",
951
+ content: "block*",
952
+ defining: !0,
953
+ isolating: !0,
954
+ addAttributes() {
955
+ return {
956
+ id: {
957
+ default: null,
958
+ parseHTML: (e) => e.getAttribute("id"),
959
+ renderHTML: (e) => e.id ? { id: e.id } : {}
960
+ },
961
+ className: {
962
+ default: null,
963
+ parseHTML: (e) => e.getAttribute("class"),
964
+ renderHTML: (e) => e.className ? { class: e.className } : {}
965
+ },
966
+ title: {
967
+ default: null,
968
+ parseHTML: (e) => e.getAttribute("title"),
969
+ renderHTML: (e) => e.title ? { title: e.title } : {}
970
+ }
971
+ };
972
+ },
973
+ parseHTML() {
974
+ return [{
975
+ tag: "div[contenteditable=\"false\"]",
976
+ priority: 1e3
977
+ }];
978
+ },
979
+ renderHTML({ HTMLAttributes: e }) {
980
+ return [
981
+ "div",
982
+ {
983
+ ...e,
984
+ contenteditable: "false"
985
+ },
986
+ 0
987
+ ];
988
+ }
989
+ }), pe = new _("ophxHeadingCollapse"), z = m.extend({
990
+ addAttributes() {
991
+ return {
992
+ ...this.parent?.(),
993
+ collapsed: {
994
+ default: !1,
995
+ parseHTML: (e) => e.dataset.collapsed === "true",
996
+ renderHTML: (e) => e.collapsed ? { "data-collapsed": "true" } : {}
997
+ }
998
+ };
999
+ },
1000
+ addNodeView() {
1001
+ return ({ node: e, getPos: t, editor: n }) => {
1002
+ let r = e, i = document.createElement(`h${e.attrs.level}`);
1003
+ i.className = "ophx-heading";
1004
+ let a = document.createElement("button");
1005
+ a.type = "button", a.className = "ophx-heading__toggle", a.innerHTML = "<svg viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"m5.5 3.5 4.5 4.5-4.5 4.5\"/></svg>";
1006
+ let o = document.createElement("span");
1007
+ o.className = "ophx-heading__content", i.append(o, a);
1008
+ let s = () => {
1009
+ let e = !!r.attrs.collapsed;
1010
+ a.setAttribute("aria-expanded", String(!e)), a.setAttribute("aria-label", e ? "Развернуть раздел" : "Свернуть раздел"), a.title = e ? "Развернуть раздел" : "Свернуть раздел", i.dataset.collapsed = String(e);
1011
+ };
1012
+ return a.addEventListener("mousedown", (e) => e.preventDefault()), a.addEventListener("click", (e) => {
1013
+ if (e.preventDefault(), e.stopPropagation(), !n.isEditable) return;
1014
+ let r = typeof t == "function" ? t() : void 0;
1015
+ if (typeof r != "number") return;
1016
+ let i = n.state.doc.nodeAt(r);
1017
+ i && i.type.name === "heading" && n.view.dispatch(n.state.tr.setNodeMarkup(r, void 0, {
1018
+ ...i.attrs,
1019
+ collapsed: !i.attrs.collapsed
1020
+ }));
1021
+ }), s(), {
1022
+ dom: i,
1023
+ contentDOM: o,
1024
+ update(e) {
1025
+ return e.type !== r.type || e.attrs.level !== r.attrs.level ? !1 : (r = e, s(), !0);
1026
+ },
1027
+ stopEvent(e) {
1028
+ return a.contains(e.target);
1029
+ }
1030
+ };
1031
+ };
1032
+ },
1033
+ addProseMirrorPlugins() {
1034
+ return [new g({
1035
+ key: pe,
1036
+ props: { decorations(e) {
1037
+ let t = [], n = null;
1038
+ return e.doc.forEach((e, r) => {
1039
+ if (e.type.name === "heading") {
1040
+ let i = Number(e.attrs.level);
1041
+ n !== null && i <= n && (n = null), n !== null && t.push(v.node(r, r + e.nodeSize, { class: "ophx-collapsed-content" })), e.attrs.collapsed && (n = i);
1042
+ } else n !== null && t.push(v.node(r, r + e.nodeSize, { class: "ophx-collapsed-content" }));
1043
+ }), y.create(e.doc, t);
1044
+ } }
1045
+ })];
1046
+ }
1047
+ }), B = new _("ophxTextSearch"), V = t.create({
1048
+ name: "ophxSearch",
1049
+ addStorage() {
1050
+ return { query: "" };
1051
+ },
1052
+ addCommands() {
1053
+ return { setSearchQuery: (e) => ({ tr: t, dispatch: n }) => (this.storage.query = e, n?.(t.setMeta(B, e)), !0) };
1054
+ },
1055
+ addProseMirrorPlugins() {
1056
+ let e = this;
1057
+ return [new g({
1058
+ key: B,
1059
+ state: {
1060
+ init: (t, n) => H(n.doc, e.storage.query),
1061
+ apply: (t, n, r, i) => {
1062
+ let a = t.getMeta(B);
1063
+ return a !== void 0 && (e.storage.query = a), !t.docChanged && a === void 0 ? n : H(i.doc, e.storage.query);
1064
+ }
1065
+ },
1066
+ props: { decorations(e) {
1067
+ return B.getState(e);
1068
+ } }
1069
+ })];
1070
+ }
1071
+ });
1072
+ function H(e, t) {
1073
+ if (!t.trim()) return y.empty;
1074
+ let n = [], r = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), i = new RegExp(r, "gi");
1075
+ return e.descendants((e, t) => {
1076
+ if (e.isText && e.text) {
1077
+ i.lastIndex = 0;
1078
+ for (let r of e.text.matchAll(i)) r.index !== void 0 && n.push(v.inline(t + r.index, t + r.index + r[0].length, { class: "ophx-search-match" }));
1079
+ }
1080
+ }), y.create(e, n);
1081
+ }
1082
+ var U = t.create({
1083
+ name: "ophxWysiwymRules",
1084
+ addProseMirrorPlugins() {
1085
+ return [new g({ props: {
1086
+ handleTextInput(e, t, n, r) {
1087
+ return r === " " && /\s/.test(e.state.doc.textBetween(Math.max(0, t - 1), t));
1088
+ },
1089
+ handleKeyDown(e, t) {
1090
+ if (t.key !== "Enter" || t.shiftKey) return !1;
1091
+ let { $from: n } = e.state.selection;
1092
+ if (!n.parent.isTextblock || n.parent.content.size !== 0 || n.depth === 0) return !1;
1093
+ let r = n.index(n.depth - 1), i = n.node(n.depth - 1);
1094
+ return [i.maybeChild(r - 1), i.maybeChild(r + 1)].some((e) => e?.isTextblock && e.content.size === 0);
1095
+ }
1096
+ } })];
1097
+ }
1098
+ }), W = t.create({
1099
+ name: "ophxIndentation",
1100
+ addGlobalAttributes() {
1101
+ return [{
1102
+ types: ["paragraph", "heading"],
1103
+ attributes: { indent: {
1104
+ default: 0,
1105
+ parseHTML: (e) => Number(e.dataset.indent ?? 0),
1106
+ renderHTML: (e) => e.indent ? { "data-indent": String(e.indent) } : {}
1107
+ } }
1108
+ }];
1109
+ }
1110
+ }), G = t.create({
1111
+ name: "ophxEditability",
1112
+ addGlobalAttributes() {
1113
+ return [{
1114
+ types: [
1115
+ "paragraph",
1116
+ "heading",
1117
+ "blockquote",
1118
+ "codeBlock",
1119
+ "bulletList",
1120
+ "orderedList",
1121
+ "listItem",
1122
+ "horizontalRule",
1123
+ "image",
1124
+ "table",
1125
+ "tableRow",
1126
+ "tableHeader",
1127
+ "tableCell",
1128
+ "bold",
1129
+ "italic",
1130
+ "underline",
1131
+ "strike",
1132
+ "link",
1133
+ "label"
1134
+ ],
1135
+ attributes: { contenteditable: {
1136
+ default: null,
1137
+ parseHTML: (e) => {
1138
+ let t = e.getAttribute("contenteditable");
1139
+ return t === "true" || t === "false" ? t : null;
1140
+ },
1141
+ renderHTML: (e) => e.contenteditable ? { contenteditable: e.contenteditable } : {}
1142
+ } }
1143
+ }];
1144
+ }
1145
+ });
1146
+ function K(e) {
1147
+ return de.test(e);
1148
+ }
1149
+ // src/editor/ophx-editor.ts
1150
+ function me(e, t) {
1151
+ if (typeof e != "string") return e;
1152
+ let n = document.querySelector(e);
1153
+ if (!n) throw Error(`OphxEditor: элемент для опции "${t}" не найден по селектору "${e}"`);
1154
+ return n;
1155
+ }
1156
+ var q = "ophx-editor-editable", J = class {
1157
+ editor;
1158
+ archiveDefaults = null;
1159
+ editableStorageKey;
1160
+ unmountToolbar = null;
1161
+ constructor(t) {
1162
+ this.editableStorageKey = t.editableStorageKey === void 0 ? q : t.editableStorageKey, this.editor = new e({
1163
+ element: me(t.element, "element"),
1164
+ content: t.content ?? "<p></p>",
1165
+ editable: this.readStoredEditable() ?? t.editable ?? !0,
1166
+ extensions: [
1167
+ a.configure({
1168
+ heading: !1,
1169
+ underline: !1,
1170
+ link: !1
1171
+ }),
1172
+ z.configure({ levels: [
1173
+ 1,
1174
+ 2,
1175
+ 3,
1176
+ 4,
1177
+ 5,
1178
+ 6
1179
+ ] }),
1180
+ o,
1181
+ s.configure({
1182
+ openOnClick: !1,
1183
+ autolink: !0,
1184
+ linkOnPaste: !0
1185
+ }),
1186
+ P.configure({
1187
+ allowBase64: !0,
1188
+ inline: !1
1189
+ }),
1190
+ l.configure({ resizable: !0 }),
1191
+ u,
1192
+ d,
1193
+ f,
1194
+ F,
1195
+ I,
1196
+ R,
1197
+ L,
1198
+ A,
1199
+ W,
1200
+ G,
1201
+ V,
1202
+ U,
1203
+ c.configure({ placeholder: t.placeholder ?? "Начните писать…" }),
1204
+ ...t.extensions ?? []
1205
+ ],
1206
+ editorProps: {
1207
+ attributes: {
1208
+ class: "ophx-editor-surface",
1209
+ spellcheck: "true",
1210
+ lang: "ru",
1211
+ "aria-label": "Редактор документа"
1212
+ },
1213
+ handleDOMEvents: { mousedown: (e, t) => {
1214
+ if (e.editable) return !1;
1215
+ let n = t.target instanceof Element ? t.target : null;
1216
+ if (!n) return !1;
1217
+ if (n.closest(".ophx-image-node, img")) return t.preventDefault(), !0;
1218
+ let r = n.closest("table"), i = n.closest("p, span, a, strong, em, code");
1219
+ return r && (!i || t.metaKey || t.ctrlKey) ? (t.preventDefault(), !0) : !1;
1220
+ } }
1221
+ },
1222
+ onUpdate: t.onUpdate
1223
+ }), this.syncEditableControls(), t.toolbar && (this.unmountToolbar = b(me(t.toolbar, "toolbar"), this.editor, t.toolbarOptions));
1224
+ }
1225
+ getHTML() {
1226
+ return this.editor.getHTML();
1227
+ }
1228
+ getJSON() {
1229
+ return this.editor.getJSON();
1230
+ }
1231
+ getText() {
1232
+ return this.editor.getText();
1233
+ }
1234
+ get isEmpty() {
1235
+ return this.editor.isEmpty;
1236
+ }
1237
+ get isEditable() {
1238
+ return this.editor.isEditable;
1239
+ }
1240
+ setContent(e, t = {}) {
1241
+ this.archiveDefaults = null, this.editor.commands.setContent(e, { emitUpdate: t.emitUpdate ?? !1 });
1242
+ }
1243
+ clearContent(e = {}) {
1244
+ this.setContent("<p></p>", e);
1245
+ }
1246
+ focus(e) {
1247
+ return this.editor.commands.focus(e), this;
1248
+ }
1249
+ blur() {
1250
+ return this.editor.commands.blur(), this;
1251
+ }
1252
+ setEditable(e) {
1253
+ return this.editor.setEditable(e, !1), this.writeStoredEditable(e), this.syncEditableControls(), this;
1254
+ }
1255
+ readStoredEditable() {
1256
+ if (!this.editableStorageKey) return null;
1257
+ try {
1258
+ let e = localStorage.getItem(this.editableStorageKey);
1259
+ return e === "true" || e !== "false" && null;
1260
+ } catch {
1261
+ return null;
1262
+ }
1263
+ }
1264
+ writeStoredEditable(e) {
1265
+ if (this.editableStorageKey) try {
1266
+ localStorage.setItem(this.editableStorageKey, String(e));
1267
+ } catch {}
1268
+ }
1269
+ undo() {
1270
+ return this.editor.commands.undo();
1271
+ }
1272
+ redo() {
1273
+ return this.editor.commands.redo();
1274
+ }
1275
+ canUndo() {
1276
+ return this.editor.can().undo();
1277
+ }
1278
+ canRedo() {
1279
+ return this.editor.can().redo();
1280
+ }
1281
+ syncEditableControls() {
1282
+ let e = this.editor.isEditable;
1283
+ this.editor.view.dom.querySelectorAll(".ophx-heading__toggle, .ophx-image-node__resize-handle").forEach((t) => {
1284
+ t.disabled = !e;
1285
+ }), this.editor.view.dom.dispatchEvent(new Event("ophx:editablechange"));
1286
+ }
1287
+ async loadOphx(e) {
1288
+ let t = await S(e);
1289
+ return this.editor.commands.setContent(t.html, { emitUpdate: !1 }), this.archiveDefaults = {
1290
+ filename: t.filename,
1291
+ title: t.title,
1292
+ styles: t.styles,
1293
+ bodyContentEditable: t.bodyContentEditable
1294
+ }, t;
1295
+ }
1296
+ saveOphx(e = {}) {
1297
+ return x(this.getHTML(), {
1298
+ ...this.archiveDefaults,
1299
+ ...e
1300
+ });
1301
+ }
1302
+ async downloadOphx(e = {}) {
1303
+ let t = e.filename ?? this.archiveDefaults?.filename ?? e.title ?? this.archiveDefaults?.title ?? "Документ";
1304
+ se(await this.saveOphx(e), `${t.replace(/\.ophx$/i, "")}.ophx`);
1305
+ }
1306
+ setSearchQuery(e) {
1307
+ this.editor.commands.setSearchQuery(e);
1308
+ }
1309
+ getFields() {
1310
+ let e = Array.from(this.editor.view.dom.querySelectorAll("[data-ophx-field]")).filter((e) => {
1311
+ let t = e.getAttribute("data-ophx-field") ?? "";
1312
+ return e.classList.contains("ophx-field") && e.getAttribute("contenteditable") === "true" && K(t);
1313
+ }), t = /* @__PURE__ */ new Map();
1314
+ for (let n of e) {
1315
+ let e = n.getAttribute("data-ophx-field");
1316
+ t.set(e, (t.get(e) ?? 0) + 1);
1317
+ }
1318
+ return e.filter((e) => t.get(e.getAttribute("data-ophx-field")) === 1).map((e) => ({
1319
+ id: e.getAttribute("data-ophx-field"),
1320
+ label: e.getAttribute("data-ophx-label"),
1321
+ required: e.getAttribute("data-ophx-required") === "true",
1322
+ value: e.textContent ?? ""
1323
+ }));
1324
+ }
1325
+ getMissingRequiredFields() {
1326
+ return this.getFields().filter((e) => e.required && !e.value.trim());
1327
+ }
1328
+ destroy() {
1329
+ this.unmountToolbar?.(), this.unmountToolbar = null, this.editor.destroy();
1330
+ }
1331
+ };
785
1332
  // src/editor/template-fields.ts
786
- function ee(e, t = "f") {
1333
+ function he(e, t = "f") {
787
1334
  if (!/^[A-Za-z0-9._-]+$/.test(t)) throw Error("Префикс идентификаторов полей содержит недопустимые символы");
788
1335
  let n = new DOMParser().parseFromString(e, "text/html"), r = new Set(Array.from(n.querySelectorAll("[data-ophx-field]")).map((e) => e.getAttribute("data-ophx-field") ?? "").filter(Boolean)), i = n.createTreeWalker(n.body, 4), a = [];
789
1336
  for (; i.nextNode();) {
@@ -810,4 +1357,4 @@ function ee(e, t = "f") {
810
1357
  ids: o
811
1358
  };
812
1359
  }
813
- export { D as CollapsibleHeading, M as Indentation, y as Label, C as OphxBlockField, N as OphxEditability, F as OphxEditor, S as OphxInlineField, T as OphxReadonlyBlock, w as OphxReadonlyInline, k as TextSearch, j as WysiwymRules, ee as convertUnderscoresToFields, U as downloadBlob, V as exportOphx, H as importOphx, P as isValidOphxFieldId, L as mountToolbar };
1360
+ export { z as CollapsibleHeading, W as Indentation, A as Label, I as OphxBlockField, G as OphxEditability, J as OphxEditor, F as OphxInlineField, R as OphxReadonlyBlock, L as OphxReadonlyInline, V as TextSearch, U as WysiwymRules, he as convertUnderscoresToFields, se as downloadBlob, x as exportOphx, S as importOphx, K as isValidOphxFieldId, b as mountToolbar };