@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/src/context.ts ADDED
@@ -0,0 +1,4 @@
1
+ import EventCenter from "./event";
2
+
3
+ export const globalEventCenter = new EventCenter();
4
+
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
+