@cloudea/yang-editor 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.
- package/LICENSE +21 -0
- package/README.md +12 -0
- package/dist/common.d.ts +15 -0
- package/dist/common.d.ts.map +1 -0
- package/dist/common.js +10 -0
- package/dist/common.js.map +1 -0
- package/dist/componets.d.ts +103 -0
- package/dist/componets.d.ts.map +1 -0
- package/dist/componets.js +684 -0
- package/dist/componets.js.map +1 -0
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +9 -0
- package/dist/context.js.map +1 -0
- package/dist/event.d.ts +13 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/event.js +42 -0
- package/dist/event.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +38 -0
- package/dist/utils.js.map +1 -0
- package/package.json +38 -0
- package/src/common.ts +24 -0
- package/src/componets.ts +795 -0
- package/src/context.ts +4 -0
- package/src/event.ts +52 -0
- package/src/index.ts +85 -0
- package/src/style.css +247 -0
- package/src/utils.ts +38 -0
- package/tsconfig.json +47 -0
package/src/context.ts
ADDED
package/src/event.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
|
|
2
|
+
export interface Event<T> {
|
|
3
|
+
name: string
|
|
4
|
+
payload : T
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type EventListener<T> = (event: Event<T>) => void;
|
|
8
|
+
|
|
9
|
+
export default class EventCenter {
|
|
10
|
+
|
|
11
|
+
private listeners : Map<string, Array<EventListener<any>>>;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.listeners = new Map<string, Array<EventListener<any>>>();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
on<T>(event: string, callback: EventListener<T>) {
|
|
18
|
+
if (!this.listeners.has(event)) {
|
|
19
|
+
this.listeners.set(event, new Array<EventListener<T>>());
|
|
20
|
+
}
|
|
21
|
+
let callbacks = this.listeners.get(event);
|
|
22
|
+
let idx = callbacks?.indexOf(callback);
|
|
23
|
+
if (idx === -1) {
|
|
24
|
+
this.listeners.get(event)?.push(callback)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
emit<T>(event: string, data : T) {
|
|
29
|
+
if (this.listeners.has(event)){
|
|
30
|
+
let callbacks = this.listeners.get(event);
|
|
31
|
+
if (callbacks !== undefined) {
|
|
32
|
+
for(let callback of callbacks){
|
|
33
|
+
callback({
|
|
34
|
+
name: event,
|
|
35
|
+
payload: data
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
off<T>(event: string, callback: EventListener<T>) {
|
|
43
|
+
if (this.listeners.has(event) ) {
|
|
44
|
+
let callbacks = this.listeners.get(event);
|
|
45
|
+
let idx = callbacks?.indexOf(callback);
|
|
46
|
+
if (idx !== undefined && idx >= 0){
|
|
47
|
+
callbacks?.splice(idx, 1)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {ComponentFactory, EditorBody, EditorComponent, EditorContent, EditorParagraph, EditorParagraphMenu, EditorToolbar} from "./componets";
|
|
2
|
+
import { SelectionUtils } from "./utils";
|
|
3
|
+
|
|
4
|
+
export type YangEditorMode = "edit" | "readonly";
|
|
5
|
+
|
|
6
|
+
export interface YangEditorOptions {
|
|
7
|
+
elem: string;
|
|
8
|
+
width: string;
|
|
9
|
+
height: string;
|
|
10
|
+
mode: YangEditorMode;
|
|
11
|
+
images: {
|
|
12
|
+
add: string,
|
|
13
|
+
menu: string,
|
|
14
|
+
bold: string,
|
|
15
|
+
italic: string,
|
|
16
|
+
underline: string,
|
|
17
|
+
deleteline: string,
|
|
18
|
+
link: string,
|
|
19
|
+
clear: string,
|
|
20
|
+
down: string,
|
|
21
|
+
delete: string,
|
|
22
|
+
copy: string,
|
|
23
|
+
cut: string,
|
|
24
|
+
fgColor: string
|
|
25
|
+
},
|
|
26
|
+
events: {
|
|
27
|
+
onContentChange?: (html: string) => void,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class YangEditor {
|
|
32
|
+
|
|
33
|
+
public readonly element: HTMLElement;
|
|
34
|
+
public readonly toolbar: EditorToolbar;
|
|
35
|
+
public readonly body: EditorBody;
|
|
36
|
+
public readonly selectionUtils: SelectionUtils;
|
|
37
|
+
public readonly componentFactory: ComponentFactory;
|
|
38
|
+
|
|
39
|
+
private components: Array<EditorComponent> = new Array<EditorComponent>();
|
|
40
|
+
|
|
41
|
+
constructor(public options: YangEditorOptions) {
|
|
42
|
+
let element = document.getElementById(options.elem);
|
|
43
|
+
if(element === null ) {
|
|
44
|
+
throw new Error(`Invalid Yang Editor option "${options.elem}"`);
|
|
45
|
+
}
|
|
46
|
+
this.element = element;
|
|
47
|
+
this.toolbar = new EditorToolbar(this);
|
|
48
|
+
this.body = new EditorBody(this);
|
|
49
|
+
this.selectionUtils = new SelectionUtils(this.element);
|
|
50
|
+
this.componentFactory = new ComponentFactory(this);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
render() {
|
|
54
|
+
// clear elements
|
|
55
|
+
this.element.classList.add("yang-editor");
|
|
56
|
+
this.element.style.width = this.options.width;
|
|
57
|
+
this.element.innerHTML = "";
|
|
58
|
+
// add components
|
|
59
|
+
if (this.options.mode === "edit") {
|
|
60
|
+
this.element.appendChild(this.toolbar.onMount());
|
|
61
|
+
}
|
|
62
|
+
this.element.appendChild(this.body.onMount());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setHTMLContent(html: string) {
|
|
66
|
+
this.body.content.getElement().innerHTML = html;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getHTMLContent(): string {
|
|
70
|
+
return this.body.content.getElement().innerHTML;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function create(options: YangEditorOptions): YangEditor {
|
|
75
|
+
let yangEditor = new YangEditor(options);
|
|
76
|
+
yangEditor.render();
|
|
77
|
+
return yangEditor;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const YangEditorFacade = {
|
|
81
|
+
create,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default YangEditorFacade;
|
|
85
|
+
|
package/src/style.css
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
.yang-editor {
|
|
2
|
+
background: white;
|
|
3
|
+
border: 1px solid rgba(235, 235, 235, 1);
|
|
4
|
+
padding: 0;
|
|
5
|
+
text-align: left;
|
|
6
|
+
position: relative;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.yang-editor p,
|
|
10
|
+
.yang-editor div,
|
|
11
|
+
.yang-editor button,
|
|
12
|
+
.yang-editor ul,
|
|
13
|
+
.yang-editor li {
|
|
14
|
+
margin: 0;
|
|
15
|
+
padding: 0;
|
|
16
|
+
border: none;
|
|
17
|
+
outline: none;
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
display: block;
|
|
20
|
+
list-style: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.yang-editor .yang-editor-body {
|
|
24
|
+
position: relative;
|
|
25
|
+
padding: 4px;
|
|
26
|
+
outline: none;
|
|
27
|
+
overflow: auto;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.yang-editor .yang-editor-content {
|
|
31
|
+
background: white;
|
|
32
|
+
height: 100%;
|
|
33
|
+
min-height: 100px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.yang-editor .yang-editor-toolbar {
|
|
37
|
+
background: transparent;
|
|
38
|
+
line-height: 24px;
|
|
39
|
+
user-select: none;
|
|
40
|
+
padding: 8px 16px;
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
border-bottom: 1px solid rgba(235, 235, 235, 1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.yang-editor .yang-editor-toolbar-button {
|
|
47
|
+
padding: 2px;
|
|
48
|
+
vertical-align: middle;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.yang-editor .yang-editor-content > p,
|
|
53
|
+
.yang-editor .yang-editor-content > div {
|
|
54
|
+
border-left: 44px solid transparent;
|
|
55
|
+
border-right: 44px solid transparent;
|
|
56
|
+
padding: 4px;
|
|
57
|
+
position: relative;
|
|
58
|
+
min-height: 31px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.yang-editor .yang-editor-content > p:hover,
|
|
62
|
+
.yang-editor .yang-editor-content > div:hover {
|
|
63
|
+
border-left: 44px solid transparent;
|
|
64
|
+
border-radius: 4px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
.yang-editor .yang-editor-content:focus {
|
|
70
|
+
/* background-color: red !important; */
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
.yang-editor .yang-editor-color-strip {
|
|
75
|
+
padding-left: 19px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.yang-editor .yang-editor-color-strip button {
|
|
79
|
+
margin-left: 8px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.yang-editor .yang-editor-color-strip button:first-child {
|
|
83
|
+
margin-left: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.yang-editor .yang-editor-fgcolor-strip {
|
|
87
|
+
padding-left: 8px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.yang-editor .yang-editor-fgcolor-strip button {
|
|
91
|
+
margin-left: 8px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.yang-editor .yang-editor-fgcolor-strip button:first-child {
|
|
95
|
+
margin-left: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.yang-editor .yang-editor-icon-button {
|
|
99
|
+
margin-left: 4px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.yang-editor .yang-editor-icon-button:nth-of-type(2){
|
|
103
|
+
margin-left: 16px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.yang-editor .yang-editor-collapse-container {
|
|
107
|
+
border: 1px solid rgba(235, 235, 235, 1);
|
|
108
|
+
border-radius: 4px;
|
|
109
|
+
overflow: hidden;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.yang-editor .yang-editor-collapse-container:hover {
|
|
113
|
+
border: 1px solid #81bbf8 !important;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.yang-editor .yang-editor-collapse-header {
|
|
117
|
+
border-bottom: 1px solid rgba(235, 235, 235, 1);
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
padding: 8px 10px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.yang-editor .yang-editor-collapse-header.collapsed {
|
|
124
|
+
border-bottom: 0px solid transparent;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.yang-editor .yang-editor-collapse-title {
|
|
128
|
+
font-size: 15px;
|
|
129
|
+
font-weight: 400;
|
|
130
|
+
letter-spacing: 0px;
|
|
131
|
+
line-height: 21px;
|
|
132
|
+
color: rgba(0, 0, 0, 1);
|
|
133
|
+
text-align: left;
|
|
134
|
+
padding: 0 10px;
|
|
135
|
+
flex-shrink: 1;
|
|
136
|
+
flex-grow: 1;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.yang-editor .yang-editor-collapse-body {
|
|
140
|
+
padding: 12px 16px;
|
|
141
|
+
min-height: 64px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.yang-editor .yang-editor-collapse-body.collapsed {
|
|
145
|
+
height: 0px;
|
|
146
|
+
min-height: 0px;
|
|
147
|
+
overflow: hidden;
|
|
148
|
+
padding: 0 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.yang-editor .yang-editor-collapse-button {
|
|
152
|
+
background-color: transparent;
|
|
153
|
+
flex-shrink: 0;
|
|
154
|
+
width: 20px;
|
|
155
|
+
height: 20px;
|
|
156
|
+
background-size: 16px 16px;
|
|
157
|
+
background-repeat: no-repeat;
|
|
158
|
+
background-position: center;
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
border-radius: 3px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.yang-editor .yang-editor-collapse-button:hover {
|
|
164
|
+
background-color: rgba(231, 233, 232, 1) !important;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.yang-editor .yang-editor-collapse-button.collapsed {
|
|
168
|
+
transform: rotate(-90deg);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.yang-editor a,
|
|
172
|
+
.yang-editor a:hover,
|
|
173
|
+
.yang-editor a:active,
|
|
174
|
+
.yang-editor a:visited {
|
|
175
|
+
color: rgba(17, 124, 238, 1);
|
|
176
|
+
cursor: pointer;
|
|
177
|
+
text-decoration: none;
|
|
178
|
+
}
|
|
179
|
+
.yang-editor a:hover {
|
|
180
|
+
background-color: #5FA8F633;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
.yang-editor .yang-editor-paragraph-menu {
|
|
186
|
+
width: 180px;
|
|
187
|
+
padding: 4px 8px;
|
|
188
|
+
opacity: 1;
|
|
189
|
+
background: rgba(255, 255, 255, 1);
|
|
190
|
+
border: 1px solid rgba(217, 217, 217, 1);
|
|
191
|
+
box-shadow: 1px 1px 5px rgba(217, 217, 217, 0.3);
|
|
192
|
+
border-radius: 6px;
|
|
193
|
+
position: absolute;
|
|
194
|
+
left: 0;
|
|
195
|
+
top: 31px;
|
|
196
|
+
display: none;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.yang-editor .yang-editor-paragraph-menu-item {
|
|
200
|
+
border-radius: 6px;
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
padding: 8px;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.yang-editor .yang-editor-paragraph-menu-item:hover {
|
|
206
|
+
background: rgba(239, 240, 240, 1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.yang-editor .yang-editor-paragraph-menu-item-icon {
|
|
210
|
+
flex-shrink: 0;
|
|
211
|
+
background: transparent;
|
|
212
|
+
margin-right: 8px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.yang-editor .yang-editor-paragraph-menu-item-text {
|
|
216
|
+
flex-shrink: 1;
|
|
217
|
+
flex-grow: 1;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.yang-editor .yang-editor-content-before-menu {
|
|
221
|
+
position: absolute;
|
|
222
|
+
width: 24px;
|
|
223
|
+
height: 31px;
|
|
224
|
+
left: 24px;
|
|
225
|
+
top: 0px;
|
|
226
|
+
display: none;
|
|
227
|
+
align-items: center;
|
|
228
|
+
justify-content: center;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.yang-editor .yang-editor-content-before-menu-button{
|
|
232
|
+
width: 24px;
|
|
233
|
+
height: 24px;
|
|
234
|
+
/* display: none; */
|
|
235
|
+
background-color: transparent;
|
|
236
|
+
background-size: 16px 16px;
|
|
237
|
+
background-position: center;
|
|
238
|
+
background-repeat: no-repeat;
|
|
239
|
+
display: block;
|
|
240
|
+
cursor: pointer;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.yang-editor .yang-editor-content-before-menu-button:hover {
|
|
244
|
+
background-color: #eff0f0;
|
|
245
|
+
border-radius: 6px;
|
|
246
|
+
}
|
|
247
|
+
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ComponentFactory } from "./componets";
|
|
2
|
+
|
|
3
|
+
export class SelectionUtils {
|
|
4
|
+
|
|
5
|
+
private element: HTMLElement;
|
|
6
|
+
|
|
7
|
+
constructor(element: HTMLElement) {
|
|
8
|
+
this.element = element;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public getSelectedParagraph(): HTMLElement | null {
|
|
12
|
+
let range = this.getSelectionRange();
|
|
13
|
+
if(range) {
|
|
14
|
+
let node = range.startContainer;
|
|
15
|
+
while(node && node !== this.element) {
|
|
16
|
+
if(node.nodeType === Node.ELEMENT_NODE) {
|
|
17
|
+
let elem = node as HTMLElement;
|
|
18
|
+
if(elem.classList.contains(ComponentFactory.PARAGRAPH_STYLE)) {
|
|
19
|
+
return elem;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
node = node.parentNode as Node;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public getSelectionRange(): Range | null {
|
|
29
|
+
let selection = window.getSelection();
|
|
30
|
+
if(selection && selection.rangeCount > 0) {
|
|
31
|
+
let range = selection.getRangeAt(0);
|
|
32
|
+
if(this.element.contains(range.commonAncestorContainer)) {
|
|
33
|
+
return range;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
// File Layout
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
|
|
8
|
+
// Environment Settings
|
|
9
|
+
// See also https://aka.ms/tsconfig/module
|
|
10
|
+
"module": "nodenext",
|
|
11
|
+
"target": "esnext",
|
|
12
|
+
"types": [],
|
|
13
|
+
// For nodejs:
|
|
14
|
+
"lib": ["esnext", "dom", "dom.iterable"],
|
|
15
|
+
// "types": ["node"],
|
|
16
|
+
// and npm install -D @types/node
|
|
17
|
+
|
|
18
|
+
// Other Outputs
|
|
19
|
+
"sourceMap": true,
|
|
20
|
+
"declaration": true,
|
|
21
|
+
"declarationMap": true,
|
|
22
|
+
|
|
23
|
+
// Stricter Typechecking Options
|
|
24
|
+
"noUncheckedIndexedAccess": true,
|
|
25
|
+
"exactOptionalPropertyTypes": true,
|
|
26
|
+
|
|
27
|
+
// Style Options
|
|
28
|
+
// "noImplicitReturns": true,
|
|
29
|
+
// "noImplicitOverride": true,
|
|
30
|
+
// "noUnusedLocals": true,
|
|
31
|
+
// "noUnusedParameters": true,
|
|
32
|
+
// "noFallthroughCasesInSwitch": true,
|
|
33
|
+
// "noPropertyAccessFromIndexSignature": true,
|
|
34
|
+
|
|
35
|
+
// Recommended Options
|
|
36
|
+
"strict": true,
|
|
37
|
+
"jsx": "react-jsx",
|
|
38
|
+
"verbatimModuleSyntax": false, // true
|
|
39
|
+
"isolatedModules": true,
|
|
40
|
+
"noUncheckedSideEffectImports": true,
|
|
41
|
+
"moduleDetection": "force",
|
|
42
|
+
"skipLibCheck": true
|
|
43
|
+
},
|
|
44
|
+
"include": ["src"]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|