@june-ai/june-ui 0.2.7 → 0.2.9
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.
- package/CHANGELOG.md +49 -2
- package/README.md +633 -400
- package/dist/{chunk-VFZI3SEL.js → chunk-27XM6EVX.js} +8 -8
- package/dist/chunk-G6YPSE6J.cjs +520 -0
- package/dist/chunk-GFCROSGQ.cjs +4399 -0
- package/dist/chunk-JSRO5CRO.js +4399 -0
- package/dist/{document-engine-RKD5SLXR.js → document-engine-BUTK73DW.js} +1 -1
- package/dist/document-engine-GXZH2LNW.cjs +12 -0
- package/dist/entry-core.cjs +148 -0
- package/dist/entry-core.d.cts +1005 -0
- package/dist/entry-core.d.ts +1005 -0
- package/dist/entry-core.js +149 -0
- package/dist/index.cjs +182 -4238
- package/dist/index.d.cts +80 -525
- package/dist/index.d.ts +80 -525
- package/dist/index.js +168 -3599
- package/dist/june.css +94 -0
- package/docs/architecture.md +95 -0
- package/docs/component-registration.md +115 -0
- package/docs/custom-features.md +68 -0
- package/docs/streaming.md +76 -0
- package/docs/theming.md +65 -0
- package/docs/troubleshooting.md +59 -0
- package/package.json +19 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/features/document-editor/document-engine.ts
|
|
2
2
|
import { Editor } from "@tiptap/core";
|
|
3
3
|
|
|
4
|
-
// src/
|
|
4
|
+
// src/utils/errors.ts
|
|
5
5
|
var JuneError = class _JuneError extends Error {
|
|
6
6
|
constructor(code, message, options = {}) {
|
|
7
7
|
super(message);
|
|
@@ -23,7 +23,7 @@ function toJuneError(error, fallbackCode, message) {
|
|
|
23
23
|
return new JuneError(fallbackCode, text, { cause: error });
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
// src/
|
|
26
|
+
// src/features/document-editor/document-extensions.ts
|
|
27
27
|
import { Node, mergeAttributes } from "@tiptap/core";
|
|
28
28
|
import Highlight from "@tiptap/extension-highlight";
|
|
29
29
|
import Image from "@tiptap/extension-image";
|
|
@@ -35,7 +35,7 @@ import { Color, FontFamily, FontSize, TextStyle } from "@tiptap/extension-text-s
|
|
|
35
35
|
import { Markdown } from "@tiptap/markdown";
|
|
36
36
|
import StarterKit from "@tiptap/starter-kit";
|
|
37
37
|
|
|
38
|
-
// src/
|
|
38
|
+
// src/features/document-editor/document-pagination.ts
|
|
39
39
|
import { Extension } from "@tiptap/core";
|
|
40
40
|
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
41
41
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
@@ -207,7 +207,7 @@ function getPageCount(editor) {
|
|
|
207
207
|
return editor && paginationKey.getState(editor.state)?.pages || 1;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
// src/
|
|
210
|
+
// src/features/document-editor/document-search.ts
|
|
211
211
|
import { Extension as Extension2 } from "@tiptap/core";
|
|
212
212
|
import { Plugin as Plugin2, PluginKey as PluginKey2 } from "@tiptap/pm/state";
|
|
213
213
|
import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "@tiptap/pm/view";
|
|
@@ -320,7 +320,7 @@ function replaceAll(editor, replacement) {
|
|
|
320
320
|
return matches.length;
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
-
// src/
|
|
323
|
+
// src/features/document-editor/document-extensions.ts
|
|
324
324
|
var PageBreak = Node.create({
|
|
325
325
|
name: "pageBreak",
|
|
326
326
|
group: "block",
|
|
@@ -379,7 +379,7 @@ function createDocumentExtensions() {
|
|
|
379
379
|
];
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
-
// src/
|
|
382
|
+
// src/features/document-editor/document-utils.ts
|
|
383
383
|
var emptyDoc = () => ({ type: "doc", content: [{ type: "paragraph" }] });
|
|
384
384
|
function isDocJSON(value) {
|
|
385
385
|
if (!value || typeof value !== "object") return false;
|
|
@@ -460,7 +460,7 @@ function sanitizeHref(input) {
|
|
|
460
460
|
return `https://${value}`;
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
-
// src/
|
|
463
|
+
// src/features/document-editor/document-engine.ts
|
|
464
464
|
var scratch = null;
|
|
465
465
|
function scratchEditor() {
|
|
466
466
|
if (!scratch || scratch.isDestroyed) {
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/features/document-editor/document-engine.ts
|
|
2
|
+
var _core = require('@tiptap/core');
|
|
3
|
+
|
|
4
|
+
// src/utils/errors.ts
|
|
5
|
+
var JuneError = class _JuneError extends Error {
|
|
6
|
+
constructor(code, message, options = {}) {
|
|
7
|
+
super(message);
|
|
8
|
+
Object.setPrototypeOf(this, _JuneError.prototype);
|
|
9
|
+
this.name = "JuneError";
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.details = options.details;
|
|
12
|
+
if (options.cause !== void 0) {
|
|
13
|
+
this.cause = options.cause;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function isJuneError(value) {
|
|
18
|
+
return value instanceof JuneError;
|
|
19
|
+
}
|
|
20
|
+
function toJuneError(error, fallbackCode, message) {
|
|
21
|
+
if (error instanceof JuneError) return error;
|
|
22
|
+
const text = _nullishCoalesce(message, () => ( (error instanceof Error ? error.message : String(error))));
|
|
23
|
+
return new JuneError(fallbackCode, text, { cause: error });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/features/document-editor/document-extensions.ts
|
|
27
|
+
|
|
28
|
+
var _extensionhighlight = require('@tiptap/extension-highlight'); var _extensionhighlight2 = _interopRequireDefault(_extensionhighlight);
|
|
29
|
+
var _extensionimage = require('@tiptap/extension-image'); var _extensionimage2 = _interopRequireDefault(_extensionimage);
|
|
30
|
+
var _extensionsubscript = require('@tiptap/extension-subscript'); var _extensionsubscript2 = _interopRequireDefault(_extensionsubscript);
|
|
31
|
+
var _extensionsuperscript = require('@tiptap/extension-superscript'); var _extensionsuperscript2 = _interopRequireDefault(_extensionsuperscript);
|
|
32
|
+
var _extensiontable = require('@tiptap/extension-table');
|
|
33
|
+
var _extensiontextalign = require('@tiptap/extension-text-align'); var _extensiontextalign2 = _interopRequireDefault(_extensiontextalign);
|
|
34
|
+
var _extensiontextstyle = require('@tiptap/extension-text-style');
|
|
35
|
+
var _markdown = require('@tiptap/markdown');
|
|
36
|
+
var _starterkit = require('@tiptap/starter-kit'); var _starterkit2 = _interopRequireDefault(_starterkit);
|
|
37
|
+
|
|
38
|
+
// src/features/document-editor/document-pagination.ts
|
|
39
|
+
|
|
40
|
+
var _state = require('@tiptap/pm/state');
|
|
41
|
+
var _view = require('@tiptap/pm/view');
|
|
42
|
+
var paginationKey = new (0, _state.PluginKey)("juneDocPagination");
|
|
43
|
+
var configs = /* @__PURE__ */ new WeakMap();
|
|
44
|
+
var SPLITTABLE = /* @__PURE__ */ new Set(["bulletList", "orderedList", "listItem", "blockquote"]);
|
|
45
|
+
function gapWidget(height) {
|
|
46
|
+
return () => {
|
|
47
|
+
const el = document.createElement("div");
|
|
48
|
+
el.className = "june-doc__page-gap";
|
|
49
|
+
el.contentEditable = "false";
|
|
50
|
+
el.setAttribute("aria-hidden", "true");
|
|
51
|
+
el.style.height = `${height}px`;
|
|
52
|
+
return el;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function build(doc, breaks) {
|
|
56
|
+
return _view.DecorationSet.create(
|
|
57
|
+
doc,
|
|
58
|
+
breaks.map(
|
|
59
|
+
(b) => _view.Decoration.widget(b.pos, gapWidget(b.height), {
|
|
60
|
+
side: -1,
|
|
61
|
+
ignoreSelection: true,
|
|
62
|
+
key: `jd-gap-${b.pos}-${Math.round(b.height)}`
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
function collectUnits(doc) {
|
|
68
|
+
const units = [];
|
|
69
|
+
const walk = (parent, start, inheritedBreak) => {
|
|
70
|
+
parent.forEach((child, offset, index) => {
|
|
71
|
+
const pos = start + offset;
|
|
72
|
+
const breakPos = index === 0 && inheritedBreak !== null ? inheritedBreak : pos;
|
|
73
|
+
if (SPLITTABLE.has(child.type.name) && child.childCount > 0) {
|
|
74
|
+
walk(child, pos + 1, breakPos);
|
|
75
|
+
} else {
|
|
76
|
+
units.push({ pos, breakPos, node: child });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
walk(doc, 0, null);
|
|
81
|
+
return units;
|
|
82
|
+
}
|
|
83
|
+
function measure(view, cfg) {
|
|
84
|
+
const prose = view.dom;
|
|
85
|
+
const proseRect = prose.getBoundingClientRect();
|
|
86
|
+
const scale = prose.offsetWidth ? proseRect.width / prose.offsetWidth : 1;
|
|
87
|
+
if (!scale || !proseRect.width) return { breaks: [], pages: 1 };
|
|
88
|
+
const gaps = [...prose.querySelectorAll(".june-doc__page-gap")].map((g) => {
|
|
89
|
+
const r = g.getBoundingClientRect();
|
|
90
|
+
return { top: (r.top - proseRect.top) / scale, height: r.height / scale };
|
|
91
|
+
});
|
|
92
|
+
const gapsAbove = (y) => gaps.reduce((sum, g) => g.top <= y + 0.5 ? sum + g.height : sum, 0);
|
|
93
|
+
const contentH = Math.max(80, cfg.pageHeight - cfg.padTop - cfg.padBottom);
|
|
94
|
+
const stride = cfg.pageHeight + cfg.gap;
|
|
95
|
+
const breaks = /* @__PURE__ */ new Map();
|
|
96
|
+
let pageStart = 0;
|
|
97
|
+
let added = 0;
|
|
98
|
+
let force = false;
|
|
99
|
+
for (const unit of collectUnits(view.state.doc)) {
|
|
100
|
+
const el = view.nodeDOM(unit.pos);
|
|
101
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
102
|
+
const r = el.getBoundingClientRect();
|
|
103
|
+
const rawTop = (r.top - proseRect.top) / scale;
|
|
104
|
+
const natural = rawTop - gapsAbove(rawTop);
|
|
105
|
+
const h = r.height / scale;
|
|
106
|
+
let top = natural + added;
|
|
107
|
+
let bottom = top + h;
|
|
108
|
+
const overflows = bottom > pageStart + contentH + 0.5 && top > pageStart + 1;
|
|
109
|
+
if (force || overflows) {
|
|
110
|
+
const target = pageStart + stride;
|
|
111
|
+
if (top < target) {
|
|
112
|
+
const spacer = target - top;
|
|
113
|
+
breaks.set(unit.breakPos, (_nullishCoalesce(breaks.get(unit.breakPos), () => ( 0))) + spacer);
|
|
114
|
+
added += spacer;
|
|
115
|
+
top = target;
|
|
116
|
+
bottom = top + h;
|
|
117
|
+
}
|
|
118
|
+
pageStart = target;
|
|
119
|
+
while (top >= pageStart + stride) pageStart += stride;
|
|
120
|
+
force = false;
|
|
121
|
+
}
|
|
122
|
+
while (bottom > pageStart + contentH + 0.5) pageStart += stride;
|
|
123
|
+
if (unit.node.type.name === "pageBreak") force = true;
|
|
124
|
+
}
|
|
125
|
+
const pages = Math.max(1, Math.round(pageStart / stride) + 1 + (force ? 1 : 0));
|
|
126
|
+
return {
|
|
127
|
+
breaks: [...breaks].map(([pos, height]) => ({ pos, height: Math.round(height * 10) / 10 })).sort((a, b) => a.pos - b.pos),
|
|
128
|
+
pages
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
var same = (a, b) => a.length === b.length && a.every((x, i) => x.pos === b[i].pos && Math.abs(x.height - b[i].height) < 1);
|
|
132
|
+
var DocumentPagination = _core.Extension.create({
|
|
133
|
+
name: "juneDocPagination",
|
|
134
|
+
addProseMirrorPlugins() {
|
|
135
|
+
return [
|
|
136
|
+
new (0, _state.Plugin)({
|
|
137
|
+
key: paginationKey,
|
|
138
|
+
state: {
|
|
139
|
+
init: () => ({ breaks: [], pages: 1, decorations: _view.DecorationSet.empty, epoch: 0 }),
|
|
140
|
+
apply(tr, prev, _old, state) {
|
|
141
|
+
const meta = tr.getMeta(paginationKey);
|
|
142
|
+
if (_optionalChain([meta, 'optionalAccess', _ => _.remeasure])) return { ...prev, epoch: prev.epoch + 1 };
|
|
143
|
+
if (_optionalChain([meta, 'optionalAccess', _2 => _2.breaks])) {
|
|
144
|
+
return { ...prev, breaks: meta.breaks, pages: _nullishCoalesce(meta.pages, () => ( prev.pages)), decorations: build(state.doc, meta.breaks) };
|
|
145
|
+
}
|
|
146
|
+
if (tr.docChanged) {
|
|
147
|
+
const breaks = prev.breaks.map((b) => ({ ...b, pos: tr.mapping.map(b.pos, -1) })).filter((b) => b.pos >= 0 && b.pos <= state.doc.content.size);
|
|
148
|
+
return { ...prev, breaks, decorations: prev.decorations.map(tr.mapping, tr.doc) };
|
|
149
|
+
}
|
|
150
|
+
return prev;
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
props: {
|
|
154
|
+
decorations: (state) => _optionalChain([paginationKey, 'access', _3 => _3.getState, 'call', _4 => _4(state), 'optionalAccess', _5 => _5.decorations])
|
|
155
|
+
},
|
|
156
|
+
view(view) {
|
|
157
|
+
let frame = null;
|
|
158
|
+
let rounds = 0;
|
|
159
|
+
const schedule = (reset) => {
|
|
160
|
+
if (reset) rounds = 0;
|
|
161
|
+
if (frame !== null || typeof window === "undefined") return;
|
|
162
|
+
const raf = _nullishCoalesce(window.requestAnimationFrame, () => ( ((cb) => window.setTimeout(() => cb(0), 16))));
|
|
163
|
+
frame = raf(() => {
|
|
164
|
+
frame = null;
|
|
165
|
+
if (view.isDestroyed) return;
|
|
166
|
+
const cfg = configs.get(view);
|
|
167
|
+
const current = paginationKey.getState(view.state);
|
|
168
|
+
if (!current) return;
|
|
169
|
+
const next = _optionalChain([cfg, 'optionalAccess', _6 => _6.enabled]) ? measure(view, cfg) : { breaks: [], pages: 1 };
|
|
170
|
+
if (same(next.breaks, current.breaks) && next.pages === current.pages) return;
|
|
171
|
+
if (++rounds > 4) return;
|
|
172
|
+
view.dispatch(view.state.tr.setMeta(paginationKey, next).setMeta("addToHistory", false));
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
schedule(true);
|
|
176
|
+
return {
|
|
177
|
+
update(v, prevState) {
|
|
178
|
+
const pg = paginationKey.getState(v.state);
|
|
179
|
+
const prevPg = paginationKey.getState(prevState);
|
|
180
|
+
if (v.state.doc !== prevState.doc || _optionalChain([pg, 'optionalAccess', _7 => _7.epoch]) !== _optionalChain([prevPg, 'optionalAccess', _8 => _8.epoch])) schedule(true);
|
|
181
|
+
else if (pg !== prevPg) schedule(false);
|
|
182
|
+
},
|
|
183
|
+
destroy() {
|
|
184
|
+
frame = null;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
];
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
function setPaginationConfig(editor, cfg) {
|
|
193
|
+
if (editor.isDestroyed) return;
|
|
194
|
+
const prev = configs.get(editor.view);
|
|
195
|
+
configs.set(editor.view, cfg);
|
|
196
|
+
if (!prev || prev.enabled !== cfg.enabled || prev.pageHeight !== cfg.pageHeight || prev.gap !== cfg.gap || prev.padTop !== cfg.padTop || prev.padBottom !== cfg.padBottom) {
|
|
197
|
+
remeasurePagination(editor);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function remeasurePagination(editor) {
|
|
201
|
+
if (editor.isDestroyed) return;
|
|
202
|
+
const s = paginationKey.getState(editor.state);
|
|
203
|
+
if (!s) return;
|
|
204
|
+
editor.view.dispatch(editor.state.tr.setMeta(paginationKey, { remeasure: true }).setMeta("addToHistory", false));
|
|
205
|
+
}
|
|
206
|
+
function getPageCount(editor) {
|
|
207
|
+
return editor && _optionalChain([paginationKey, 'access', _9 => _9.getState, 'call', _10 => _10(editor.state), 'optionalAccess', _11 => _11.pages]) || 1;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/features/document-editor/document-search.ts
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
var searchKey = new (0, _state.PluginKey)("juneDocSearch");
|
|
215
|
+
function findMatches(doc, term, caseSensitive) {
|
|
216
|
+
if (!term) return [];
|
|
217
|
+
const needle = caseSensitive ? term : term.toLowerCase();
|
|
218
|
+
const out = [];
|
|
219
|
+
doc.descendants((node, pos) => {
|
|
220
|
+
if (!node.isTextblock) return true;
|
|
221
|
+
let text = "";
|
|
222
|
+
const map = [];
|
|
223
|
+
node.forEach((child, offset) => {
|
|
224
|
+
const start = pos + 1 + offset;
|
|
225
|
+
if (child.isText && child.text) {
|
|
226
|
+
for (let i = 0; i < child.text.length; i++) map.push(start + i);
|
|
227
|
+
text += child.text;
|
|
228
|
+
} else {
|
|
229
|
+
map.push(start);
|
|
230
|
+
text += "\uFFFC";
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
const hay = caseSensitive ? text : text.toLowerCase();
|
|
234
|
+
let idx = hay.indexOf(needle);
|
|
235
|
+
while (idx !== -1) {
|
|
236
|
+
out.push({ from: map[idx], to: map[idx + needle.length - 1] + 1 });
|
|
237
|
+
idx = hay.indexOf(needle, idx + needle.length);
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
});
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
function build2(doc, term, caseSensitive, current) {
|
|
244
|
+
const matches = findMatches(doc, term, caseSensitive);
|
|
245
|
+
const active = matches.length ? Math.min(Math.max(current, 0), matches.length - 1) : -1;
|
|
246
|
+
const decorations = _view.DecorationSet.create(
|
|
247
|
+
doc,
|
|
248
|
+
matches.map(
|
|
249
|
+
(m, i) => _view.Decoration.inline(m.from, m.to, {
|
|
250
|
+
class: i === active ? "june-doc__match june-doc__match--active" : "june-doc__match"
|
|
251
|
+
})
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
return { term, caseSensitive, current: active, matches, decorations };
|
|
255
|
+
}
|
|
256
|
+
var DocumentSearch = _core.Extension.create({
|
|
257
|
+
name: "juneDocSearch",
|
|
258
|
+
addProseMirrorPlugins() {
|
|
259
|
+
return [
|
|
260
|
+
new (0, _state.Plugin)({
|
|
261
|
+
key: searchKey,
|
|
262
|
+
state: {
|
|
263
|
+
init: () => ({ term: "", caseSensitive: false, current: -1, matches: [], decorations: _view.DecorationSet.empty }),
|
|
264
|
+
apply(tr, prev, _old, state) {
|
|
265
|
+
const meta = tr.getMeta(searchKey);
|
|
266
|
+
if (!meta && !tr.docChanged) return prev;
|
|
267
|
+
if (!meta && !prev.term) return prev;
|
|
268
|
+
return build2(
|
|
269
|
+
state.doc,
|
|
270
|
+
_nullishCoalesce(_optionalChain([meta, 'optionalAccess', _12 => _12.term]), () => ( prev.term)),
|
|
271
|
+
_nullishCoalesce(_optionalChain([meta, 'optionalAccess', _13 => _13.caseSensitive]), () => ( prev.caseSensitive)),
|
|
272
|
+
_nullishCoalesce(_optionalChain([meta, 'optionalAccess', _14 => _14.current]), () => ( prev.current))
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
props: {
|
|
277
|
+
decorations: (state) => _optionalChain([searchKey, 'access', _15 => _15.getState, 'call', _16 => _16(state), 'optionalAccess', _17 => _17.decorations])
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
];
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
function getSearchState(editor) {
|
|
284
|
+
return editor ? searchKey.getState(editor.state) : void 0;
|
|
285
|
+
}
|
|
286
|
+
function setSearch(editor, meta) {
|
|
287
|
+
editor.view.dispatch(editor.state.tr.setMeta(searchKey, meta));
|
|
288
|
+
}
|
|
289
|
+
function gotoMatch(editor, step) {
|
|
290
|
+
const s = getSearchState(editor);
|
|
291
|
+
if (!s || !s.matches.length) return;
|
|
292
|
+
const n = s.matches.length;
|
|
293
|
+
const current = s.current < 0 ? 0 : (s.current + step + n) % n;
|
|
294
|
+
setSearch(editor, { current });
|
|
295
|
+
const m = s.matches[current];
|
|
296
|
+
editor.chain().setTextSelection(m).scrollIntoView().run();
|
|
297
|
+
}
|
|
298
|
+
function replaceCurrent(editor, replacement) {
|
|
299
|
+
const s = getSearchState(editor);
|
|
300
|
+
if (!s || s.current < 0) return;
|
|
301
|
+
const m = s.matches[s.current];
|
|
302
|
+
editor.chain().command(({ tr }) => {
|
|
303
|
+
if (replacement) tr.insertText(replacement, m.from, m.to);
|
|
304
|
+
else tr.delete(m.from, m.to);
|
|
305
|
+
return true;
|
|
306
|
+
}).run();
|
|
307
|
+
gotoMatch(editor, 0);
|
|
308
|
+
}
|
|
309
|
+
function replaceAll(editor, replacement) {
|
|
310
|
+
const s = getSearchState(editor);
|
|
311
|
+
if (!s || !s.matches.length) return 0;
|
|
312
|
+
const matches = [...s.matches].reverse();
|
|
313
|
+
editor.chain().command(({ tr }) => {
|
|
314
|
+
for (const m of matches) {
|
|
315
|
+
if (replacement) tr.insertText(replacement, m.from, m.to);
|
|
316
|
+
else tr.delete(m.from, m.to);
|
|
317
|
+
}
|
|
318
|
+
return true;
|
|
319
|
+
}).run();
|
|
320
|
+
return matches.length;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/features/document-editor/document-extensions.ts
|
|
324
|
+
var PageBreak = _core.Node.create({
|
|
325
|
+
name: "pageBreak",
|
|
326
|
+
group: "block",
|
|
327
|
+
atom: true,
|
|
328
|
+
selectable: true,
|
|
329
|
+
parseHTML() {
|
|
330
|
+
return [{ tag: "div[data-page-break]" }];
|
|
331
|
+
},
|
|
332
|
+
renderHTML({ HTMLAttributes }) {
|
|
333
|
+
return [
|
|
334
|
+
"div",
|
|
335
|
+
_core.mergeAttributes.call(void 0, HTMLAttributes, {
|
|
336
|
+
"data-page-break": "true",
|
|
337
|
+
class: "june-doc__page-break",
|
|
338
|
+
style: "break-after: page; page-break-after: always;"
|
|
339
|
+
})
|
|
340
|
+
];
|
|
341
|
+
},
|
|
342
|
+
// Markdown has no page break; a thematic break is the closest stable equivalent.
|
|
343
|
+
renderMarkdown: () => "---",
|
|
344
|
+
addCommands() {
|
|
345
|
+
return {
|
|
346
|
+
setPageBreak: () => ({ chain }) => chain().insertContent([{ type: this.name }, { type: "paragraph" }]).run()
|
|
347
|
+
};
|
|
348
|
+
},
|
|
349
|
+
addKeyboardShortcuts() {
|
|
350
|
+
return { "Mod-Enter": () => this.editor.commands.setPageBreak() };
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
function createDocumentExtensions() {
|
|
354
|
+
return [
|
|
355
|
+
_starterkit2.default.configure({
|
|
356
|
+
heading: { levels: [1, 2, 3] },
|
|
357
|
+
// Tiptap v3 StarterKit already includes Link and Underline
|
|
358
|
+
link: { openOnClick: false, autolink: true, defaultProtocol: "https" }
|
|
359
|
+
}),
|
|
360
|
+
_extensiontextalign2.default.configure({ types: ["heading", "paragraph"] }),
|
|
361
|
+
_extensiontable.TableKit,
|
|
362
|
+
_extensiontextstyle.TextStyle,
|
|
363
|
+
_extensiontextstyle.Color,
|
|
364
|
+
_extensiontextstyle.FontFamily,
|
|
365
|
+
_extensiontextstyle.FontSize,
|
|
366
|
+
_extensionhighlight2.default.configure({ multicolor: true }),
|
|
367
|
+
_extensionsubscript2.default,
|
|
368
|
+
_extensionsuperscript2.default,
|
|
369
|
+
_extensionimage2.default.configure({
|
|
370
|
+
inline: false,
|
|
371
|
+
allowBase64: true,
|
|
372
|
+
resize: { enabled: true, minWidth: 48, minHeight: 32, alwaysPreserveAspectRatio: true }
|
|
373
|
+
}),
|
|
374
|
+
PageBreak,
|
|
375
|
+
DocumentSearch,
|
|
376
|
+
DocumentPagination,
|
|
377
|
+
// inactive until the live editor sets a page geometry
|
|
378
|
+
_markdown.Markdown
|
|
379
|
+
];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/features/document-editor/document-utils.ts
|
|
383
|
+
var emptyDoc = () => ({ type: "doc", content: [{ type: "paragraph" }] });
|
|
384
|
+
function isDocJSON(value) {
|
|
385
|
+
if (!value || typeof value !== "object") return false;
|
|
386
|
+
const v = value;
|
|
387
|
+
return v.type === "doc" && (v.content === void 0 || Array.isArray(v.content));
|
|
388
|
+
}
|
|
389
|
+
function looksLikeDocJSON(text) {
|
|
390
|
+
return /^\s*\{\s*"type"\s*:\s*"doc"/.test(text);
|
|
391
|
+
}
|
|
392
|
+
function tryParseDocJSON(text) {
|
|
393
|
+
try {
|
|
394
|
+
const value = JSON.parse(text);
|
|
395
|
+
return isDocJSON(value) ? value : null;
|
|
396
|
+
} catch (e) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
var keyCache = /* @__PURE__ */ new WeakMap();
|
|
401
|
+
function docKey(doc) {
|
|
402
|
+
let key = keyCache.get(doc);
|
|
403
|
+
if (key === void 0) {
|
|
404
|
+
key = JSON.stringify(doc);
|
|
405
|
+
keyCache.set(doc, key);
|
|
406
|
+
}
|
|
407
|
+
return key;
|
|
408
|
+
}
|
|
409
|
+
function plainText(node) {
|
|
410
|
+
const n = node;
|
|
411
|
+
if (typeof n.text === "string") return n.text;
|
|
412
|
+
return (_nullishCoalesce(n.content, () => ( []))).map(plainText).join(" ");
|
|
413
|
+
}
|
|
414
|
+
function countWords(text) {
|
|
415
|
+
const trimmed = text.trim();
|
|
416
|
+
return trimmed ? trimmed.split(/\s+/).length : 0;
|
|
417
|
+
}
|
|
418
|
+
var wordCache = /* @__PURE__ */ new WeakMap();
|
|
419
|
+
function wordCountOf(doc) {
|
|
420
|
+
let count = wordCache.get(doc);
|
|
421
|
+
if (count === void 0) {
|
|
422
|
+
count = countWords(plainText(doc));
|
|
423
|
+
wordCache.set(doc, count);
|
|
424
|
+
}
|
|
425
|
+
return count;
|
|
426
|
+
}
|
|
427
|
+
function slugify(value) {
|
|
428
|
+
return value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
429
|
+
}
|
|
430
|
+
function debounce(fn, wait) {
|
|
431
|
+
let timer = null;
|
|
432
|
+
const run = () => {
|
|
433
|
+
timer = null;
|
|
434
|
+
fn();
|
|
435
|
+
};
|
|
436
|
+
const debounced = (() => {
|
|
437
|
+
if (timer !== null) clearTimeout(timer);
|
|
438
|
+
timer = setTimeout(run, wait);
|
|
439
|
+
});
|
|
440
|
+
debounced.cancel = () => {
|
|
441
|
+
if (timer !== null) {
|
|
442
|
+
clearTimeout(timer);
|
|
443
|
+
timer = null;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
debounced.flush = () => {
|
|
447
|
+
if (timer !== null) {
|
|
448
|
+
clearTimeout(timer);
|
|
449
|
+
run();
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
return debounced;
|
|
453
|
+
}
|
|
454
|
+
function sanitizeHref(input) {
|
|
455
|
+
const value = input.trim();
|
|
456
|
+
if (!value) return null;
|
|
457
|
+
if (/^(https?:|mailto:|tel:)/i.test(value)) return value;
|
|
458
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(value)) return null;
|
|
459
|
+
if (value.startsWith("/") || value.startsWith("#")) return value;
|
|
460
|
+
return `https://${value}`;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/features/document-editor/document-engine.ts
|
|
464
|
+
var scratch = null;
|
|
465
|
+
function scratchEditor() {
|
|
466
|
+
if (!scratch || scratch.isDestroyed) {
|
|
467
|
+
scratch = new (0, _core.Editor)({ extensions: createDocumentExtensions(), content: "" });
|
|
468
|
+
}
|
|
469
|
+
return scratch;
|
|
470
|
+
}
|
|
471
|
+
function parseDocumentContent(text) {
|
|
472
|
+
const editor = scratchEditor();
|
|
473
|
+
const trimmed = text.trim();
|
|
474
|
+
try {
|
|
475
|
+
if (looksLikeDocJSON(trimmed)) {
|
|
476
|
+
const json = tryParseDocJSON(trimmed);
|
|
477
|
+
if (!json) throw new Error("The document JSON could not be parsed");
|
|
478
|
+
editor.schema.nodeFromJSON(json).check();
|
|
479
|
+
editor.commands.setContent(json, { emitUpdate: false });
|
|
480
|
+
} else {
|
|
481
|
+
editor.commands.setContent(trimmed, { contentType: "markdown", emitUpdate: false });
|
|
482
|
+
}
|
|
483
|
+
return editor.getJSON();
|
|
484
|
+
} catch (error) {
|
|
485
|
+
throw toJuneError(error, "DOCUMENT_ERROR", "The document content is invalid");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function load(doc) {
|
|
489
|
+
const editor = scratchEditor();
|
|
490
|
+
editor.commands.setContent(doc, { emitUpdate: false });
|
|
491
|
+
return editor;
|
|
492
|
+
}
|
|
493
|
+
var docToHtml = (doc) => load(doc).getHTML();
|
|
494
|
+
var docToMarkdown = (doc) => load(doc).getMarkdown();
|
|
495
|
+
var docToText = (doc) => load(doc).getText({ blockSeparator: "\n\n" });
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
exports.JuneError = JuneError; exports.isJuneError = isJuneError; exports.toJuneError = toJuneError; exports.emptyDoc = emptyDoc; exports.docKey = docKey; exports.wordCountOf = wordCountOf; exports.slugify = slugify; exports.debounce = debounce; exports.sanitizeHref = sanitizeHref; exports.setPaginationConfig = setPaginationConfig; exports.remeasurePagination = remeasurePagination; exports.getPageCount = getPageCount; exports.getSearchState = getSearchState; exports.setSearch = setSearch; exports.gotoMatch = gotoMatch; exports.replaceCurrent = replaceCurrent; exports.replaceAll = replaceAll; exports.createDocumentExtensions = createDocumentExtensions; exports.parseDocumentContent = parseDocumentContent; exports.docToHtml = docToHtml; exports.docToMarkdown = docToMarkdown; exports.docToText = docToText;
|