@jenkin-a/jeditor 1.0.1 → 1.0.3

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/README.md CHANGED
@@ -2,34 +2,283 @@
2
2
 
3
3
  Language: [English](README.en.md) | 中文
4
4
 
5
- 当前版本:v1.0.1
5
+ 当前版本:`v1.0.2`
6
6
 
7
- JEditor 是一个基于 Tiptap 的轻量富文本编辑器,采用插件驱动的工具栏架构,并提供简单直接的 DOM API(`JEditor.create`)。v1.0.1 开始补上两项关键能力:
7
+ JEditor 是一个面向中文内容创作、静态片段编写与 Source HTML 编辑的 Web 富文本编辑器。它最初基于 Tiptap 构建,但当前版本已经不再只是一个“工具栏 + Tiptap 的简单封装”,而是在此基础上逐步演进为一个支持:
8
8
 
9
- - 支持从容器原生 HTML 直接初始化编辑器
10
- - 支持自包含的 UMD / IIFE 浏览器构建,可用于 CDN 场景
9
+ - 可视化文档编辑
10
+ - Source HTML 编辑
11
+ - HTML 保活与回写
12
+ - 浏览器 CDN 直连
11
13
 
12
- ![示例截图](Demo/img.png)
14
+ 的混合编辑架构。
15
+
16
+ ![JEditor Screenshot](Demo/img.png)
17
+
18
+ ## 项目目标
19
+
20
+ JEditor 的目标不是单纯复刻一个传统富文本编辑器,而是解决一个更难的问题:
21
+
22
+ `让用户既能像写文档一样编辑内容,又能尽可能保留和操作原始 HTML。`
23
+
24
+ 这也是 JEditor 与常规富文本工具的核心差异:
25
+
26
+ - 普通编辑器更强调“结构化内容”
27
+ - JEditor 同时强调“结构化编辑体验”和“HTML 存活能力”
13
28
 
14
29
  ## 当前能力
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` 上传接口(待实现)
30
+
31
+ 当前版本已经具备以下核心能力:
32
+
33
+ - 双工具栏富文本编辑体验
34
+ - 原生 HTML 容器启动
35
+ - `textarea` 启动与自动回写
36
+ - ESM / UMD / IIFE 三种构建产物
37
+ - CDN 直接接入,无需 npm
38
+ - Source 按钮切换源码编辑
39
+ - 左侧源码、右侧高保真预览
40
+ - 完整 HTML 文档保留
41
+ - fragment HTML 预处理与回写
42
+ - Raw HTML Block 占位与保活
43
+ - 基础富文本能力:
44
+ - 撤销 / 重做
45
+ - 格式刷
46
+ - 清除格式
47
+ - 标题 / 正文
48
+ - 字体 / 字号
49
+ - 粗体 / 斜体 / 下划线 / 删除线
50
+ - 颜色
51
+ - 引用
52
+ - inline code
53
+ - code block
54
+ - 列表
55
+ - 链接
56
+ - 表格
57
+ - 图片
58
+ - Callout
59
+ - 全屏
60
+
61
+ ## 架构概览
62
+
63
+ JEditor 当前采用的是一套“Source HTML 为唯一真相”的混合架构:
64
+
65
+ ```text
66
+ Source HTML
67
+
68
+ Parser / Preprocess
69
+
70
+ Projection
71
+
72
+ Visual Editor (Tiptap)
73
+
74
+ restoreRawHTML()
75
+
76
+ Output HTML
77
+ ```
78
+
79
+ 可以把它理解为三层:
80
+
81
+ 1. Source Layer
82
+ - 保存原始 HTML
83
+ - 完整文档模式下保持源码权威
84
+ - 负责高保真预览
85
+
86
+ 2. Projection Layer
87
+ - 在 `setContent` 前预处理 HTML
88
+ - 把可识别节点交给 schema
89
+ - 把未知节点包成 `RawHtmlIsland`
90
+
91
+ 3. Visual Layer
92
+ - 由 Tiptap 承担结构化编辑能力
93
+ - 工具栏、插件、命令、选区逻辑都工作在这一层
94
+
95
+ ## 三种编辑状态
96
+
97
+ ### 1. Visual Mode
98
+
99
+ 默认模式。适合写正文、排版、插入表格、Callout、图片、代码块等结构化内容。
100
+
101
+ 这一层主要依赖 Tiptap 提供:
102
+
103
+ - 选区管理
104
+ - 命令链
105
+ - schema
106
+ - 历史记录
107
+ - 节点与 mark 扩展
108
+
109
+ ### 2. Source Mode
110
+
111
+ 点击右侧 `Source` 按钮进入。
112
+
113
+ 当前实现为:
114
+
115
+ - 左侧:源码编辑区 `textarea`
116
+ - 右侧:高保真 iframe 预览
117
+
118
+ 当内容是完整 HTML 文档时,JEditor 不会强制把它重新喂回可视化编辑器,从而避免:
119
+
120
+ - `<!DOCTYPE html>` 丢失
121
+ - `<head>` 丢失
122
+ - `<style>` 丢失
123
+ - `<script>` 丢失
124
+ - 文档结构被 Tiptap 规范化
125
+
126
+ ### 3. Hybrid / Preservation Flow
127
+
128
+ 这是 JEditor 目前最关键的一层。
129
+
130
+ 对于 fragment HTML,JEditor 会在 `setContent()` 之前先做预处理:
131
+
132
+ - 支持的节点:正常解析进入编辑器
133
+ - 不支持的节点:包成 `raw-html` 占位节点
134
+
135
+ 导出时再通过 `restoreRawHTML()` 还原成原始 HTML。
136
+
137
+ 这一步的目标很明确:
138
+
139
+ `未知 HTML 不一定能编辑,但不能被删除。`
140
+
141
+ ## 目录结构
142
+
143
+ 核心源码主要在 `src`:
144
+
145
+ - `src/jeditor.js`
146
+ - 编辑器总入口
147
+ - 负责模式切换、Source 控制、HTML 同步、初始化
148
+
149
+ - `src/editor/index.js`
150
+ - 创建 Tiptap Editor 实例
151
+
152
+ - `src/core/plugin-manager.js`
153
+ - 统一注册和管理插件
154
+
155
+ - `src/core/config.js`
156
+ - 默认配置与用户配置合并
157
+
158
+ - `src/core/html-preservation.js`
159
+ - `preprocessHTML`
160
+ - `restoreRawHTML`
161
+ - HTML 保活入口
162
+
163
+ - `src/toolbar/ui.js`
164
+ - 工具栏 DOM 生成、下拉菜单、状态同步
165
+
166
+ - `src/plugins`
167
+ - 所有工具栏能力与命令实现
168
+
169
+ - `src/extensions`
170
+ - 自定义 schema / mark / node 扩展
171
+
172
+ - `src/styles/editor.css`
173
+ - 编辑器内置样式
174
+
175
+ ## HTML 保活机制
176
+
177
+ 这一部分是 JEditor 当前最重要的架构能力。
178
+
179
+ ### 1. preprocessHTML
180
+
181
+ 在 `setContent()` 前执行。
182
+
183
+ 职责:
184
+
185
+ - 遍历输入 HTML
186
+ - 判断节点是否属于当前 schema 可接受范围
187
+ - 对未知节点生成占位节点
188
+
189
+ 例如:
190
+
191
+ ```html
192
+ <section style="display:flex">
193
+ <article>...</article>
194
+ </section>
195
+ ```
196
+
197
+ 如果当前 schema 不能完整表达这类结构,就不会直接丢弃,而是转换为:
198
+
199
+ ```html
200
+ <raw-html data-raw-html="...encoded html..."></raw-html>
201
+ ```
202
+
203
+ ### 2. RawHtmlIsland
204
+
205
+ `RawHtmlIsland` 是一个不可拆的 block node。
206
+
207
+ 它的职责不是编辑 HTML,而是“保留 HTML”。
208
+
209
+ 在视觉上,它会显示为一个明确的块,而不是空节点,例如:
210
+
211
+ - Raw HTML Block
212
+ - HTML preserved
213
+
214
+ 并支持:
215
+
216
+ - 双击跳转 Source
217
+ - 复制原始 HTML
218
+ - 删除该块
219
+
220
+ 对应实现:
221
+
222
+ - `src/extensions/raw-html-island.js`
223
+
224
+ ### 3. restoreRawHTML
225
+
226
+ 在 `getHTML()` 时执行。
227
+
228
+ 职责:
229
+
230
+ - 扫描编辑器输出
231
+ - 找到 `raw-html[data-raw-html]`
232
+ - 将其还原为保存的原始 `outerHTML`
233
+
234
+ 因此即便某些节点在可视化层无法编辑,导出时仍然可以完整回到原始 HTML。
235
+
236
+ ## Schema 扩展方向
237
+
238
+ 当前已经开始接入第一阶段的 schema 扩展:
239
+
240
+ - `GenericDiv`
241
+ - `GlobalStyle`
242
+ - `RawHtmlIsland`
243
+
244
+ 目标是逐步让 JEditor 支持更多“宽松但可保留”的 HTML 语法,而不是一开始就被严格 schema 过滤掉。
245
+
246
+ 后续会继续推进:
247
+
248
+ - `GenericSpan`
249
+ - 更细粒度的 inline fallback
250
+ - 更完整的未知标签保留策略
251
+
252
+ ## 插件体系
253
+
254
+ JEditor 使用插件驱动工具栏和命令。
255
+
256
+ 每个插件通常包含以下能力:
257
+
258
+ - `name`
259
+ - `toolbar`
260
+ - `tiptapExtension`
261
+ - `command`
262
+ - `isActive`
263
+ - `renderPopover`
264
+ - `init / destroy`
265
+
266
+ 这意味着:
267
+
268
+ - UI 与命令可以解耦
269
+ - 工具栏项可以按配置组合
270
+ - 扩展新按钮时无需改动编辑器核心
271
+
272
+ 内置插件入口:
273
+
274
+ - `src/plugins/index.js`
275
+
276
+ 插件管理器:
277
+
278
+ - `src/core/plugin-manager.js`
31
279
 
32
280
  ## 安装与开发
281
+
33
282
  ```bash
34
283
  npm install
35
284
  npm run dev
@@ -37,58 +286,57 @@ npm run build
37
286
  npm run preview
38
287
  ```
39
288
 
40
- 构建后会生成:
289
+ 构建产物:
290
+
41
291
  - `dist/jeditor.es.js`
42
292
  - `dist/jeditor.umd.js`
43
293
  - `dist/jeditor.iife.js`
44
294
  - `dist/jeditor.css`
45
295
 
46
296
  ## ESM 用法
297
+
47
298
  ```js
48
- import 'jeditor/dist/jeditor.css'
299
+ import '@jenkin-a/jeditor/dist/jeditor.css'
49
300
  import { JEditor } from '@jenkin-a/jeditor'
50
301
 
51
- const editor = JEditor.create('#j-editor-container', {
302
+ const editor = JEditor.create('#editor', {
52
303
  placeholder: '开始你的创作...',
53
304
  })
54
305
  ```
55
306
 
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
- ```
307
+ 也可以直接从 HTML 字符串创建:
68
308
 
69
309
  ```js
70
- JEditor.create('#editor')
310
+ const editor = JEditor.fromHTML('#editor', '<h2>Hello</h2><p>World</p>')
71
311
  ```
72
312
 
73
- ## CDN / 浏览器直连
313
+ ## CDN / 无 npm 用法
314
+
74
315
  ```html
75
- <link rel="stylesheet" href="https://unpkg.com/@jenkin-a/jeditor/dist/jeditor.css" />
316
+ <link rel="stylesheet" href="https://unpkg.com/@jenkin-a/jeditor/dist/jeditor.css">
76
317
  <script src="https://unpkg.com/feather-icons"></script>
77
318
  <script src="https://unpkg.com/@jenkin-a/jeditor/dist/jeditor.iife.js"></script>
78
319
 
79
320
  <div id="editor">
80
- <h2>Hello JEditor</h2>
81
- <p>This HTML is parsed on startup.</p>
321
+ <h2>你好,JEditor</h2>
322
+ <p>这里的原生 HTML 会在启动时被解析。</p>
82
323
  </div>
83
324
 
84
325
  <script>
85
- const editor = window.JEditor.create('#editor')
326
+ const editor = window.JEditor.create('#editor', {
327
+ placeholder: '开始你的创作...',
328
+ })
86
329
  </script>
87
330
  ```
88
331
 
89
- 本地也可以先执行 `npm run build`,然后打开 `Demo/cdn.html` 查看浏览器直连示例。
332
+ 如果需要锁定版本:
333
+
334
+ ```html
335
+ <script src="https://unpkg.com/@jenkin-a/jeditor@1.0.2/dist/jeditor.iife.js"></script>
336
+ ```
337
+
338
+ ## API
90
339
 
91
- ## 公开 API
92
340
  ```js
93
341
  editor.getHTML()
94
342
  editor.getJSON()
@@ -97,25 +345,46 @@ editor.setContent('<p>Hello</p>')
97
345
  editor.importHTML('<p>Hello</p>')
98
346
  editor.focus()
99
347
  editor.destroy()
348
+ editor.toggleSourceMode()
100
349
  ```
101
350
 
102
- ## 依赖说明
103
- ESM 版本需要你的项目安装以下 peer 依赖:
104
- ```bash
105
- npm install @tiptap/core @tiptap/starter-kit @tiptap/extension-underline @tiptap/extension-image @tiptap/pm
106
- ```
351
+ ## 适用场景
352
+
353
+ JEditor 适合以下场景:
354
+
355
+ - 企业知识库编辑器
356
+ - SOP / 公告 / 文档创作台
357
+ - 支持 Source HTML 的 CMS 编辑器
358
+ - 需要可视化 + HTML 混合编辑的后台系统
359
+ - 需要 CDN 直连接入的轻量编辑器
360
+
361
+ ## 当前边界
362
+
363
+ 虽然当前版本已经非常强,但它还在持续演进中。
364
+
365
+ 目前仍存在一些边界:
366
+
367
+ - 部分 UI 细节仍需继续打磨
368
+ - 个别工具栏交互仍可继续优化
369
+ - 复杂未知 inline HTML 的保活能力还不是最终形态
370
+ - Source Mode 与 Hybrid Mode 还会继续深化
371
+
372
+ ## 下一阶段方向
373
+
374
+ 下一步的重点已经明确:
375
+
376
+ 1. 扩展 schema
377
+ - `GenericSpan`
378
+ - 更宽松的 style / attr 保留
107
379
 
108
- CDN / IIFE 版本为自包含构建,不需要额外引入 Tiptap。
380
+ 2. 完善 Raw HTML 保活
381
+ - 更细粒度 fallback
382
+ - 更好的导入 / 导出还原
109
383
 
110
- ## 已知限制
111
- - 当前仍有部分工具栏按钮是占位能力,见 `src/plugins/placeholders.js`
112
- - 图片上传仍默认使用 base64,`uploadUrl` 尚未接入完整上传链路
113
- - Source HTML 编辑模式是后续版本的核心工作
384
+ 3. 强化 Source / Visual 协同
385
+ - 更稳定的双向切换
386
+ - 更完整的 Hybrid 编辑体验
114
387
 
115
- ## 路线图
116
- 1. v1.0.1:原生 HTML 初始化 + CDN / IIFE 自包含构建
117
- 2. v1.1.x:补齐基础编辑能力(标题、列表、对齐、颜色、字号、字体、链接、代码块等)
118
- 3. v1.2.x:Source HTML 模式(可视化 ↔ HTML 双向切换)
388
+ ## License
119
389
 
120
- ## 许可
121
390
  MIT
package/dist/jeditor.css CHANGED
@@ -1,2 +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}
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:visible}.je-toolbar-row{z-index:40;flex-shrink:0;align-items:center;gap:1px;padding:0 12px;display:flex;position:sticky}.je-toolbar-row--primary{background:#fff;height:44px;top:0}.je-toolbar-row--secondary{background:#f3f4f6;border-radius:6px;height:42px;margin:0 8px 4px;position:sticky;top:44px}.je-toolbar-row--secondary:after{content:"";pointer-events:none;opacity:0;background:#eef0f2;height:1px;transition:opacity .12s;position:absolute;bottom:-5px;left:-8px;right:-8px}.je-container.is-scrolled .je-toolbar-row--secondary:after{opacity:1}.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.is-muted,.tool-btn-text.is-muted,.je-color-group.is-muted{color:#9ca3af;opacity:1;pointer-events:none}.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-text--icon{gap:4px}.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{justify-content:center;align-items:center;line-height:1;display:flex}.je-color-chip{background:0 0;border-radius:6px;justify-content:center;align-items:center;width:20px;height:20px;padding:0;display:inline-flex}.je-color-char{color:currentColor;font-size:14px;font-weight:600}.tool-btn.is-active,.tool-btn-text.is-active,.je-color-group.is-active{background-color:var(--je-blue-active);color:var(--je-blue)}.tool-btn.is-disabled,.tool-btn-text.is-disabled,.je-color-group.is-disabled{opacity:.35;pointer-events:none;cursor:default}.je-container.is-source-mode .je-toolbar-row .is-disabled{opacity:.35}.tool-btn.is-muted:hover,.tool-btn-text.is-muted:hover{background:0 0}.is-overflow-hidden{display:none!important}.je-editor-area,.je-editor-visual{flex-direction:column;flex:1;display:flex}.je-document-preview{background:#fff;border:none;width:100%;min-height:700px;display:none}.je-document-preview.is-active{display:block}.je-source-pane{box-sizing:border-box;background:linear-gradient(#f8fafc 0%,#fff 100%);flex:1;grid-template-columns:minmax(320px,1fr) minmax(320px,1fr);gap:16px;min-height:700px;padding:20px 24px 24px;display:none}.je-source-pane.is-active{display:grid}.je-source-textarea{color:#0f172a;resize:none;box-sizing:border-box;white-space:pre;background:#fff;border:1px solid #e5e7eb;border-radius:14px;outline:none;width:100%;min-height:100%;padding:18px 20px;font:13px/1.7 JetBrains Mono,SFMono-Regular,Consolas,monospace}.je-source-textarea:focus{border-color:#bfdbfe;box-shadow:0 0 0 4px #bfdbfe59}.je-source-preview{box-sizing:border-box;background:#fff;border:1px solid #e5e7eb;border-radius:14px;width:100%;min-height:100%}.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 blockquote{color:#111827;background:#f3f4f6;border-left:3px solid #374151;margin:.8em 0;padding:8px 16px 8px 18px}.je-editor-area .tiptap ul{margin-bottom:.8em;padding-left:1.7em;list-style-type:disc}.je-editor-area .tiptap ul li::marker{color:#1c81d9}.je-editor-area .tiptap ol{counter-reset:je-ol;margin-bottom:.8em;padding-left:3.2em;list-style-type:none}.je-editor-area .tiptap ol>li{counter-increment:je-ol;position:relative}.je-editor-area .tiptap ol>li:before{content:counter(je-ol) ".";color:#1c81d9;white-space:nowrap;text-align:right;font-variant-numeric:tabular-nums;width:2.6em;position:absolute;left:-3.2em}.je-editor-area .tiptap ol ol{counter-reset:je-sub-ol;padding-left:3.8em}.je-editor-area .tiptap ol ol>li{counter-increment:je-sub-ol}.je-editor-area .tiptap ol ol>li:before{content:counter(je-ol) "." counter(je-sub-ol);width:3.2em;left:-3.8em}.je-editor-area .tiptap ol ol ol{counter-reset:je-sub-sub-ol;padding-left:4.8em}.je-editor-area .tiptap ol ol ol>li{counter-increment:je-sub-sub-ol}.je-editor-area .tiptap ol ol ol>li:before{content:counter(je-ol) "." counter(je-sub-ol) "." counter(je-sub-sub-ol);width:4.2em;left:-4.8em}.je-editor-area .tiptap li>p{margin-bottom:.35em}.je-editor-area .tiptap hr{background:#e5e7eb;border:none;height:1px;margin:1.1em 0}.je-editor-area .tiptap code{color:#c2410c;background:#f7f9fc;border:1px solid #e8edf3;border-radius:6px;padding:.14em .42em;font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:.92em;font-weight:700}.je-editor-area .tiptap a{color:#2563eb;text-underline-offset:2px;text-decoration:underline;text-decoration-thickness:1px}.je-editor-area .tiptap .tableWrapper{width:fit-content;max-width:100%;margin:1em 0;position:relative}.je-editor-area .tiptap table{border-collapse:collapse;table-layout:fixed;background:#fff;width:auto;margin:1em 0}.je-editor-area .tiptap th,.je-editor-area .tiptap td{vertical-align:top;border:1px solid #e5e7eb;width:8px;min-width:8px;height:4px;padding:4px 8px}.je-editor-area .tiptap th{color:#374151;background:#f8fafc;font-weight:600}.je-modal-overlay{z-index:1400;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);background:#0f172a2e;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.je-modal{background:#fff;border:1px solid #e8edf3;border-radius:14px;width:min(430px,100vw - 32px);padding:22px 24px 20px;box-shadow:0 24px 60px #0f172a29}.je-modal-title{color:#111827;margin-bottom:18px;font-size:18px;font-weight:700}.je-modal-field{grid-template-columns:44px 1fr;align-items:center;gap:10px;margin-bottom:14px;display:grid}.je-modal-label{color:#374151;font-size:14px}.je-modal-input{color:#111827;background:#f9fbfd;border:1px solid #dbe3ec;border-radius:8px;outline:none;height:36px;padding:0 12px;font-size:14px}.je-modal-input::placeholder{color:#9ca3af}.je-modal-input:focus{background:#fff;border-color:#9ec5fe;box-shadow:0 0 0 3px #93c5fd2e}.je-modal-actions{justify-content:flex-end;gap:10px;margin-top:24px;display:flex}.je-modal-btn{color:#374151;cursor:pointer;background:#fff;border:1px solid #e3e8ef;border-radius:8px;min-width:70px;height:34px;padding:0 14px;font-size:14px;transition:background-color .12s,border-color .12s,color .12s}.je-modal-btn:hover{background:#f8fafc;border-color:#d4dde8}.je-modal-btn.is-primary{color:#3b82f6;background:#ddebff;border-color:#cfe1f8}.je-modal-btn.is-primary:hover{background:#d3e6ff;border-color:#bfd6f6}::-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}.je-code-button{color:#1d4ed8;font-family:Courier New,monospace;font-weight:700}.je-popover{z-index:1000;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);background:#fffffff5;border:1px solid #e5e7eb;border-radius:14px;min-width:180px;padding:10px;position:absolute;box-shadow:0 10px 28px #0f172a1a}.je-popover-list{flex-direction:column;gap:4px;display:flex}.je-popover-list--scroll{max-height:260px;overflow-y:auto}.je-popover-list--font-family{max-height:312px}.je-popover--font-family{min-width:144px;max-width:180px}.je-popover--font-size{min-width:126px;max-width:150px}.je-popover-item{color:#374151;cursor:pointer;text-align:left;background:0 0;border:none;border-radius:10px;align-items:center;gap:8px;width:100%;padding:8px 10px;font-size:13px;display:flex}.je-popover-item svg{flex-shrink:0;width:16px;height:16px}.je-popover-item:hover{background:#f3f4f6}.je-popover-color{width:fit-content;min-width:unset;max-width:calc(100vw - 32px)}.je-color-section+.je-color-section{margin-top:10px}.je-color-section-title{color:#9ca3af;margin-bottom:8px;font-size:12px;font-weight:500}.je-color-grid{grid-template-columns:repeat(6,30px);justify-content:start;gap:6px;display:grid}.je-color-grid--recent{grid-template-columns:repeat(6,30px)}.je-color-swatch{cursor:pointer;color:#6b7280;background:#fff;border:1px solid #d1d5db;border-radius:8px;justify-content:center;align-items:center;width:30px;height:30px;transition:transform .12s,border-color .12s,box-shadow .12s;display:inline-flex}.je-color-swatch:hover{border-color:#93c5fd;transform:translateY(-1px);box-shadow:0 0 0 3px #3b82f624}.je-color-swatch.is-empty{opacity:.28;cursor:default}.je-color-swatch--filled{border-color:#d1d5db}.je-color-swatch--text{background:#fff;padding:0}.je-color-swatch--custom,.je-color-swatch--clear,.je-color-swatch--combo{background:#fff}.je-color-swatch--custom{position:relative;overflow:hidden}.je-style-preview{background:0 0;border-radius:5px;justify-content:center;align-items:center;width:18px;height:18px;font-size:15px;font-weight:700;display:inline-flex}.je-style-preview.is-empty{opacity:.45;box-shadow:inset 0 0 0 1px #d1d5dbe6}.je-style-preview.is-clear{color:#9ca3af}.je-color-custom-panel{background:#fafbfc;border:1px solid #eef2f7;border-radius:10px;flex-direction:column;gap:8px;margin-top:10px;padding:10px;display:flex}.je-color-custom-panel.is-hidden{display:none}.je-color-custom-title{color:#9ca3af;font-size:12px;font-weight:500}.je-color-picker{cursor:pointer;background:#fff;border:1px solid #edf2f7;border-radius:8px;width:100%;height:34px;padding:2px}.je-color-actions{gap:6px;display:flex}.je-action-btn{color:#667085;cursor:pointer;background:#fff;border:1px solid #e8edf3;border-radius:8px;flex:1;height:30px;padding:0 10px;font-size:12px;transition:background-color .12s,border-color .12s,color .12s}.je-action-btn:hover{background:#f8fafc;border-color:#dde5ee}.je-action-btn.is-primary{color:#4b5563;background:#eef3f8;border-color:#dbe5f0}.je-action-btn.is-primary:hover{background:#e4ebf3;border-color:#ced8e4}.je-callout-item{color:#374151;cursor:pointer;text-align:left;background:0 0;border:none;border-radius:10px;grid-template-columns:30px 1fr;align-items:center;gap:10px;width:100%;padding:8px 10px;display:grid}.je-callout-item:hover{background:#f3f4f6}.je-callout-badge{border-radius:8px;justify-content:center;align-items:center;width:30px;height:30px;font-size:14px;font-weight:700;display:inline-flex}.je-callout-content{flex-direction:column;gap:2px;display:flex}.je-callout-label{color:#111827;font-size:13px;font-weight:600}.je-callout-short{color:#9ca3af;font-size:12px}.je-editor-area .tiptap .je-callout-wrapper{box-sizing:border-box;width:auto;margin:12px 0;position:relative}.je-editor-area .tiptap .je-callout{background:var(--je-callout-bg,#faf8ff);width:auto;color:var(--je-callout-color,#9333ea);box-sizing:border-box;border:1px solid #0000;border-radius:14px;margin:0;padding:14px 16px;position:relative}.je-editor-area .tiptap .je-callout-header{-webkit-user-select:none;user-select:none;align-items:center;gap:10px;margin-bottom:10px;display:flex}.je-editor-area .tiptap .je-callout-drag-handle{width:24px;height:24px;color:inherit;cursor:grab;opacity:0;pointer-events:none;background:0 0;border:none;border-radius:8px;flex-shrink:0;justify-content:center;align-items:center;transition:opacity .18s,background-color .12s;display:inline-flex}.je-editor-area .tiptap .je-callout-wrapper:hover .je-callout-drag-handle,.je-editor-area .tiptap .je-callout-wrapper.is-handle-visible .je-callout-drag-handle,.je-editor-area .tiptap .je-callout-wrapper.ProseMirror-selectednode .je-callout-drag-handle,.je-editor-area .tiptap .je-callout-drag-handle:focus-visible{opacity:1;pointer-events:auto}.je-editor-area .tiptap .je-callout-drag-handle:hover,.je-editor-area .tiptap .je-callout-drag-handle:focus-visible{background:#ffffff80;outline:none}.je-editor-area .tiptap .je-callout-drag-handle svg{width:14px;height:14px}.je-editor-area .tiptap .je-callout-wrapper.ProseMirror-selectednode .je-callout{border-color:var(--je-callout-color,#9333ea);box-shadow:0 0 0 2px color-mix(in srgb, var(--je-callout-color,#9333ea) 12%, transparent)}.je-editor-area .tiptap .je-callout-title-btn{color:inherit;cursor:pointer;background:0 0;border:none;align-items:center;gap:8px;padding:0;font-size:14px;font-weight:700;display:inline-flex}.je-editor-area .tiptap .je-callout-title-btn svg,.je-editor-area .tiptap .je-callout-icon svg{width:18px;height:18px}.je-editor-area .tiptap .je-callout-body>:last-child{margin-bottom:0}.je-editor-area .tiptap .je-callout-body{color:inherit;min-height:24px}.je-callout-type-popover{z-index:1001;min-width:220px;position:absolute}.je-insert-popover{min-width:160px;padding:8px}.je-insert-item{color:#374151;cursor:pointer;text-align:left;background:0 0;border:none;border-radius:10px;justify-content:space-between;align-items:center;width:100%;min-height:34px;padding:0 10px;display:flex}.je-insert-item:hover{background:#f3f6fa}.je-insert-item-main{align-items:center;gap:8px;display:inline-flex}.je-insert-item-icon,.je-insert-item-arrow{color:#6b7280;justify-content:center;align-items:center;display:inline-flex}.je-insert-item-icon svg{width:16px;height:16px}.je-insert-table-panel{flex-direction:column;align-items:center;width:fit-content;min-width:216px;padding:10px;display:flex}.je-insert-table-title{color:#6b7280;width:196px;margin-bottom:10px;font-size:13px}.je-insert-table-grid{grid-template-columns:repeat(10,16px);gap:4px;width:196px;display:grid}.je-insert-table-cell{cursor:pointer;background:#f8fafc;border:1px solid #dbe3ec;width:16px;height:16px}.je-insert-table-cell.is-active{background:#dbeafe;border-color:#93c5fd}.je-insert-table-info{color:#6b7280;width:196px;margin-top:10px;font-size:13px}.je-insert-table-custom{border-top:1px solid #eef2f7;grid-template-columns:minmax(0,1fr) minmax(0,1fr) 48px;gap:6px;width:196px;margin-top:12px;padding-top:10px;display:grid}.je-insert-table-custom-field{color:#6b7280;flex-direction:column;gap:4px;font-size:12px;display:flex}.je-insert-table-number{box-sizing:border-box;background:#fff;border:1px solid #dbe3ec;border-radius:8px;outline:none;width:100%;height:30px;padding:0 8px}.je-insert-table-confirm{color:#4b5563;cursor:pointer;box-sizing:border-box;background:#eef3f8;border:1px solid #dbe5f0;border-radius:8px;align-self:end;width:100%;height:30px;padding:0}.je-table-wrap{width:fit-content;max-width:100%;position:relative}.je-table-move-handle,.je-table-resize-handle{z-index:3;opacity:0;transition:opacity .12s,background-color .12s;position:absolute}.je-table-wrap:hover .je-table-move-handle,.je-table-wrap:hover .je-table-resize-handle,.je-table-wrap.ProseMirror-selectednode .je-table-move-handle,.je-table-wrap.ProseMirror-selectednode .je-table-resize-handle{opacity:1}.je-table-move-handle{color:#6b7280;cursor:grab;background:#fff;border:1px solid #dbe3ec;border-radius:6px;justify-content:center;align-items:center;width:20px;height:20px;display:inline-flex;top:-10px;left:-10px}.je-table-move-handle svg{width:12px;height:12px}.je-table-resize-handle{cursor:nwse-resize;background:#cbd5e1;border-radius:4px;width:14px;height:14px;bottom:-8px;right:-8px}.je-code-block-wrap{background:#fff;border:1px solid #d0d7de;border-radius:12px;width:100%;margin:1em 0;position:relative;overflow:hidden}.je-code-block-header{background:#f6f8fa;align-items:center;gap:8px;min-height:34px;padding:0 12px;display:flex}.je-code-block-select{color:#6b7280;appearance:none;background:0 0;border:none;border-radius:0;outline:none;height:24px;padding:0 18px 0 0;font-size:12px}.je-code-block-select option{color:#111827}.je-code-block-header:after{content:"";pointer-events:none;border-bottom:1.5px solid #9ca3af;border-right:1.5px solid #9ca3af;width:10px;height:10px;margin-left:-12px;transform:rotate(45deg)scale(.6)}.je-code-block-spacer{flex:1}.je-code-block-copy{color:#6b7280;opacity:0;pointer-events:none;cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;width:20px;height:20px;padding:0;transition:opacity .12s,background-color .12s,color .12s;display:inline-flex}.je-code-block-copy:hover{color:#374151;background:#eaeef2}.je-code-block-copy.is-copied{color:#1d4ed8}.je-code-block-copy svg{width:14px;height:14px}.je-code-block-wrap:hover .je-code-block-copy,.je-code-block-wrap:focus-within .je-code-block-copy{opacity:1;pointer-events:auto}.je-code-block-wrap pre{background:0 0;margin:0;padding:12px 16px 16px;overflow-x:auto}.je-code-block-wrap pre code{color:#24292e;box-shadow:none;background:0 0;border:none;padding:0;font-size:13px;font-weight:400;display:block}.je-code-block-wrap pre code.je-code-block{box-shadow:none;background:0 0;border:none}.je-code-block-wrap .hljs-comment,.je-code-block-wrap .hljs-quote{color:#6a737d;font-style:italic}.je-code-block-wrap .hljs-keyword,.je-code-block-wrap .hljs-doctag,.je-code-block-wrap .hljs-selector-tag,.je-code-block-wrap .hljs-literal,.je-code-block-wrap .hljs-type{color:#d73a49}.je-code-block-wrap .hljs-string,.je-code-block-wrap .hljs-attr,.je-code-block-wrap .hljs-template-tag{color:#032f62}.je-code-block-wrap .hljs-number,.je-code-block-wrap .hljs-built_in,.je-code-block-wrap .hljs-title.class_,.je-code-block-wrap .hljs-symbol{color:#005cc5}.je-code-block-wrap .hljs-function,.je-code-block-wrap .hljs-title.function_{color:#6f42c1}.je-link-popover{z-index:1100;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#fffffffa;border:1px solid #e5e7eb;border-radius:10px;grid-template-columns:1fr auto;align-items:center;width:268px;min-height:36px;padding:0 8px;display:grid;position:absolute;box-shadow:0 10px 28px #0f172a1f}.je-link-popover__main{color:#2563eb;white-space:nowrap;text-overflow:ellipsis;align-items:center;font-size:13px;text-decoration:none;display:inline-flex;overflow:hidden}.je-link-popover__actions{align-items:center;gap:4px;margin-left:8px;display:inline-flex}.je-link-popover__action{color:#6b7280;cursor:pointer;background:#fff;border:1px solid #e5e7eb;border-radius:8px;width:28px;height:28px}.je-link-popover__action:hover{color:#374151;background:#f8fafc}.raw-html-island{background:linear-gradient(#f8fafc 0%,#fff 100%);border:1px solid #e5e7eb;border-radius:14px;margin:12px 0;overflow:hidden}.raw-html-island__header{color:#475569;letter-spacing:.02em;text-transform:uppercase;border-bottom:1px solid #eef2f7;justify-content:space-between;align-items:center;gap:12px;padding:10px 14px;font-size:12px;font-weight:700;display:flex}.raw-html-island__actions{align-items:center;gap:6px;display:inline-flex}.raw-html-island__action{color:#64748b;cursor:pointer;background:#fff;border:1px solid #e6ebf1;border-radius:8px;height:28px;padding:0 10px;font-size:12px;transition:background-color .12s,border-color .12s,color .12s}.raw-html-island__action:hover{color:#475569;background:#f8fafc;border-color:#d6dee8}.raw-html-island__action.is-danger{color:#b91c1c}.raw-html-island__body{color:#0f172a;white-space:pre-wrap;word-break:break-word;margin:0;padding:14px;font:12px/1.7 JetBrains Mono,SFMono-Regular,Consolas,monospace}.raw-html-island.is-editing .raw-html-island__body{display:none}.raw-html-island__editor{background:#fff;border-top:1px solid #eef2f7;padding:14px}.raw-html-island__editor.is-hidden{display:none}.raw-html-island__textarea{color:#0f172a;resize:vertical;box-sizing:border-box;white-space:pre;background:#f8fafc;border:1px solid #e5e7eb;border-radius:12px;outline:none;width:100%;min-height:220px;padding:12px 14px;font:12px/1.7 JetBrains Mono,SFMono-Regular,Consolas,monospace}.raw-html-island__textarea:focus{background:#fff;border-color:#bfdbfe;box-shadow:0 0 0 4px #bfdbfe47}.raw-html-island__editor-actions{justify-content:flex-end;gap:8px;margin-top:10px;display:flex}.raw-html-island__action.is-primary{color:#4b5563;background:#eef3f8;border-color:#dbe5f0}.raw-html-island__action.is-primary:hover{background:#e4ebf3;border-color:#ced8e4}@media (width<=980px){.je-source-pane.is-active{grid-template-columns:1fr;min-height:auto}.je-source-textarea,.je-source-preview{min-height:360px}}pre code.hljs{padding:1em;display:block;overflow-x:auto}code.hljs{padding:3px 5px}.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#005cc5}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-comment,.hljs-code,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}
2
2
  /*$vite$:1*/