christy-richtext 1.0.0 → 1.1.2
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/LICENSE +33 -18
- package/README.md +128 -17
- package/dist-lib/christy-richtext.css +133 -3
- package/dist-lib/christy-richtext.js +4265 -3637
- package/dist-lib/christy-richtext.umd.cjs +103 -103
- package/package.json +3 -2
package/LICENSE
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
Christy Richtext Proprietary License
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2026 Christy Love (彩虹小馬工作室)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
This software and associated documentation files (the "Software") are proprietary
|
|
8
|
+
and are owned by Christy Love (彩虹小馬工作室).
|
|
9
|
+
|
|
10
|
+
Permission is granted only to individuals or organizations who have obtained a
|
|
11
|
+
valid license from Christy Love (彩虹小馬工作室) to install and use the Software
|
|
12
|
+
according to the terms of their purchased license.
|
|
13
|
+
|
|
14
|
+
Unless expressly permitted by a valid written license agreement, you may not:
|
|
15
|
+
|
|
16
|
+
1. copy, redistribute, sublicense, sell, rent, lease, or transfer the Software;
|
|
17
|
+
2. publish, share, or make the Software available to any third party;
|
|
18
|
+
3. modify, reverse engineer, decompile, or disassemble the Software;
|
|
19
|
+
4. remove or alter any copyright, trademark, license, or proprietary notice;
|
|
20
|
+
5. use the Software in any product, service, or project without a valid license.
|
|
21
|
+
|
|
22
|
+
A valid license grants the license holder the right to use the Software only
|
|
23
|
+
within the scope defined by the purchased plan, agreement, invoice, or written
|
|
24
|
+
permission provided by Christy Love (彩虹小馬工作室).
|
|
25
|
+
|
|
26
|
+
The Software is provided "AS IS", without warranty of any kind, express or
|
|
27
|
+
implied, including but not limited to the warranties of merchantability, fitness
|
|
28
|
+
for a particular purpose, and non-infringement.
|
|
29
|
+
|
|
30
|
+
In no event shall the authors or copyright holders be liable for any claim,
|
|
31
|
+
damages, or other liability, whether in an action of contract, tort, or
|
|
32
|
+
otherwise, arising from, out of, or in connection with the Software or the use
|
|
33
|
+
or other dealings in the Software.
|
|
34
|
+
|
|
35
|
+
Unauthorized use, copying, distribution, or modification of this Software is
|
|
36
|
+
strictly prohibited and may result in legal action.
|
package/README.md
CHANGED
|
@@ -1,42 +1,153 @@
|
|
|
1
1
|
# christy-richtext
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`christy-richtext` 是一個基於 Vue 3 的富文字編輯器元件,適合用在後台管理系統、文章編輯、公告內容、商品描述等需要輸入格式化文字的場景。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
* 支援 Vue 3
|
|
8
|
+
* 支援 TypeScript
|
|
9
|
+
* 支援 `v-model`
|
|
10
|
+
* 可作為表單輸入元件使用
|
|
11
|
+
* 適合 Vite / Vue 專案使用
|
|
8
12
|
|
|
9
|
-
##
|
|
13
|
+
## Installation
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
|
13
|
-
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
|
14
|
-
- Firefox:
|
|
15
|
-
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
|
16
|
-
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
|
15
|
+
使用 npm 安裝:
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
```sh
|
|
18
|
+
npm install christy-richtext
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Basic Usage
|
|
22
|
+
|
|
23
|
+
```vue
|
|
24
|
+
<script setup lang="ts">
|
|
25
|
+
import { ref } from 'vue'
|
|
26
|
+
import ChristyRichtext from 'christy-richtext'
|
|
27
|
+
|
|
28
|
+
// 控制富文字編輯器的 HTML 內容
|
|
29
|
+
const content = ref('<p>Hello christy-richtext</p>')
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<!-- 富文字編輯器,使用 v-model 綁定內容 -->
|
|
34
|
+
<ChristyRichtext v-model="content" />
|
|
35
|
+
|
|
36
|
+
<!-- 預覽目前輸出的 HTML 內容 -->
|
|
37
|
+
<div v-html="content"></div>
|
|
38
|
+
</template>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Props
|
|
42
|
+
|
|
43
|
+
| Prop | Type | Default | Description |
|
|
44
|
+
| ------------ | -------- | ------- | -------------- |
|
|
45
|
+
| `modelValue` | `string` | `''` | 編輯器目前的 HTML 內容 |
|
|
46
|
+
|
|
47
|
+
## Emits
|
|
48
|
+
|
|
49
|
+
| Event | Payload | Description |
|
|
50
|
+
| ------------------- | -------- | ----------- |
|
|
51
|
+
| `update:modelValue` | `string` | 當編輯器內容變更時觸發 |
|
|
52
|
+
|
|
53
|
+
## Example
|
|
54
|
+
|
|
55
|
+
```vue
|
|
56
|
+
<script setup lang="ts">
|
|
57
|
+
import { ref } from 'vue'
|
|
58
|
+
import ChristyRichtext from 'christy-richtext'
|
|
59
|
+
|
|
60
|
+
// 控制公告內容
|
|
61
|
+
const announcementContent = ref('')
|
|
62
|
+
|
|
63
|
+
// 模擬送出公告內容
|
|
64
|
+
const submit = () => {
|
|
65
|
+
console.log('公告內容:', announcementContent.value)
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<template>
|
|
70
|
+
<section>
|
|
71
|
+
<h2>新增公告</h2>
|
|
72
|
+
|
|
73
|
+
<!-- 公告內容輸入區 -->
|
|
74
|
+
<ChristyRichtext v-model="announcementContent" />
|
|
19
75
|
|
|
20
|
-
|
|
76
|
+
<!-- 送出按鈕 -->
|
|
77
|
+
<button type="button" @click="submit">
|
|
78
|
+
送出
|
|
79
|
+
</button>
|
|
80
|
+
</section>
|
|
81
|
+
</template>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Usage in Nuxt
|
|
85
|
+
|
|
86
|
+
如果你要在 Nuxt 專案中使用,建議使用 `<ClientOnly>` 包起來,避免富文字編輯器在伺服器端渲染時遇到瀏覽器 API 錯誤。
|
|
21
87
|
|
|
22
|
-
|
|
88
|
+
```vue
|
|
89
|
+
<script setup lang="ts">
|
|
90
|
+
import { ref } from 'vue'
|
|
91
|
+
import ChristyRichtext from 'christy-richtext'
|
|
23
92
|
|
|
24
|
-
|
|
93
|
+
// 控制富文字內容
|
|
94
|
+
const content = ref('')
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<template>
|
|
98
|
+
<ClientOnly>
|
|
99
|
+
<ChristyRichtext v-model="content" />
|
|
100
|
+
</ClientOnly>
|
|
101
|
+
</template>
|
|
102
|
+
```
|
|
25
103
|
|
|
26
|
-
##
|
|
104
|
+
## Development
|
|
105
|
+
|
|
106
|
+
安裝專案依賴:
|
|
27
107
|
|
|
28
108
|
```sh
|
|
29
109
|
npm install
|
|
30
110
|
```
|
|
31
111
|
|
|
32
|
-
|
|
112
|
+
啟動開發環境:
|
|
33
113
|
|
|
34
114
|
```sh
|
|
35
115
|
npm run dev
|
|
36
116
|
```
|
|
37
117
|
|
|
38
|
-
|
|
118
|
+
建置專案:
|
|
39
119
|
|
|
40
120
|
```sh
|
|
41
121
|
npm run build
|
|
42
122
|
```
|
|
123
|
+
|
|
124
|
+
執行型別檢查:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
npm run type-check
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Project Structure
|
|
131
|
+
|
|
132
|
+
```txt
|
|
133
|
+
christy-richtext/
|
|
134
|
+
├─ src/
|
|
135
|
+
│ ├─ components/
|
|
136
|
+
│ │ └─ ChristyRichtext.vue
|
|
137
|
+
│ ├─ index.ts
|
|
138
|
+
│ └─ style.css
|
|
139
|
+
├─ package.json
|
|
140
|
+
├─ README.md
|
|
141
|
+
├─ tsconfig.json
|
|
142
|
+
└─ vite.config.ts
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Notes
|
|
146
|
+
|
|
147
|
+
如果你在頁面上使用 `v-html` 顯示富文字內容,請注意 HTML 內容來源是否可信。
|
|
148
|
+
|
|
149
|
+
若內容來自使用者輸入,建議在後端或前端進行 HTML 清理,避免 XSS 攻擊。
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT
|
|
@@ -1,4 +1,59 @@
|
|
|
1
1
|
|
|
2
|
+
.crt-img-nv[data-v-2f71d5d7] {
|
|
3
|
+
/* 定位由 outerStyle 動態注入 */
|
|
4
|
+
}
|
|
5
|
+
.crt-img-container[data-v-2f71d5d7] {
|
|
6
|
+
user-select: none;
|
|
7
|
+
}
|
|
8
|
+
.crt-img-el[data-v-2f71d5d7] {
|
|
9
|
+
display: block;
|
|
10
|
+
width: 100%;
|
|
11
|
+
height: auto;
|
|
12
|
+
max-width: 100%;
|
|
13
|
+
transition: opacity 0.15s;
|
|
14
|
+
}
|
|
15
|
+
.crt-img-el--selected[data-v-2f71d5d7] {
|
|
16
|
+
outline: 2px solid #2563eb;
|
|
17
|
+
outline-offset: 1px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* 移動 icon(自由浮動模式) */
|
|
21
|
+
.crt-move-handle[data-v-2f71d5d7] {
|
|
22
|
+
position: absolute;
|
|
23
|
+
top: 4px;
|
|
24
|
+
left: 50%;
|
|
25
|
+
transform: translateX(-50%);
|
|
26
|
+
background: #2563ebcc;
|
|
27
|
+
color: #fff;
|
|
28
|
+
font-size: 14px;
|
|
29
|
+
border-radius: 4px;
|
|
30
|
+
padding: 0 6px 1px;
|
|
31
|
+
cursor: move;
|
|
32
|
+
user-select: none;
|
|
33
|
+
z-index: 20;
|
|
34
|
+
line-height: 1.6;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Resize handles */
|
|
38
|
+
.crt-rh[data-v-2f71d5d7] {
|
|
39
|
+
position: absolute;
|
|
40
|
+
width: 10px;
|
|
41
|
+
height: 10px;
|
|
42
|
+
background: #2563eb;
|
|
43
|
+
border: 2px solid #fff;
|
|
44
|
+
border-radius: 2px;
|
|
45
|
+
z-index: 10;
|
|
46
|
+
box-shadow: 0 1px 3px rgba(0,0,0,.25);
|
|
47
|
+
}
|
|
48
|
+
.crt-rh--nw[data-v-2f71d5d7] { top: -5px; left: -5px; cursor: nw-resize;
|
|
49
|
+
}
|
|
50
|
+
.crt-rh--ne[data-v-2f71d5d7] { top: -5px; right: -5px; cursor: ne-resize;
|
|
51
|
+
}
|
|
52
|
+
.crt-rh--sw[data-v-2f71d5d7] { bottom: -5px; left: -5px; cursor: sw-resize;
|
|
53
|
+
}
|
|
54
|
+
.crt-rh--se[data-v-2f71d5d7] { bottom: -5px; right: -5px; cursor: se-resize;
|
|
55
|
+
}
|
|
56
|
+
|
|
2
57
|
/* === Wrapper === */
|
|
3
58
|
.crt-editor-wrapper {
|
|
4
59
|
display: flex;
|
|
@@ -44,6 +99,37 @@
|
|
|
44
99
|
.crt-input-width:focus {
|
|
45
100
|
border-color: #2563eb;
|
|
46
101
|
}
|
|
102
|
+
|
|
103
|
+
/* 透明度滑桿 */
|
|
104
|
+
.crt-input-opacity {
|
|
105
|
+
width: 80px;
|
|
106
|
+
height: 4px;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
accent-color: #2563eb;
|
|
109
|
+
}
|
|
110
|
+
.crt-opacity-val {
|
|
111
|
+
font-size: 11px;
|
|
112
|
+
color: #6b7280;
|
|
113
|
+
min-width: 28px;
|
|
114
|
+
text-align: right;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* 自由浮動按鈕 */
|
|
118
|
+
.crt-btn--free {
|
|
119
|
+
background: #fdf4ff;
|
|
120
|
+
color: #7e22ce;
|
|
121
|
+
border: 1px solid #e9d5ff;
|
|
122
|
+
font-size: 12px;
|
|
123
|
+
padding: 0 8px;
|
|
124
|
+
}
|
|
125
|
+
.crt-btn--free:hover:not(:disabled) {
|
|
126
|
+
background: #f3e8ff;
|
|
127
|
+
}
|
|
128
|
+
.crt-btn--free.crt-btn--active {
|
|
129
|
+
background: #7e22ce;
|
|
130
|
+
color: #fff;
|
|
131
|
+
border-color: #7e22ce;
|
|
132
|
+
}
|
|
47
133
|
.crt-btn--word {
|
|
48
134
|
background: #f0fdf4;
|
|
49
135
|
color: #15803d;
|
|
@@ -239,6 +325,7 @@
|
|
|
239
325
|
min-height: 200px;
|
|
240
326
|
outline: none;
|
|
241
327
|
overflow-y: auto;
|
|
328
|
+
position: relative;
|
|
242
329
|
}
|
|
243
330
|
.crt-editor-content .tiptap {
|
|
244
331
|
outline: none;
|
|
@@ -246,6 +333,7 @@
|
|
|
246
333
|
font-size: 15px;
|
|
247
334
|
line-height: 1.7;
|
|
248
335
|
color: #111827;
|
|
336
|
+
position: relative;
|
|
249
337
|
}
|
|
250
338
|
|
|
251
339
|
/* 文繞圖 clearfix */
|
|
@@ -255,7 +343,7 @@
|
|
|
255
343
|
clear: both;
|
|
256
344
|
}
|
|
257
345
|
|
|
258
|
-
/* 選取圖片 highlight */
|
|
346
|
+
/* 選取圖片 highlight(NodeView 接管後已由 selected prop 控制,保留備用) */
|
|
259
347
|
.crt-editor-content .tiptap img.ProseMirror-selectednode {
|
|
260
348
|
outline: 2px solid #2563eb;
|
|
261
349
|
outline-offset: 2px;
|
|
@@ -383,6 +471,7 @@
|
|
|
383
471
|
min-height: 200px;
|
|
384
472
|
outline: none;
|
|
385
473
|
overflow-y: auto;
|
|
474
|
+
position: relative;
|
|
386
475
|
}
|
|
387
476
|
.crt-editor-content .tiptap {
|
|
388
477
|
outline: none;
|
|
@@ -390,6 +479,7 @@
|
|
|
390
479
|
font-size: 15px;
|
|
391
480
|
line-height: 1.7;
|
|
392
481
|
color: #111827;
|
|
482
|
+
position: relative;
|
|
393
483
|
}
|
|
394
484
|
|
|
395
485
|
/* 文繞圖 clearfix */
|
|
@@ -399,7 +489,7 @@
|
|
|
399
489
|
clear: both;
|
|
400
490
|
}
|
|
401
491
|
|
|
402
|
-
/* 選取圖片 highlight */
|
|
492
|
+
/* 選取圖片 highlight(NodeView 接管後已由 selected prop 控制,保留備用) */
|
|
403
493
|
.crt-editor-content .tiptap img.ProseMirror-selectednode {
|
|
404
494
|
outline: 2px solid #2563eb;
|
|
405
495
|
outline-offset: 2px;
|
|
@@ -491,6 +581,7 @@
|
|
|
491
581
|
min-height: 200px;
|
|
492
582
|
outline: none;
|
|
493
583
|
overflow-y: auto;
|
|
584
|
+
position: relative;
|
|
494
585
|
}
|
|
495
586
|
.crt-editor-content .tiptap {
|
|
496
587
|
outline: none;
|
|
@@ -498,6 +589,7 @@
|
|
|
498
589
|
font-size: 15px;
|
|
499
590
|
line-height: 1.7;
|
|
500
591
|
color: #111827;
|
|
592
|
+
position: relative;
|
|
501
593
|
}
|
|
502
594
|
|
|
503
595
|
/* 文繞圖 clearfix */
|
|
@@ -507,7 +599,7 @@
|
|
|
507
599
|
clear: both;
|
|
508
600
|
}
|
|
509
601
|
|
|
510
|
-
/* 選取圖片 highlight */
|
|
602
|
+
/* 選取圖片 highlight(NodeView 接管後已由 selected prop 控制,保留備用) */
|
|
511
603
|
.crt-editor-content .tiptap img.ProseMirror-selectednode {
|
|
512
604
|
outline: 2px solid #2563eb;
|
|
513
605
|
outline-offset: 2px;
|
|
@@ -597,6 +689,7 @@
|
|
|
597
689
|
border: 1px solid #d1d5db;
|
|
598
690
|
padding: 8px 12px;
|
|
599
691
|
min-width: 60px;
|
|
692
|
+
position: relative;
|
|
600
693
|
}
|
|
601
694
|
.crt-editor-content .tiptap table th {
|
|
602
695
|
background: #f3f4f6;
|
|
@@ -813,6 +906,43 @@
|
|
|
813
906
|
background: #ccfbf1;
|
|
814
907
|
color: #0f766e;
|
|
815
908
|
}
|
|
909
|
+
|
|
910
|
+
/* 儲存格背景色預覽 */
|
|
911
|
+
.crt-cell-bg-preview {
|
|
912
|
+
display: inline-block;
|
|
913
|
+
width: 14px;
|
|
914
|
+
height: 14px;
|
|
915
|
+
border-radius: 2px;
|
|
916
|
+
border: 1px solid rgba(0,0,0,.2);
|
|
917
|
+
vertical-align: middle;
|
|
918
|
+
margin-right: 2px;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/* 表格樣式快速套用 */
|
|
922
|
+
.crt-tbl-preset {
|
|
923
|
+
font-size: 11px;
|
|
924
|
+
font-weight: 700;
|
|
925
|
+
min-width: 24px;
|
|
926
|
+
height: 24px;
|
|
927
|
+
border-radius: 4px;
|
|
928
|
+
color: #fff;
|
|
929
|
+
border: none;
|
|
930
|
+
}
|
|
931
|
+
.crt-tbl-preset--gold { background: #f5c51e; color: #1a1a1a;
|
|
932
|
+
}
|
|
933
|
+
.crt-tbl-preset--blue { background: #3b82f6;
|
|
934
|
+
}
|
|
935
|
+
.crt-tbl-preset--green { background: #22c55e;
|
|
936
|
+
}
|
|
937
|
+
.crt-tbl-preset--gray { background: #6b7280;
|
|
938
|
+
}
|
|
939
|
+
.crt-btn--sm {
|
|
940
|
+
font-size: 11px;
|
|
941
|
+
min-width: 22px;
|
|
942
|
+
height: 22px;
|
|
943
|
+
padding: 0 4px;
|
|
944
|
+
color: #9ca3af;
|
|
945
|
+
}
|
|
816
946
|
.crt-btn--img {
|
|
817
947
|
background: #ede9fe;
|
|
818
948
|
color: #7c3aed;
|