@jenkin-a/jeditor 1.0.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.
@@ -0,0 +1,2 @@
1
+ :root{--je-blue:#0052d9;--je-blue-active:#e6f0ff;--je-divider:#e0e0e0;--je-hover:#eee;--je-radius:4px}.je-container{-webkit-font-smoothing:antialiased;background:#fff;border:1px solid #e6e6e6;border-radius:4px;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,PingFang SC,Microsoft YaHei,sans-serif;display:flex;overflow:hidden}.je-toolbar-row{flex-shrink:0;align-items:center;gap:1px;padding:0 12px;display:flex}.je-toolbar-row--primary{background:#fff;height:44px}.je-toolbar-row--secondary{background:#f3f4f6;border-radius:6px;height:42px;margin:0 8px 4px}.je-spacer{flex:1}.v-divider{background-color:var(--je-divider);flex-shrink:0;width:1px;height:16px;margin:0 6px}.tool-btn,.tool-btn-text,.tool-btn-arrow-down{border-radius:var(--je-radius);color:#444;cursor:pointer;background:0 0;border:none;flex-shrink:0;justify-content:center;align-items:center;gap:2px;line-height:1;transition:background-color .1s;display:inline-flex}.tool-btn{width:28px;height:28px;padding:0;font-size:13px}.tool-btn:hover{background-color:var(--je-hover)}.tool-btn-text{white-space:nowrap;height:28px;padding:0 6px;font-size:13px}.tool-btn-text:hover{background-color:var(--je-hover)}.tool-btn-arrow-down{color:#999;width:16px;height:28px;padding:0}.tool-btn-arrow-down:hover{background-color:var(--je-hover)}.tool-btn svg,.tool-btn-text svg,.tool-btn-arrow-down svg{flex-shrink:0;width:20px;height:20px}.tool-btn-text svg,.tool-btn-arrow-down svg{width:14px;height:14px}.je-color-group{flex-shrink:0;align-items:center;display:inline-flex}.je-color-inner{flex-direction:column;align-items:center;gap:1px;line-height:1;display:flex}.je-color-char{font-size:14px;font-weight:600}.je-color-bar{border-radius:1px;width:16px;height:3px}.tool-btn.is-active{background-color:var(--je-blue-active);color:var(--je-blue)}.tool-btn.is-disabled{opacity:.35;pointer-events:none;cursor:default}.je-editor-area{flex-direction:column;flex:1;display:flex}.je-editor-area .tiptap{cursor:text;color:#333;outline:none;flex:1;min-height:700px;padding:32px 60px;font-size:14px;line-height:1.7}.je-editor-area .tiptap h1{margin-bottom:.5em;font-size:2em;font-weight:700}.je-editor-area .tiptap h2{margin-bottom:.5em;font-size:1.5em;font-weight:700}.je-editor-area .tiptap h3{margin-bottom:.5em;font-size:1.25em;font-weight:700}.je-editor-area .tiptap p{margin-bottom:.8em;line-height:1.7}.je-editor-area .tiptap ul{margin-bottom:.8em;padding-left:1.5em;list-style-type:disc}.je-editor-area .tiptap ol{margin-bottom:.8em;padding-left:1.5em;list-style-type:decimal}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:#ddd;border-radius:10px}.image-node-wrapper{cursor:default;-webkit-user-select:none;user-select:none;line-height:0;display:inline-block;position:relative}.image-node-wrapper img{border:2px solid #0000;border-radius:2px;max-width:100%;transition:border-color .15s;display:block}.image-node-wrapper.is-selected img{border-color:var(--je-blue)}.image-resize-handle{background:var(--je-blue);z-index:10;border-radius:50%;width:10px;height:10px;display:none;position:absolute}.image-node-wrapper.is-selected .image-resize-handle{display:block}.image-resize-handle[data-corner=tl]{cursor:nwse-resize;top:-5px;left:-5px}.image-resize-handle[data-corner=tr]{cursor:nesw-resize;top:-5px;right:-5px}.image-resize-handle[data-corner=bl]{cursor:nesw-resize;bottom:-5px;left:-5px}.image-resize-handle[data-corner=br]{cursor:nwse-resize;bottom:-5px;right:-5px}
2
+ /*$vite$:1*/
@@ -0,0 +1,523 @@
1
+ import e from "@tiptap/extension-underline";
2
+ import { Editor as t, Node as n, mergeAttributes as r } from "@tiptap/core";
3
+ import i from "@tiptap/starter-kit";
4
+ //#region src/core/plugin-manager.js
5
+ var a = class {
6
+ constructor() {
7
+ this._plugins = /* @__PURE__ */ new Map();
8
+ }
9
+ register(e) {
10
+ if (!e.name) throw Error("[JEditor] Plugin 缺少 name 字段");
11
+ this._plugins.set(e.name, e);
12
+ }
13
+ registerAll(e) {
14
+ e.forEach((e) => this.register(e));
15
+ }
16
+ get(e) {
17
+ return this._plugins.get(e);
18
+ }
19
+ getAll() {
20
+ return Array.from(this._plugins.values());
21
+ }
22
+ getTiptapExtensions() {
23
+ return this.getAll().filter((e) => e.tiptapExtension != null).map((e) => e.tiptapExtension);
24
+ }
25
+ initAll(e, t = {}) {
26
+ this.getAll().forEach((n) => {
27
+ typeof n.init == "function" && n.init(e, t[n.name] || {});
28
+ });
29
+ }
30
+ destroyAll() {
31
+ this.getAll().forEach((e) => {
32
+ typeof e.destroy == "function" && e.destroy();
33
+ }), this._plugins.clear();
34
+ }
35
+ }, o = {
36
+ placeholder: "开始在此编写文档...",
37
+ toolbar: [[
38
+ "undo",
39
+ "redo",
40
+ "|",
41
+ "insertImage",
42
+ "insert",
43
+ "|",
44
+ "attachment",
45
+ "table",
46
+ "link",
47
+ "mention",
48
+ "|",
49
+ "more",
50
+ "->",
51
+ "fullscreen"
52
+ ], /* @__PURE__ */ "heading.|.fontFamily.|.fontSizeDown.fontSize.fontSizeUp.|.bold.italic.underline.strike.clearFormat.|.textColor.highlight.|.bulletList.orderedList.alignLeft.alignCenter.alignRight.lineHeight.|.blockquote.codeBlock".split(".")],
53
+ image: {
54
+ maxSize: 20 * 1024 * 1024,
55
+ uploadUrl: null,
56
+ accept: "image/png,image/jpeg,image/gif,image/webp,image/svg+xml"
57
+ }
58
+ };
59
+ function s(e = {}) {
60
+ let t = {
61
+ ...o,
62
+ ...e
63
+ };
64
+ return e.image && (t.image = {
65
+ ...o.image,
66
+ ...e.image
67
+ }), t;
68
+ }
69
+ //#endregion
70
+ //#region src/plugins/bold.js
71
+ var c = {
72
+ name: "bold",
73
+ toolbar: {
74
+ text: "B",
75
+ title: "粗体",
76
+ shortcut: "Ctrl+B",
77
+ className: "font-bold"
78
+ },
79
+ tiptapExtension: null,
80
+ command: (e) => e.chain().focus().toggleBold().run(),
81
+ isActive: (e) => e.isActive("bold")
82
+ }, l = {
83
+ name: "italic",
84
+ toolbar: {
85
+ text: "I",
86
+ title: "斜体",
87
+ shortcut: "Ctrl+I",
88
+ className: "italic"
89
+ },
90
+ tiptapExtension: null,
91
+ command: (e) => e.chain().focus().toggleItalic().run(),
92
+ isActive: (e) => e.isActive("italic")
93
+ }, u = {
94
+ name: "underline",
95
+ toolbar: {
96
+ text: "U",
97
+ title: "下划线",
98
+ shortcut: "Ctrl+U",
99
+ className: "underline"
100
+ },
101
+ tiptapExtension: e,
102
+ command: (e) => e.chain().focus().toggleUnderline().run(),
103
+ isActive: (e) => e.isActive("underline")
104
+ }, d = {
105
+ name: "strike",
106
+ toolbar: {
107
+ text: "S",
108
+ title: "删除线",
109
+ shortcut: "Ctrl+Shift+X",
110
+ className: "line-through text-[13px]"
111
+ },
112
+ tiptapExtension: null,
113
+ command: (e) => e.chain().focus().toggleStrike().run(),
114
+ isActive: (e) => e.isActive("strike")
115
+ }, f = {
116
+ name: "undo",
117
+ toolbar: {
118
+ icon: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" fill=\"currentColor\">\n <path d=\"M6.25 3.75 2.5 6.25l3.75 2.5V6.875h5.938a4.062 4.062 0 1 1 0 8.125H8.125a.625.625 0 1 0 0 1.25h4.063a5.313 5.313 0 0 0 0-10.625H6.25z\"></path>\n</svg>",
119
+ title: "撤销",
120
+ shortcut: "Ctrl+Z"
121
+ },
122
+ tiptapExtension: null,
123
+ command: (e) => e.chain().focus().undo().run(),
124
+ isActive: () => !1,
125
+ isDisabled: (e) => !e.can().undo()
126
+ }, p = {
127
+ name: "redo",
128
+ toolbar: {
129
+ icon: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" fill=\"currentColor\" style=\"transform:scaleX(-1)\">\n <path d=\"M6.25 3.75 2.5 6.25l3.75 2.5V6.875h5.938a4.062 4.062 0 1 1 0 8.125H8.125a.625.625 0 1 0 0 1.25h4.063a5.313 5.313 0 0 0 0-10.625H6.25z\"></path>\n</svg>",
130
+ title: "重做",
131
+ shortcut: "Ctrl+Shift+Z"
132
+ },
133
+ tiptapExtension: null,
134
+ command: (e) => e.chain().focus().redo().run(),
135
+ isActive: () => !1,
136
+ isDisabled: (e) => !e.can().redo()
137
+ }, m = {
138
+ name: "clearFormat",
139
+ toolbar: {
140
+ icon: "slash",
141
+ title: "清除格式"
142
+ },
143
+ tiptapExtension: null,
144
+ command: (e) => e.chain().focus().unsetAllMarks().run(),
145
+ isActive: () => !1
146
+ }, h = {
147
+ name: "formatPainter",
148
+ toolbar: {
149
+ icon: "edit-3",
150
+ title: "格式刷"
151
+ },
152
+ tiptapExtension: null,
153
+ command: () => console.log("[JEditor] 格式刷功能待实现"),
154
+ isActive: () => !1
155
+ }, g = class {
156
+ constructor({ node: e, editor: t, getPos: n }) {
157
+ this.node = e, this.editor = t, this.getPos = n, this._startX = 0, this._startWidth = 0, this._pendingWidth = null, this._activeCorner = null, this.dom = document.createElement("span"), this.dom.className = "image-node-wrapper", this.dom.contentEditable = "false", this.img = document.createElement("img"), this.img.src = e.attrs.src, this.img.alt = e.attrs.alt || "", this.img.draggable = !1, e.attrs.width && (this.img.style.width = e.attrs.width + "px"), this.dom.appendChild(this.img);
158
+ for (let e of [
159
+ "tl",
160
+ "tr",
161
+ "bl",
162
+ "br"
163
+ ]) {
164
+ let t = document.createElement("span");
165
+ t.className = "image-resize-handle", t.dataset.corner = e, t.addEventListener("mousedown", (t) => this._onHandleMouseDown(t, e)), this.dom.appendChild(t);
166
+ }
167
+ this.dom.addEventListener("click", (e) => {
168
+ e.stopPropagation();
169
+ let t = this.getPos();
170
+ typeof t == "number" && this.editor.commands.setNodeSelection(t);
171
+ }), this._boundMouseMove = this._handleMouseMove.bind(this), this._boundMouseUp = this._handleMouseUp.bind(this);
172
+ }
173
+ selectNode() {
174
+ this.dom.classList.add("is-selected");
175
+ }
176
+ deselectNode() {
177
+ this.dom.classList.remove("is-selected");
178
+ }
179
+ _onHandleMouseDown(e, t) {
180
+ e.preventDefault(), e.stopPropagation(), this._activeCorner = t, this._startX = e.clientX, this._startWidth = this.img.getBoundingClientRect().width, document.addEventListener("mousemove", this._boundMouseMove), document.addEventListener("mouseup", this._boundMouseUp), document.body.style.userSelect = "none";
181
+ }
182
+ _handleMouseMove(e) {
183
+ let t = e.clientX - this._startX, n = this._activeCorner.includes("r") ? this._startWidth + t : this._startWidth - t;
184
+ n = Math.max(50, Math.round(n)), this.img.style.width = n + "px", this._pendingWidth = n;
185
+ }
186
+ _handleMouseUp() {
187
+ if (document.removeEventListener("mousemove", this._boundMouseMove), document.removeEventListener("mouseup", this._boundMouseUp), document.body.style.userSelect = "", this._pendingWidth !== null) {
188
+ let e = this.getPos();
189
+ typeof e == "number" && this.editor.view.dispatch(this.editor.state.tr.setNodeMarkup(e, null, {
190
+ ...this.node.attrs,
191
+ width: this._pendingWidth
192
+ })), this._pendingWidth = null;
193
+ }
194
+ }
195
+ update(e) {
196
+ return e.type === this.node.type ? (this.node = e, this.img.src = e.attrs.src, this.img.alt = e.attrs.alt || "", e.attrs.width ? this.img.style.width = e.attrs.width + "px" : this.img.style.width = "", !0) : !1;
197
+ }
198
+ destroy() {
199
+ document.removeEventListener("mousemove", this._boundMouseMove), document.removeEventListener("mouseup", this._boundMouseUp), document.body.style.userSelect = "";
200
+ }
201
+ }, _ = n.create({
202
+ name: "image",
203
+ group: "inline",
204
+ inline: !0,
205
+ atom: !0,
206
+ draggable: !0,
207
+ addAttributes() {
208
+ return {
209
+ src: { default: null },
210
+ alt: { default: null },
211
+ width: { default: null }
212
+ };
213
+ },
214
+ parseHTML() {
215
+ return [{ tag: "img[src]" }];
216
+ },
217
+ renderHTML({ HTMLAttributes: e }) {
218
+ let t = { ...e };
219
+ return t.width && (t.style = `width:${t.width}px`), delete t.width, ["img", r(t)];
220
+ },
221
+ addNodeView() {
222
+ return (e) => new g(e);
223
+ },
224
+ addCommands() {
225
+ return { setImage: (e) => ({ commands: t }) => t.insertContent({
226
+ type: this.name,
227
+ attrs: e
228
+ }) };
229
+ }
230
+ }), v = null, y = null, b = {};
231
+ function x(e) {
232
+ if (!e || !e.type.startsWith("image/")) return;
233
+ let t = b.maxSize || 20 * 1024 * 1024;
234
+ if (e.size > t) {
235
+ alert(`图片大小不能超过 ${Math.round(t / 1024 / 1024)}MB`);
236
+ return;
237
+ }
238
+ let n = new FileReader();
239
+ n.onload = (e) => {
240
+ y.chain().focus().setImage({ src: e.target.result }).run();
241
+ }, n.readAsDataURL(e);
242
+ }
243
+ function S(e) {
244
+ let t = e.clipboardData?.items;
245
+ if (t) {
246
+ for (let n of t) if (n.type.startsWith("image/")) {
247
+ e.preventDefault(), x(n.getAsFile());
248
+ return;
249
+ }
250
+ }
251
+ }
252
+ var C = {
253
+ name: "insertImage",
254
+ toolbar: {
255
+ icon: "image",
256
+ title: "插入图片"
257
+ },
258
+ tiptapExtension: _,
259
+ command: () => {
260
+ v && v.click();
261
+ },
262
+ isActive: () => !1,
263
+ init(e, t) {
264
+ y = e, b = t, v = document.createElement("input"), v.type = "file", v.accept = t.accept || "image/png,image/jpeg,image/gif,image/webp,image/svg+xml", v.style.display = "none", document.body.appendChild(v), v.addEventListener("change", () => {
265
+ let e = v.files[0];
266
+ e && x(e), v.value = "";
267
+ }), e.view.dom.addEventListener("paste", S);
268
+ },
269
+ destroy() {
270
+ v &&= (v.remove(), null), y &&= (y.view.dom.removeEventListener("paste", S), null);
271
+ }
272
+ };
273
+ //#endregion
274
+ //#region src/plugins/placeholders.js
275
+ function w(e, t) {
276
+ return {
277
+ name: e,
278
+ toolbar: t,
279
+ tiptapExtension: null,
280
+ command: () => console.log(`[JEditor] ${t.title} — 待实现`),
281
+ isActive: () => !1
282
+ };
283
+ }
284
+ //#endregion
285
+ //#region src/plugins/index.js
286
+ var T = [
287
+ f,
288
+ p,
289
+ c,
290
+ l,
291
+ u,
292
+ d,
293
+ m,
294
+ h,
295
+ C,
296
+ ...[
297
+ w("insert", {
298
+ text: "插入",
299
+ title: "插入",
300
+ dropdown: !0
301
+ }),
302
+ w("attachment", {
303
+ icon: "paperclip",
304
+ title: "附件"
305
+ }),
306
+ w("table", {
307
+ icon: "grid",
308
+ title: "表格"
309
+ }),
310
+ w("link", {
311
+ icon: "link",
312
+ title: "链接"
313
+ }),
314
+ w("mention", {
315
+ icon: "at-sign",
316
+ title: "提及"
317
+ }),
318
+ w("more", {
319
+ icon: "more-horizontal",
320
+ title: "更多"
321
+ }),
322
+ w("fullscreen", {
323
+ icon: "maximize-2",
324
+ title: "全屏"
325
+ }),
326
+ w("heading", {
327
+ text: "正文",
328
+ title: "段落样式",
329
+ dropdown: !0
330
+ }),
331
+ w("fontFamily", {
332
+ text: "微软雅黑",
333
+ title: "字体",
334
+ dropdown: !0
335
+ }),
336
+ w("fontSizeDown", {
337
+ icon: "minus",
338
+ title: "减小字号"
339
+ }),
340
+ w("fontSize", {
341
+ text: "14",
342
+ title: "字号"
343
+ }),
344
+ w("fontSizeUp", {
345
+ icon: "plus",
346
+ title: "增大字号"
347
+ }),
348
+ w("textColor", {
349
+ type: "color",
350
+ text: "A",
351
+ colorBar: "#ef4444",
352
+ title: "文字颜色"
353
+ }),
354
+ w("highlight", {
355
+ type: "color",
356
+ text: "笔",
357
+ colorBar: "#facc15",
358
+ title: "背景颜色"
359
+ }),
360
+ w("bulletList", {
361
+ icon: "list",
362
+ title: "无序列表"
363
+ }),
364
+ w("orderedList", {
365
+ icon: "hash",
366
+ title: "有序列表"
367
+ }),
368
+ w("alignLeft", {
369
+ icon: "align-left",
370
+ title: "左对齐"
371
+ }),
372
+ w("alignCenter", {
373
+ icon: "align-center",
374
+ title: "居中"
375
+ }),
376
+ w("alignRight", {
377
+ icon: "align-right",
378
+ title: "右对齐"
379
+ }),
380
+ w("lineHeight", {
381
+ text: "1.5",
382
+ title: "行间距"
383
+ }),
384
+ w("blockquote", {
385
+ icon: "message-square",
386
+ title: "引用"
387
+ }),
388
+ w("codeBlock", {
389
+ text: "</>",
390
+ title: "代码块",
391
+ className: "font-mono font-bold text-indigo-600"
392
+ })
393
+ ]
394
+ ];
395
+ //#endregion
396
+ //#region src/editor/index.js
397
+ function E(e, n = [], r = {}) {
398
+ return new t({
399
+ element: e,
400
+ extensions: [i.configure({ history: !0 }), ...n],
401
+ editorProps: { attributes: {
402
+ class: "tiptap-editor",
403
+ spellcheck: "false"
404
+ } },
405
+ content: r.content || `<p>${r.placeholder || "开始在此编写文档..."}</p>`
406
+ });
407
+ }
408
+ //#endregion
409
+ //#region src/toolbar/ui.js
410
+ var D = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"></polyline></svg>", O = ["je-toolbar-row je-toolbar-row--primary", "je-toolbar-row je-toolbar-row--secondary"];
411
+ function k(e) {
412
+ return e ? e.startsWith("<") ? e : window.feather?.icons[e] ? feather.icons[e].toSvg({
413
+ width: 20,
414
+ height: 20
415
+ }) : null : null;
416
+ }
417
+ function A(e) {
418
+ let t = e.toolbar, n = t.dropdown === !0;
419
+ if (t.type === "color") {
420
+ let n = document.createElement("div");
421
+ n.className = "je-color-group";
422
+ let r = document.createElement("button");
423
+ r.className = "tool-btn", r.dataset.command = e.name, r.title = t.title || "", r.innerHTML = `<span class="je-color-inner"><span class="je-color-char">${t.text || "A"}</span><span class="je-color-bar" style="background:${t.colorBar || "#000"}"></span></span>`;
424
+ let i = document.createElement("button");
425
+ return i.className = "tool-btn-arrow-down", i.innerHTML = D, n.append(r, i), n;
426
+ }
427
+ if (n) {
428
+ let n = document.createElement("button");
429
+ return n.className = "tool-btn-text", n.dataset.command = e.name, n.title = t.title || "", n.innerHTML = `${t.text || ""} ${D}`, n;
430
+ }
431
+ let r = document.createElement("button");
432
+ r.className = "tool-btn", t.className && (r.className += " " + t.className), r.dataset.command = e.name, r.title = t.shortcut ? `${t.title} (${t.shortcut})` : t.title || "";
433
+ let i = k(t.icon);
434
+ return i ? r.innerHTML = i : r.textContent = t.text || "", r;
435
+ }
436
+ function j() {
437
+ let e = document.createElement("div");
438
+ return e.className = "v-divider", e;
439
+ }
440
+ function M() {
441
+ let e = document.createElement("div");
442
+ return e.className = "je-spacer", e;
443
+ }
444
+ function N(e, t) {
445
+ let n = document.createDocumentFragment();
446
+ return e.toolbar.forEach((e, r) => {
447
+ let i = document.createElement("div");
448
+ i.className = O[r] || O[0], e.forEach((e) => {
449
+ if (e === "|") i.appendChild(j());
450
+ else if (e === "->") i.appendChild(M());
451
+ else {
452
+ let n = t.get(e);
453
+ n && i.appendChild(A(n));
454
+ }
455
+ }), n.appendChild(i);
456
+ }), n;
457
+ }
458
+ function P(e, t, n) {
459
+ e.querySelectorAll("[data-command]").forEach((e) => {
460
+ e.addEventListener("click", () => {
461
+ let r = n.get(e.dataset.command);
462
+ r && r.command(t);
463
+ });
464
+ });
465
+ let r = n.getAll();
466
+ function i() {
467
+ r.forEach((n) => {
468
+ let r = e.querySelector(`[data-command="${n.name}"]`);
469
+ r && (typeof n.isActive == "function" && r.classList.toggle("is-active", n.isActive(t)), typeof n.isDisabled == "function" && r.classList.toggle("is-disabled", n.isDisabled(t)));
470
+ });
471
+ }
472
+ t.on("selectionUpdate", i), t.on("update", i), i();
473
+ }
474
+ //#endregion
475
+ //#region src/jeditor.js
476
+ var F = class e {
477
+ static create(t, n = {}) {
478
+ let r = typeof t == "string" ? document.querySelector(t) : t;
479
+ if (!r) throw Error(`[JEditor] 找不到容器元素: ${t}`);
480
+ return new e(r, n);
481
+ }
482
+ constructor(e, t = {}) {
483
+ this._container = e, this._config = s(t), this._pm = new a(), this._editor = null, this._bootstrap();
484
+ }
485
+ _bootstrap() {
486
+ let { _container: e, _config: t, _pm: n } = this;
487
+ e.classList.add("je-container"), n.registerAll(T), e.appendChild(N(t, n));
488
+ let r = document.createElement("div");
489
+ r.className = "je-editor-area", e.appendChild(r), this._editor = E(r, n.getTiptapExtensions(), t), n.initAll(this._editor, t), P(e, this._editor, n);
490
+ }
491
+ getHTML() {
492
+ return this._editor.getHTML();
493
+ }
494
+ getJSON() {
495
+ return this._editor.getJSON();
496
+ }
497
+ getText() {
498
+ return this._editor.getText();
499
+ }
500
+ setContent(e, t = !1) {
501
+ this._editor.commands.setContent(e, t);
502
+ }
503
+ isEmpty() {
504
+ return this._editor.isEmpty;
505
+ }
506
+ focus() {
507
+ this._editor.commands.focus();
508
+ }
509
+ on(e, t) {
510
+ return this._editor.on(e, t), this;
511
+ }
512
+ off(e, t) {
513
+ return this._editor.off(e, t), this;
514
+ }
515
+ get tiptap() {
516
+ return this._editor;
517
+ }
518
+ destroy() {
519
+ this._pm.destroyAll(), this._editor.destroy(), this._container.innerHTML = "", this._container.classList.remove("je-container"), this._editor = null;
520
+ }
521
+ };
522
+ //#endregion
523
+ export { F as JEditor };
@@ -0,0 +1,5 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`@tiptap/extension-underline`),require(`@tiptap/core`),require(`@tiptap/starter-kit`)):typeof define==`function`&&define.amd?define([`exports`,`@tiptap/extension-underline`,`@tiptap/core`,`@tiptap/starter-kit`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.JEditor={},e.TiptapUnderline,e.TiptapCore,e.StarterKit))})(this,function(e,t,n,r){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var i=Object.create,a=Object.defineProperty,o=Object.getOwnPropertyDescriptor,s=Object.getOwnPropertyNames,c=Object.getPrototypeOf,l=Object.prototype.hasOwnProperty,u=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var i=s(t),c=0,u=i.length,d;c<u;c++)d=i[c],!l.call(e,d)&&d!==n&&a(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=o(t,d))||r.enumerable});return e},d=(e,t,n)=>(n=e==null?{}:i(c(e)),u(t||!e||!e.__esModule?a(n,`default`,{value:e,enumerable:!0}):n,e));t=d(t),r=d(r);var f=class{constructor(){this._plugins=new Map}register(e){if(!e.name)throw Error(`[JEditor] Plugin 缺少 name 字段`);this._plugins.set(e.name,e)}registerAll(e){e.forEach(e=>this.register(e))}get(e){return this._plugins.get(e)}getAll(){return Array.from(this._plugins.values())}getTiptapExtensions(){return this.getAll().filter(e=>e.tiptapExtension!=null).map(e=>e.tiptapExtension)}initAll(e,t={}){this.getAll().forEach(n=>{typeof n.init==`function`&&n.init(e,t[n.name]||{})})}destroyAll(){this.getAll().forEach(e=>{typeof e.destroy==`function`&&e.destroy()}),this._plugins.clear()}},p={placeholder:`开始在此编写文档...`,toolbar:[[`undo`,`redo`,`|`,`insertImage`,`insert`,`|`,`attachment`,`table`,`link`,`mention`,`|`,`more`,`->`,`fullscreen`],`heading.|.fontFamily.|.fontSizeDown.fontSize.fontSizeUp.|.bold.italic.underline.strike.clearFormat.|.textColor.highlight.|.bulletList.orderedList.alignLeft.alignCenter.alignRight.lineHeight.|.blockquote.codeBlock`.split(`.`)],image:{maxSize:20*1024*1024,uploadUrl:null,accept:`image/png,image/jpeg,image/gif,image/webp,image/svg+xml`}};function m(e={}){let t={...p,...e};return e.image&&(t.image={...p.image,...e.image}),t}var h={name:`bold`,toolbar:{text:`B`,title:`粗体`,shortcut:`Ctrl+B`,className:`font-bold`},tiptapExtension:null,command:e=>e.chain().focus().toggleBold().run(),isActive:e=>e.isActive(`bold`)},g={name:`italic`,toolbar:{text:`I`,title:`斜体`,shortcut:`Ctrl+I`,className:`italic`},tiptapExtension:null,command:e=>e.chain().focus().toggleItalic().run(),isActive:e=>e.isActive(`italic`)},_={name:`underline`,toolbar:{text:`U`,title:`下划线`,shortcut:`Ctrl+U`,className:`underline`},tiptapExtension:t.default,command:e=>e.chain().focus().toggleUnderline().run(),isActive:e=>e.isActive(`underline`)},v={name:`strike`,toolbar:{text:`S`,title:`删除线`,shortcut:`Ctrl+Shift+X`,className:`line-through text-[13px]`},tiptapExtension:null,command:e=>e.chain().focus().toggleStrike().run(),isActive:e=>e.isActive(`strike`)},y={name:`undo`,toolbar:{icon:`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
2
+ <path d="M6.25 3.75 2.5 6.25l3.75 2.5V6.875h5.938a4.062 4.062 0 1 1 0 8.125H8.125a.625.625 0 1 0 0 1.25h4.063a5.313 5.313 0 0 0 0-10.625H6.25z"></path>
3
+ </svg>`,title:`撤销`,shortcut:`Ctrl+Z`},tiptapExtension:null,command:e=>e.chain().focus().undo().run(),isActive:()=>!1,isDisabled:e=>!e.can().undo()},b={name:`redo`,toolbar:{icon:`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" style="transform:scaleX(-1)">
4
+ <path d="M6.25 3.75 2.5 6.25l3.75 2.5V6.875h5.938a4.062 4.062 0 1 1 0 8.125H8.125a.625.625 0 1 0 0 1.25h4.063a5.313 5.313 0 0 0 0-10.625H6.25z"></path>
5
+ </svg>`,title:`重做`,shortcut:`Ctrl+Shift+Z`},tiptapExtension:null,command:e=>e.chain().focus().redo().run(),isActive:()=>!1,isDisabled:e=>!e.can().redo()},x={name:`clearFormat`,toolbar:{icon:`slash`,title:`清除格式`},tiptapExtension:null,command:e=>e.chain().focus().unsetAllMarks().run(),isActive:()=>!1},S={name:`formatPainter`,toolbar:{icon:`edit-3`,title:`格式刷`},tiptapExtension:null,command:()=>console.log(`[JEditor] 格式刷功能待实现`),isActive:()=>!1},C=class{constructor({node:e,editor:t,getPos:n}){this.node=e,this.editor=t,this.getPos=n,this._startX=0,this._startWidth=0,this._pendingWidth=null,this._activeCorner=null,this.dom=document.createElement(`span`),this.dom.className=`image-node-wrapper`,this.dom.contentEditable=`false`,this.img=document.createElement(`img`),this.img.src=e.attrs.src,this.img.alt=e.attrs.alt||``,this.img.draggable=!1,e.attrs.width&&(this.img.style.width=e.attrs.width+`px`),this.dom.appendChild(this.img);for(let e of[`tl`,`tr`,`bl`,`br`]){let t=document.createElement(`span`);t.className=`image-resize-handle`,t.dataset.corner=e,t.addEventListener(`mousedown`,t=>this._onHandleMouseDown(t,e)),this.dom.appendChild(t)}this.dom.addEventListener(`click`,e=>{e.stopPropagation();let t=this.getPos();typeof t==`number`&&this.editor.commands.setNodeSelection(t)}),this._boundMouseMove=this._handleMouseMove.bind(this),this._boundMouseUp=this._handleMouseUp.bind(this)}selectNode(){this.dom.classList.add(`is-selected`)}deselectNode(){this.dom.classList.remove(`is-selected`)}_onHandleMouseDown(e,t){e.preventDefault(),e.stopPropagation(),this._activeCorner=t,this._startX=e.clientX,this._startWidth=this.img.getBoundingClientRect().width,document.addEventListener(`mousemove`,this._boundMouseMove),document.addEventListener(`mouseup`,this._boundMouseUp),document.body.style.userSelect=`none`}_handleMouseMove(e){let t=e.clientX-this._startX,n=this._activeCorner.includes(`r`)?this._startWidth+t:this._startWidth-t;n=Math.max(50,Math.round(n)),this.img.style.width=n+`px`,this._pendingWidth=n}_handleMouseUp(){if(document.removeEventListener(`mousemove`,this._boundMouseMove),document.removeEventListener(`mouseup`,this._boundMouseUp),document.body.style.userSelect=``,this._pendingWidth!==null){let e=this.getPos();typeof e==`number`&&this.editor.view.dispatch(this.editor.state.tr.setNodeMarkup(e,null,{...this.node.attrs,width:this._pendingWidth})),this._pendingWidth=null}}update(e){return e.type===this.node.type?(this.node=e,this.img.src=e.attrs.src,this.img.alt=e.attrs.alt||``,e.attrs.width?this.img.style.width=e.attrs.width+`px`:this.img.style.width=``,!0):!1}destroy(){document.removeEventListener(`mousemove`,this._boundMouseMove),document.removeEventListener(`mouseup`,this._boundMouseUp),document.body.style.userSelect=``}},w=n.Node.create({name:`image`,group:`inline`,inline:!0,atom:!0,draggable:!0,addAttributes(){return{src:{default:null},alt:{default:null},width:{default:null}}},parseHTML(){return[{tag:`img[src]`}]},renderHTML({HTMLAttributes:e}){let t={...e};return t.width&&(t.style=`width:${t.width}px`),delete t.width,[`img`,(0,n.mergeAttributes)(t)]},addNodeView(){return e=>new C(e)},addCommands(){return{setImage:e=>({commands:t})=>t.insertContent({type:this.name,attrs:e})}}}),T=null,E=null,D={};function O(e){if(!e||!e.type.startsWith(`image/`))return;let t=D.maxSize||20*1024*1024;if(e.size>t){alert(`图片大小不能超过 ${Math.round(t/1024/1024)}MB`);return}let n=new FileReader;n.onload=e=>{E.chain().focus().setImage({src:e.target.result}).run()},n.readAsDataURL(e)}function k(e){let t=e.clipboardData?.items;if(t){for(let n of t)if(n.type.startsWith(`image/`)){e.preventDefault(),O(n.getAsFile());return}}}var A={name:`insertImage`,toolbar:{icon:`image`,title:`插入图片`},tiptapExtension:w,command:()=>{T&&T.click()},isActive:()=>!1,init(e,t){E=e,D=t,T=document.createElement(`input`),T.type=`file`,T.accept=t.accept||`image/png,image/jpeg,image/gif,image/webp,image/svg+xml`,T.style.display=`none`,document.body.appendChild(T),T.addEventListener(`change`,()=>{let e=T.files[0];e&&O(e),T.value=``}),e.view.dom.addEventListener(`paste`,k)},destroy(){T&&=(T.remove(),null),E&&=(E.view.dom.removeEventListener(`paste`,k),null)}};function j(e,t){return{name:e,toolbar:t,tiptapExtension:null,command:()=>console.log(`[JEditor] ${t.title} — 待实现`),isActive:()=>!1}}var M=[y,b,h,g,_,v,x,S,A,...[j(`insert`,{text:`插入`,title:`插入`,dropdown:!0}),j(`attachment`,{icon:`paperclip`,title:`附件`}),j(`table`,{icon:`grid`,title:`表格`}),j(`link`,{icon:`link`,title:`链接`}),j(`mention`,{icon:`at-sign`,title:`提及`}),j(`more`,{icon:`more-horizontal`,title:`更多`}),j(`fullscreen`,{icon:`maximize-2`,title:`全屏`}),j(`heading`,{text:`正文`,title:`段落样式`,dropdown:!0}),j(`fontFamily`,{text:`微软雅黑`,title:`字体`,dropdown:!0}),j(`fontSizeDown`,{icon:`minus`,title:`减小字号`}),j(`fontSize`,{text:`14`,title:`字号`}),j(`fontSizeUp`,{icon:`plus`,title:`增大字号`}),j(`textColor`,{type:`color`,text:`A`,colorBar:`#ef4444`,title:`文字颜色`}),j(`highlight`,{type:`color`,text:`笔`,colorBar:`#facc15`,title:`背景颜色`}),j(`bulletList`,{icon:`list`,title:`无序列表`}),j(`orderedList`,{icon:`hash`,title:`有序列表`}),j(`alignLeft`,{icon:`align-left`,title:`左对齐`}),j(`alignCenter`,{icon:`align-center`,title:`居中`}),j(`alignRight`,{icon:`align-right`,title:`右对齐`}),j(`lineHeight`,{text:`1.5`,title:`行间距`}),j(`blockquote`,{icon:`message-square`,title:`引用`}),j(`codeBlock`,{text:`</>`,title:`代码块`,className:`font-mono font-bold text-indigo-600`})]];function N(e,t=[],i={}){return new n.Editor({element:e,extensions:[r.default.configure({history:!0}),...t],editorProps:{attributes:{class:`tiptap-editor`,spellcheck:`false`}},content:i.content||`<p>${i.placeholder||`开始在此编写文档...`}</p>`})}var P=`<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>`,F=[`je-toolbar-row je-toolbar-row--primary`,`je-toolbar-row je-toolbar-row--secondary`];function I(e){return e?e.startsWith(`<`)?e:window.feather?.icons[e]?feather.icons[e].toSvg({width:20,height:20}):null:null}function L(e){let t=e.toolbar,n=t.dropdown===!0;if(t.type===`color`){let n=document.createElement(`div`);n.className=`je-color-group`;let r=document.createElement(`button`);r.className=`tool-btn`,r.dataset.command=e.name,r.title=t.title||``,r.innerHTML=`<span class="je-color-inner"><span class="je-color-char">${t.text||`A`}</span><span class="je-color-bar" style="background:${t.colorBar||`#000`}"></span></span>`;let i=document.createElement(`button`);return i.className=`tool-btn-arrow-down`,i.innerHTML=P,n.append(r,i),n}if(n){let n=document.createElement(`button`);return n.className=`tool-btn-text`,n.dataset.command=e.name,n.title=t.title||``,n.innerHTML=`${t.text||``} ${P}`,n}let r=document.createElement(`button`);r.className=`tool-btn`,t.className&&(r.className+=` `+t.className),r.dataset.command=e.name,r.title=t.shortcut?`${t.title} (${t.shortcut})`:t.title||``;let i=I(t.icon);return i?r.innerHTML=i:r.textContent=t.text||``,r}function R(){let e=document.createElement(`div`);return e.className=`v-divider`,e}function z(){let e=document.createElement(`div`);return e.className=`je-spacer`,e}function B(e,t){let n=document.createDocumentFragment();return e.toolbar.forEach((e,r)=>{let i=document.createElement(`div`);i.className=F[r]||F[0],e.forEach(e=>{if(e===`|`)i.appendChild(R());else if(e===`->`)i.appendChild(z());else{let n=t.get(e);n&&i.appendChild(L(n))}}),n.appendChild(i)}),n}function V(e,t,n){e.querySelectorAll(`[data-command]`).forEach(e=>{e.addEventListener(`click`,()=>{let r=n.get(e.dataset.command);r&&r.command(t)})});let r=n.getAll();function i(){r.forEach(n=>{let r=e.querySelector(`[data-command="${n.name}"]`);r&&(typeof n.isActive==`function`&&r.classList.toggle(`is-active`,n.isActive(t)),typeof n.isDisabled==`function`&&r.classList.toggle(`is-disabled`,n.isDisabled(t)))})}t.on(`selectionUpdate`,i),t.on(`update`,i),i()}e.JEditor=class e{static create(t,n={}){let r=typeof t==`string`?document.querySelector(t):t;if(!r)throw Error(`[JEditor] 找不到容器元素: ${t}`);return new e(r,n)}constructor(e,t={}){this._container=e,this._config=m(t),this._pm=new f,this._editor=null,this._bootstrap()}_bootstrap(){let{_container:e,_config:t,_pm:n}=this;e.classList.add(`je-container`),n.registerAll(M),e.appendChild(B(t,n));let r=document.createElement(`div`);r.className=`je-editor-area`,e.appendChild(r),this._editor=N(r,n.getTiptapExtensions(),t),n.initAll(this._editor,t),V(e,this._editor,n)}getHTML(){return this._editor.getHTML()}getJSON(){return this._editor.getJSON()}getText(){return this._editor.getText()}setContent(e,t=!1){this._editor.commands.setContent(e,t)}isEmpty(){return this._editor.isEmpty}focus(){this._editor.commands.focus()}on(e,t){return this._editor.on(e,t),this}off(e,t){return this._editor.off(e,t),this}get tiptap(){return this._editor}destroy(){this._pm.destroyAll(),this._editor.destroy(),this._container.innerHTML=``,this._container.classList.remove(`je-container`),this._editor=null}}});
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@jenkin-a/jeditor",
3
+ "version": "1.0.0",
4
+ "description": "JEditor - 腾讯系办公风格富文本编辑器",
5
+ "type": "module",
6
+ "main": "dist/jeditor.umd.js",
7
+ "module": "dist/jeditor.es.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/jeditor.es.js",
11
+ "require": "./dist/jeditor.umd.js"
12
+ },
13
+ "./dist/jeditor.css": "./dist/jeditor.css"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "dev": "vite",
23
+ "build": "vite build",
24
+ "preview": "vite preview"
25
+ },
26
+ "keywords": [
27
+ "editor",
28
+ "rich-text",
29
+ "tiptap",
30
+ "wysiwyg"
31
+ ],
32
+ "author": "jenkin",
33
+ "license": "MIT",
34
+ "peerDependencies": {
35
+ "@tiptap/core": "^3.0.0",
36
+ "@tiptap/starter-kit": "^3.0.0",
37
+ "@tiptap/extension-underline": "^3.0.0",
38
+ "@tiptap/extension-image": "^3.0.0",
39
+ "@tiptap/pm": "^3.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "@tiptap/core": "^3.20.4",
43
+ "@tiptap/extension-image": "^3.20.4",
44
+ "@tiptap/extension-underline": "^3.20.4",
45
+ "@tiptap/pm": "^3.20.4",
46
+ "@tiptap/starter-kit": "^3.20.4",
47
+ "vite": "^8.0.1"
48
+ }
49
+ }