@jenkin-a/jeditor 1.0.0 → 1.0.1

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/Demo/cdn.html ADDED
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>JEditor CDN Demo</title>
7
+ <link rel="stylesheet" href="../dist/jeditor.css">
8
+ <script src="https://unpkg.com/feather-icons"></script>
9
+ </head>
10
+ <body style="margin:0;background:#f2f2f2;font-family:Arial,sans-serif;">
11
+ <div style="max-width:1200px;margin:40px auto;padding:0 16px;">
12
+ <div id="cdn-editor">
13
+ <h2>CDN bootstrap</h2>
14
+ <p>This block is parsed from native HTML without Vite.</p>
15
+ <p>After building, open this file and load <code>../dist/jeditor.iife.js</code>.</p>
16
+ </div>
17
+ </div>
18
+
19
+ <script src="../dist/jeditor.iife.js"></script>
20
+ <script>
21
+ window.editor = window.JEditor.create('#cdn-editor')
22
+ </script>
23
+ </body>
24
+ </html>
package/Demo/img.png ADDED
Binary file
package/README.en.md ADDED
@@ -0,0 +1,121 @@
1
+ # JEditor
2
+
3
+ Language: English | [中文](README.md)
4
+
5
+ Current version: v1.0.1
6
+
7
+ JEditor is a lightweight rich-text editor built on Tiptap. It uses a plugin-driven toolbar architecture and exposes a simple DOM API (`JEditor.create`). In v1.0.1, two key capabilities are added:
8
+
9
+ - native HTML bootstrapping from an existing container
10
+ - self-contained UMD / IIFE browser builds for CDN usage
11
+
12
+ ![Screenshot](Demo/img.png)
13
+
14
+ ## Current capabilities
15
+ - ESM build for Vite / Webpack / npm projects
16
+ - self-contained UMD / IIFE builds for direct browser usage
17
+ - native HTML bootstrap from existing container markup
18
+ - `textarea` bootstrap with automatic HTML sync
19
+ - core text formatting:
20
+ - bold
21
+ - italic
22
+ - underline
23
+ - strike
24
+ - undo / redo
25
+ - clear format
26
+ - image plugin:
27
+ - paste images
28
+ - base64 insertion
29
+ - drag-to-resize
30
+ - reserved `uploadUrl` hook
31
+
32
+ ## Install and develop
33
+ ```bash
34
+ npm install
35
+ npm run dev
36
+ npm run build
37
+ npm run preview
38
+ ```
39
+
40
+ Build outputs:
41
+ - `dist/jeditor.es.js`
42
+ - `dist/jeditor.umd.js`
43
+ - `dist/jeditor.iife.js`
44
+ - `dist/jeditor.css`
45
+
46
+ ## ESM usage
47
+ ```js
48
+ import 'jeditor/dist/jeditor.css'
49
+ import { JEditor } from '@jenkin-a/jeditor'
50
+
51
+ const editor = JEditor.create('#j-editor-container', {
52
+ placeholder: 'Start creating...',
53
+ })
54
+ ```
55
+
56
+ You can also create an editor from an HTML string:
57
+ ```js
58
+ const editor = JEditor.fromHTML('#j-editor-container', '<h2>Hello</h2><p>World</p>')
59
+ ```
60
+
61
+ If the container already contains HTML, JEditor will parse it automatically:
62
+ ```html
63
+ <div id="editor">
64
+ <h2>Existing HTML</h2>
65
+ <p>This will be parsed as the initial content.</p>
66
+ </div>
67
+ ```
68
+
69
+ ```js
70
+ JEditor.create('#editor')
71
+ ```
72
+
73
+ ## CDN / direct browser usage
74
+ ```html
75
+ <link rel="stylesheet" href="https://unpkg.com/@jenkin-a/jeditor/dist/jeditor.css" />
76
+ <script src="https://unpkg.com/feather-icons"></script>
77
+ <script src="https://unpkg.com/@jenkin-a/jeditor/dist/jeditor.iife.js"></script>
78
+
79
+ <div id="editor">
80
+ <h2>Hello JEditor</h2>
81
+ <p>This HTML is parsed on startup.</p>
82
+ </div>
83
+
84
+ <script>
85
+ const editor = window.JEditor.create('#editor')
86
+ </script>
87
+ ```
88
+
89
+ For a local browser-only example, run `npm run build` and open `Demo/cdn.html`.
90
+
91
+ ## Public API
92
+ ```js
93
+ editor.getHTML()
94
+ editor.getJSON()
95
+ editor.getText()
96
+ editor.setContent('<p>Hello</p>')
97
+ editor.importHTML('<p>Hello</p>')
98
+ editor.focus()
99
+ editor.destroy()
100
+ ```
101
+
102
+ ## Dependency notes
103
+ The ESM build expects these peer dependencies in your app:
104
+ ```bash
105
+ npm install @tiptap/core @tiptap/starter-kit @tiptap/extension-underline @tiptap/extension-image @tiptap/pm
106
+ ```
107
+
108
+ The CDN / IIFE build is self-contained and does not require extra Tiptap imports.
109
+
110
+ ## Known limitations
111
+ - Several toolbar buttons are still placeholders; see `src/plugins/placeholders.js`
112
+ - Image upload still defaults to base64; the full `uploadUrl` pipeline is not wired yet
113
+ - Source HTML editing mode is planned for the next major feature cycle
114
+
115
+ ## Roadmap
116
+ 1. v1.0.1: native HTML bootstrap + self-contained CDN / IIFE build
117
+ 2. v1.1.x: fill core editing features such as headings, lists, alignment, colors, font controls, links, and code blocks
118
+ 3. v1.2.x: Source HTML mode with visual ↔ HTML switching
119
+
120
+ ## License
121
+ MIT
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # JEditor
2
+
3
+ Language: [English](README.en.md) | 中文
4
+
5
+ 当前版本:v1.0.1
6
+
7
+ JEditor 是一个基于 Tiptap 的轻量富文本编辑器,采用插件驱动的工具栏架构,并提供简单直接的 DOM API(`JEditor.create`)。v1.0.1 开始补上两项关键能力:
8
+
9
+ - 支持从容器原生 HTML 直接初始化编辑器
10
+ - 支持自包含的 UMD / IIFE 浏览器构建,可用于 CDN 场景
11
+
12
+ ![示例截图](Demo/img.png)
13
+
14
+ ## 当前能力
15
+ - ESM 构建:适合 Vite / Webpack / npm 场景
16
+ - UMD / IIFE 自包含构建:适合浏览器脚本直接引入
17
+ - 原生 HTML 初始化:容器里的现有 HTML 会作为初始内容被解析
18
+ - `textarea` 初始化:可直接从 `textarea` 启动,并自动回写 HTML
19
+ - 基础文本格式:
20
+ - 粗体(bold)
21
+ - 斜体(italic)
22
+ - 下划线(underline)
23
+ - 删除线(strike)
24
+ - 撤销 / 重做(undo / redo)
25
+ - 清除格式(clear format)
26
+ - 图片插件:
27
+ - 支持粘贴图片
28
+ - 支持 base64 方式插入
29
+ - 支持拖拽调整大小
30
+ - 预留 `uploadUrl` 上传接口(待实现)
31
+
32
+ ## 安装与开发
33
+ ```bash
34
+ npm install
35
+ npm run dev
36
+ npm run build
37
+ npm run preview
38
+ ```
39
+
40
+ 构建后会生成:
41
+ - `dist/jeditor.es.js`
42
+ - `dist/jeditor.umd.js`
43
+ - `dist/jeditor.iife.js`
44
+ - `dist/jeditor.css`
45
+
46
+ ## ESM 用法
47
+ ```js
48
+ import 'jeditor/dist/jeditor.css'
49
+ import { JEditor } from '@jenkin-a/jeditor'
50
+
51
+ const editor = JEditor.create('#j-editor-container', {
52
+ placeholder: '开始你的创作...',
53
+ })
54
+ ```
55
+
56
+ 也可以直接导入 HTML:
57
+ ```js
58
+ const editor = JEditor.fromHTML('#j-editor-container', '<h2>Hello</h2><p>World</p>')
59
+ ```
60
+
61
+ 如果容器里本身已经有 HTML,直接创建即可:
62
+ ```html
63
+ <div id="editor">
64
+ <h2>Existing HTML</h2>
65
+ <p>This will be parsed as the initial content.</p>
66
+ </div>
67
+ ```
68
+
69
+ ```js
70
+ JEditor.create('#editor')
71
+ ```
72
+
73
+ ## CDN / 浏览器直连
74
+ ```html
75
+ <link rel="stylesheet" href="https://unpkg.com/@jenkin-a/jeditor/dist/jeditor.css" />
76
+ <script src="https://unpkg.com/feather-icons"></script>
77
+ <script src="https://unpkg.com/@jenkin-a/jeditor/dist/jeditor.iife.js"></script>
78
+
79
+ <div id="editor">
80
+ <h2>Hello JEditor</h2>
81
+ <p>This HTML is parsed on startup.</p>
82
+ </div>
83
+
84
+ <script>
85
+ const editor = window.JEditor.create('#editor')
86
+ </script>
87
+ ```
88
+
89
+ 本地也可以先执行 `npm run build`,然后打开 `Demo/cdn.html` 查看浏览器直连示例。
90
+
91
+ ## 公开 API
92
+ ```js
93
+ editor.getHTML()
94
+ editor.getJSON()
95
+ editor.getText()
96
+ editor.setContent('<p>Hello</p>')
97
+ editor.importHTML('<p>Hello</p>')
98
+ editor.focus()
99
+ editor.destroy()
100
+ ```
101
+
102
+ ## 依赖说明
103
+ ESM 版本需要你的项目安装以下 peer 依赖:
104
+ ```bash
105
+ npm install @tiptap/core @tiptap/starter-kit @tiptap/extension-underline @tiptap/extension-image @tiptap/pm
106
+ ```
107
+
108
+ CDN / IIFE 版本为自包含构建,不需要额外引入 Tiptap。
109
+
110
+ ## 已知限制
111
+ - 当前仍有部分工具栏按钮是占位能力,见 `src/plugins/placeholders.js`
112
+ - 图片上传仍默认使用 base64,`uploadUrl` 尚未接入完整上传链路
113
+ - Source HTML 编辑模式是后续版本的核心工作
114
+
115
+ ## 路线图
116
+ 1. v1.0.1:原生 HTML 初始化 + CDN / IIFE 自包含构建
117
+ 2. v1.1.x:补齐基础编辑能力(标题、列表、对齐、颜色、字号、字体、链接、代码块等)
118
+ 3. v1.2.x:Source HTML 模式(可视化 ↔ HTML 双向切换)
119
+
120
+ ## 许可
121
+ MIT
@@ -24,7 +24,10 @@ var a = class {
24
24
  }
25
25
  initAll(e, t = {}) {
26
26
  this.getAll().forEach((n) => {
27
- typeof n.init == "function" && n.init(e, t[n.name] || {});
27
+ if (typeof n.init == "function") {
28
+ let r = n.configKey || n.name;
29
+ n.init(e, t[r] || {});
30
+ }
28
31
  });
29
32
  }
30
33
  destroyAll() {
@@ -251,6 +254,7 @@ function S(e) {
251
254
  }
252
255
  var C = {
253
256
  name: "insertImage",
257
+ configKey: "image",
254
258
  toolbar: {
255
259
  icon: "image",
256
260
  title: "插入图片"
@@ -399,17 +403,17 @@ function E(e, n = [], r = {}) {
399
403
  element: e,
400
404
  extensions: [i.configure({ history: !0 }), ...n],
401
405
  editorProps: { attributes: {
402
- class: "tiptap-editor",
406
+ class: "tiptap tiptap-editor",
403
407
  spellcheck: "false"
404
408
  } },
405
- content: r.content || `<p>${r.placeholder || "开始在此编写文档..."}</p>`
409
+ content: r.content ?? `<p>${r.placeholder || "Start writing..."}</p>`
406
410
  });
407
411
  }
408
412
  //#endregion
409
413
  //#region src/toolbar/ui.js
410
414
  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
415
  function k(e) {
412
- return e ? e.startsWith("<") ? e : window.feather?.icons[e] ? feather.icons[e].toSvg({
416
+ return e ? e.startsWith("<") ? e : window.feather?.icons[e] ? window.feather.icons[e].toSvg({
413
417
  width: 20,
414
418
  height: 20
415
419
  }) : null : null;
@@ -446,47 +450,81 @@ function N(e, t) {
446
450
  return e.toolbar.forEach((e, r) => {
447
451
  let i = document.createElement("div");
448
452
  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));
453
+ if (e === "|") {
454
+ i.appendChild(j());
455
+ return;
456
+ }
457
+ if (e === "->") {
458
+ i.appendChild(M());
459
+ return;
454
460
  }
461
+ let n = t.get(e);
462
+ n && i.appendChild(A(n));
455
463
  }), n.appendChild(i);
456
464
  }), n;
457
465
  }
458
466
  function P(e, t, n) {
467
+ let r = [];
459
468
  e.querySelectorAll("[data-command]").forEach((e) => {
460
- e.addEventListener("click", () => {
469
+ let i = () => {
461
470
  let r = n.get(e.dataset.command);
462
471
  r && r.command(t);
463
- });
472
+ };
473
+ e.addEventListener("click", i), r.push(() => e.removeEventListener("click", i));
464
474
  });
465
- let r = n.getAll();
466
- function i() {
467
- r.forEach((n) => {
475
+ let i = n.getAll();
476
+ function a() {
477
+ i.forEach((n) => {
468
478
  let r = e.querySelector(`[data-command="${n.name}"]`);
469
479
  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
480
  });
471
481
  }
472
- t.on("selectionUpdate", i), t.on("update", i), i();
482
+ return t.on("selectionUpdate", a), t.on("update", a), r.push(() => t.off("selectionUpdate", a)), r.push(() => t.off("update", a)), a(), () => {
483
+ r.forEach((e) => e());
484
+ };
473
485
  }
474
486
  //#endregion
475
487
  //#region src/jeditor.js
476
- var F = class e {
488
+ function F(e) {
489
+ let t = typeof e == "string" ? document.querySelector(e) : e;
490
+ if (!t) throw Error(`[JEditor] Container not found: ${e}`);
491
+ return t;
492
+ }
493
+ function I(e, t) {
494
+ return t.content == null ? e instanceof HTMLTextAreaElement ? e.value.trim() || null : e.innerHTML.trim() || null : t.content;
495
+ }
496
+ function L(e) {
497
+ if (e instanceof HTMLTextAreaElement) {
498
+ let t = document.createElement("div");
499
+ return t.className = e.className, e.insertAdjacentElement("afterend", t), e.style.display = "none", t;
500
+ }
501
+ return e.innerHTML = "", e;
502
+ }
503
+ var R = class e {
477
504
  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);
505
+ return new e(F(t), n);
506
+ }
507
+ static fromHTML(t, n, r = {}) {
508
+ return e.create(t, {
509
+ ...r,
510
+ content: n
511
+ });
481
512
  }
482
513
  constructor(e, t = {}) {
483
- this._container = e, this._config = s(t), this._pm = new a(), this._editor = null, this._bootstrap();
514
+ this._sourceElement = e, this._config = s(t), this._pm = new a(), this._editor = null, this._toolbarCleanup = null, this._mountElement = null, this._bootstrap();
484
515
  }
485
516
  _bootstrap() {
486
- let { _container: e, _config: t, _pm: n } = this;
487
- e.classList.add("je-container"), n.registerAll(T), e.appendChild(N(t, n));
517
+ this._pm.registerAll(T);
518
+ let e = I(this._sourceElement, this._config), t = L(this._sourceElement), n = {
519
+ ...this._config,
520
+ content: e ?? this._config.content
521
+ };
522
+ t.classList.add("je-container"), t.appendChild(N(n, this._pm));
488
523
  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);
524
+ r.className = "je-editor-area", t.appendChild(r), this._editor = E(r, this._pm.getTiptapExtensions(), n), this._pm.initAll(this._editor, n), this._toolbarCleanup = P(t, this._editor, this._pm), this._mountElement = t, this._sourceElement instanceof HTMLTextAreaElement && (this._syncTextareaValue(), this._editor.on("update", () => this._syncTextareaValue()));
525
+ }
526
+ _syncTextareaValue() {
527
+ this._sourceElement instanceof HTMLTextAreaElement && this._editor && (this._sourceElement.value = this._editor.getHTML());
490
528
  }
491
529
  getHTML() {
492
530
  return this._editor.getHTML();
@@ -498,13 +536,16 @@ var F = class e {
498
536
  return this._editor.getText();
499
537
  }
500
538
  setContent(e, t = !1) {
501
- this._editor.commands.setContent(e, t);
539
+ return this._editor.commands.setContent(e, t), this._syncTextareaValue(), this;
540
+ }
541
+ importHTML(e, t = !1) {
542
+ return this.setContent(e, t);
502
543
  }
503
544
  isEmpty() {
504
545
  return this._editor.isEmpty;
505
546
  }
506
547
  focus() {
507
- this._editor.commands.focus();
548
+ return this._editor.commands.focus(), this;
508
549
  }
509
550
  on(e, t) {
510
551
  return this._editor.on(e, t), this;
@@ -516,8 +557,9 @@ var F = class e {
516
557
  return this._editor;
517
558
  }
518
559
  destroy() {
519
- this._pm.destroyAll(), this._editor.destroy(), this._container.innerHTML = "", this._container.classList.remove("je-container"), this._editor = null;
560
+ let e = this._editor ? this._editor.getHTML() : "";
561
+ this._toolbarCleanup?.(), this._pm.destroyAll(), this._editor?.destroy(), this._sourceElement instanceof HTMLTextAreaElement ? (this._sourceElement.value = e, this._sourceElement.style.display = "", this._mountElement?.remove()) : (this._mountElement.innerHTML = e, this._mountElement.classList.remove("je-container")), this._editor = null, this._mountElement = null;
520
562
  }
521
563
  };
522
564
  //#endregion
523
- export { F as JEditor };
565
+ export { R as JEditor };