@lesjoursfr/edith 2.1.0 → 2.1.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.
Files changed (58) hide show
  1. package/build/edith.css +1 -0
  2. package/build/edith.js +1 -0
  3. package/dist/core/dom.d.ts +224 -0
  4. package/dist/core/dom.js +480 -0
  5. package/dist/core/edit.d.ts +36 -0
  6. package/dist/core/edit.js +255 -0
  7. package/dist/core/events.d.ts +47 -0
  8. package/dist/core/events.js +100 -0
  9. package/dist/core/history.d.ts +14 -0
  10. package/dist/core/history.js +24 -0
  11. package/dist/core/index.d.ts +7 -0
  12. package/dist/core/index.js +7 -0
  13. package/dist/core/mode.d.ts +4 -0
  14. package/dist/core/mode.js +5 -0
  15. package/dist/core/range.d.ts +45 -0
  16. package/dist/core/range.js +86 -0
  17. package/dist/core/throttle.d.ts +53 -0
  18. package/dist/core/throttle.js +139 -0
  19. package/dist/edith-options.d.ts +17 -0
  20. package/dist/edith-options.js +56 -0
  21. package/dist/edith.d.ts +30 -0
  22. package/dist/edith.js +76 -0
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +1 -0
  25. package/dist/ui/button.d.ts +25 -0
  26. package/dist/ui/button.js +165 -0
  27. package/dist/ui/editor.d.ts +37 -0
  28. package/dist/ui/editor.js +322 -0
  29. package/dist/ui/index.d.ts +3 -0
  30. package/dist/ui/index.js +3 -0
  31. package/dist/ui/modal.d.ts +32 -0
  32. package/dist/ui/modal.js +145 -0
  33. package/package.json +49 -32
  34. package/src/core/dom.ts +584 -0
  35. package/src/core/{edit.js → edit.ts} +59 -40
  36. package/src/core/events.ts +148 -0
  37. package/src/core/history.ts +28 -0
  38. package/src/core/index.ts +7 -0
  39. package/src/core/mode.ts +4 -0
  40. package/src/core/{range.js → range.ts} +32 -22
  41. package/src/core/{throttle.js → throttle.ts} +37 -23
  42. package/src/css/edith.scss +283 -0
  43. package/src/edith-options.ts +75 -0
  44. package/src/edith.ts +98 -0
  45. package/src/index.ts +1 -0
  46. package/src/ui/button.ts +197 -0
  47. package/src/ui/editor.ts +403 -0
  48. package/src/ui/index.ts +3 -0
  49. package/src/ui/modal.ts +180 -0
  50. package/src/core/dom.js +0 -353
  51. package/src/core/event.js +0 -4
  52. package/src/core/history.js +0 -27
  53. package/src/core/mode.js +0 -4
  54. package/src/index.js +0 -90
  55. package/src/ui/button.js +0 -200
  56. package/src/ui/editor.js +0 -392
  57. package/src/ui/modal.js +0 -151
  58. /package/{src/css/main.scss → dist/css/edith.scss} +0 -0
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  /**
2
3
  * Based on lodash version of throttle : https://github.com/lodash/lodash/blob/master/throttle.js
3
4
  */
@@ -22,48 +23,56 @@
22
23
  * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's invoked
23
24
  * @returns {Function} Returns the new debounced function
24
25
  */
25
- function debounce(func, wait, options = {}) {
26
- let lastArgs, lastThis, result, timerId, lastCallTime;
27
-
28
- let lastInvokeTime = 0;
26
+ function debounce<F extends (...args: any) => any>(
27
+ func: F,
28
+ wait: number,
29
+ options: { leading?: boolean; trailing?: boolean; maxWait?: number } = {}
30
+ ): (...args: Parameters<F>) => ReturnType<F> {
31
+ let lastArgs: Parameters<F> | undefined,
32
+ lastThis: any | undefined,
33
+ result: ReturnType<F> | undefined,
34
+ timerId: ReturnType<typeof setTimeout> | undefined,
35
+ lastCallTime: number | undefined;
36
+
37
+ let lastInvokeTime: number = 0;
29
38
  const leading = !!options.leading;
30
39
  const maxing = "maxWait" in options;
31
- const maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : undefined;
40
+ const maxWait = maxing ? Math.max(options.maxWait || 0, wait) : undefined;
32
41
  const trailing = "trailing" in options ? !!options.trailing : true;
33
42
 
34
- function invokeFunc(time) {
43
+ function invokeFunc(time: number): ReturnType<F> {
35
44
  const args = lastArgs;
36
45
  const thisArg = lastThis;
37
46
 
38
47
  lastArgs = lastThis = undefined;
39
48
  lastInvokeTime = time;
40
- result = func.apply(thisArg, args);
41
- return result;
49
+ result = func.apply(thisArg!, args!);
50
+ return result!;
42
51
  }
43
52
 
44
- function startTimer(pendingFunc, wait) {
53
+ function startTimer(pendingFunc: () => void, wait: number): ReturnType<typeof setTimeout> {
45
54
  return setTimeout(pendingFunc, wait);
46
55
  }
47
56
 
48
- function leadingEdge(time) {
57
+ function leadingEdge(time: number): ReturnType<F> {
49
58
  // Reset any `maxWait` timer.
50
59
  lastInvokeTime = time;
51
60
  // Start the timer for the trailing edge.
52
61
  timerId = startTimer(timerExpired, wait);
53
62
  // Invoke the leading edge.
54
- return leading ? invokeFunc(time) : result;
63
+ return leading ? invokeFunc(time) : result!;
55
64
  }
56
65
 
57
- function remainingWait(time) {
58
- const timeSinceLastCall = time - lastCallTime;
66
+ function remainingWait(time: number): number {
67
+ const timeSinceLastCall = time - lastCallTime!;
59
68
  const timeSinceLastInvoke = time - lastInvokeTime;
60
69
  const timeWaiting = wait - timeSinceLastCall;
61
70
 
62
- return maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting;
71
+ return maxing ? Math.min(timeWaiting, maxWait! - timeSinceLastInvoke) : timeWaiting;
63
72
  }
64
73
 
65
- function shouldInvoke(time) {
66
- const timeSinceLastCall = time - lastCallTime;
74
+ function shouldInvoke(time: number): boolean {
75
+ const timeSinceLastCall = time - lastCallTime!;
67
76
  const timeSinceLastInvoke = time - lastInvokeTime;
68
77
 
69
78
  // Either this is the first call, activity has stopped and we're at the
@@ -73,11 +82,11 @@ function debounce(func, wait, options = {}) {
73
82
  lastCallTime === undefined ||
74
83
  timeSinceLastCall >= wait ||
75
84
  timeSinceLastCall < 0 ||
76
- (maxing && timeSinceLastInvoke >= maxWait)
85
+ (maxing && timeSinceLastInvoke >= maxWait!)
77
86
  );
78
87
  }
79
88
 
80
- function timerExpired() {
89
+ function timerExpired(): ReturnType<F> | undefined {
81
90
  const time = Date.now();
82
91
  if (shouldInvoke(time)) {
83
92
  return trailingEdge(time);
@@ -86,7 +95,7 @@ function debounce(func, wait, options = {}) {
86
95
  timerId = startTimer(timerExpired, remainingWait(time));
87
96
  }
88
97
 
89
- function trailingEdge(time) {
98
+ function trailingEdge(time: number): ReturnType<F> {
90
99
  timerId = undefined;
91
100
 
92
101
  // Only invoke if we have `lastArgs` which means `func` has been
@@ -95,14 +104,15 @@ function debounce(func, wait, options = {}) {
95
104
  return invokeFunc(time);
96
105
  }
97
106
  lastArgs = lastThis = undefined;
98
- return result;
107
+ return result!;
99
108
  }
100
109
 
101
- function debounced(...args) {
110
+ function debounced(this: any, ...args: Parameters<F>): ReturnType<F> {
102
111
  const time = Date.now();
103
112
  const isInvoking = shouldInvoke(time);
104
113
 
105
114
  lastArgs = args;
115
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
106
116
  lastThis = this;
107
117
  lastCallTime = time;
108
118
 
@@ -119,7 +129,7 @@ function debounce(func, wait, options = {}) {
119
129
  if (timerId === undefined) {
120
130
  timerId = startTimer(timerExpired, wait);
121
131
  }
122
- return result;
132
+ return result!;
123
133
  }
124
134
 
125
135
  return debounced;
@@ -144,7 +154,11 @@ function debounce(func, wait, options = {}) {
144
154
  * @param {boolean} [options.trailing=true] Specify invoking on the trailing edge of the timeout
145
155
  * @returns {Function} Returns the new throttled function
146
156
  */
147
- function throttle(func, wait, options = {}) {
157
+ function throttle<F extends (...args: any) => any>(
158
+ func: F,
159
+ wait: number,
160
+ options: { leading?: boolean; trailing?: boolean } = {}
161
+ ): (...args: Parameters<F>) => ReturnType<F> {
148
162
  const leading = "leading" in options ? !!options.leading : true;
149
163
  const trailing = "trailing" in options ? !!options.trailing : true;
150
164
 
@@ -0,0 +1,283 @@
1
+ @use "sass:color";
2
+
3
+ $color-toolbar: #212529;
4
+ $color-toolbar-border: #212529;
5
+ $color-button: #e9ecef;
6
+ $color-button-border: #ced4da;
7
+ $color-button-text: #212529;
8
+ $color-tooltip: #000;
9
+ $color-tooltip-text: #fff;
10
+ $color-editor: #fff;
11
+ $color-editor-text: #212529;
12
+ $color-modal: #fff;
13
+ $color-modal-border: #ced4da;
14
+ $color-modal-title: #000;
15
+ $color-modal-text: #212529;
16
+ $color-input-border: #bfbfbf;
17
+ $color-checkbox-background: #0d6efd;
18
+ $color-checkbox-border: #bfbfbf;
19
+ $color-modal-cancel-background: #fff;
20
+ $color-modal-cancel-color: #212529;
21
+ $color-modal-cancel-border: #212529;
22
+ $color-modal-submit-background: #0d6efd;
23
+ $color-modal-submit-color: #fff;
24
+ $color-modal-submit-border: #0d6efd;
25
+
26
+ .edith {
27
+ background-color: $color-toolbar;
28
+ border: 1px solid $color-toolbar-border;
29
+ border-radius: 0.25rem;
30
+ padding: 5px;
31
+ }
32
+
33
+ .edith-toolbar {
34
+ background-color: $color-toolbar;
35
+ }
36
+
37
+ .edith-btn {
38
+ background-color: $color-button;
39
+ border: 1px solid $color-button-border;
40
+ border-radius: 0.25rem;
41
+ color: $color-button-text;
42
+ cursor: pointer;
43
+ display: inline-block;
44
+ font-size: 1rem;
45
+ font-weight: 900;
46
+ line-height: 1.5;
47
+ padding: 0.375rem 0.75rem;
48
+ text-align: center;
49
+ text-decoration: none;
50
+ user-select: none;
51
+ vertical-align: middle;
52
+
53
+ &:disabled {
54
+ color: color.scale($color-button-text, $lightness: 70%);
55
+ cursor: not-allowed;
56
+ }
57
+ }
58
+
59
+ .edith-btn-group {
60
+ display: inline-flex;
61
+ position: relative;
62
+ vertical-align: middle;
63
+
64
+ &:not(:first-child) {
65
+ margin-left: 10px;
66
+ }
67
+
68
+ :not(:first-child) {
69
+ margin-left: -1px;
70
+ }
71
+
72
+ .edith-btn:not(:last-child) {
73
+ border-bottom-right-radius: 0;
74
+ border-top-right-radius: 0;
75
+ }
76
+
77
+ .edith-btn:not(:first-child) {
78
+ border-bottom-left-radius: 0;
79
+ border-top-left-radius: 0;
80
+ }
81
+ }
82
+
83
+ .edith-btn-nbsp {
84
+ &::before {
85
+ content: "\0020";
86
+ display: block;
87
+ height: 16px;
88
+ width: 12px;
89
+ }
90
+ }
91
+
92
+ .edith-tooltip {
93
+ background: $color-tooltip;
94
+ border-radius: 4px;
95
+ color: $color-tooltip-text;
96
+ font-size: 13px;
97
+ font-weight: bold;
98
+ padding: 4px 8px;
99
+ z-index: 10;
100
+
101
+ .arrow,
102
+ .arrow::before {
103
+ background: inherit;
104
+ height: 8px;
105
+ position: absolute;
106
+ width: 8px;
107
+ }
108
+
109
+ .arrow {
110
+ visibility: hidden;
111
+ }
112
+
113
+ .arrow::before {
114
+ content: "";
115
+ transform: rotate(45deg);
116
+ visibility: visible;
117
+ }
118
+
119
+ &[data-popper-placement^="top"] > .arrow {
120
+ bottom: -4px;
121
+ }
122
+
123
+ &[data-popper-placement^="bottom"] > .arrow {
124
+ top: -4px;
125
+ }
126
+
127
+ &[data-popper-placement^="left"] > .arrow {
128
+ right: -4px;
129
+ }
130
+
131
+ &[data-popper-placement^="right"] > .arrow {
132
+ left: -4px;
133
+ }
134
+ }
135
+
136
+ .edith-editing-area {
137
+ background-color: $color-editor;
138
+ border-radius: 0.25rem;
139
+ margin-top: 5px;
140
+ padding: 5px;
141
+ }
142
+
143
+ .edith-visual,
144
+ .edith-code {
145
+ height: 100%;
146
+ outline: none;
147
+ overflow: auto;
148
+ }
149
+
150
+ .edith-hidden {
151
+ display: none;
152
+ }
153
+
154
+ .edith-visual {
155
+ color: $color-editor-text;
156
+
157
+ .edith-nbsp {
158
+ color: color.scale($color-button-text, $lightness: 70%);
159
+ }
160
+ }
161
+
162
+ .edith-modal {
163
+ background: $color-modal;
164
+ border: 2px solid $color-modal-border;
165
+ border-radius: 10px;
166
+ left: calc(50% - 200px);
167
+ position: fixed;
168
+ top: 20%;
169
+ width: 400px;
170
+ z-index: 10;
171
+
172
+ .edith-modal-header {
173
+ border-bottom: 1px solid $color-modal-border;
174
+ color: $color-modal-title;
175
+ font-size: 20px;
176
+ font-weight: 700;
177
+ line-height: 1.4;
178
+ padding: 5px 10px;
179
+ }
180
+
181
+ .edith-modal-content {
182
+ color: $color-modal-text;
183
+ margin: 10px;
184
+ }
185
+
186
+ .edith-modal-input {
187
+ display: flex;
188
+ flex-wrap: wrap;
189
+ margin: 10px 0;
190
+
191
+ label,
192
+ input {
193
+ width: 100%;
194
+ }
195
+
196
+ input {
197
+ appearance: none;
198
+ background-clip: padding-box;
199
+ background-color: $color-modal;
200
+ border: 1px solid $color-checkbox-border;
201
+ border-radius: 0.25rem;
202
+ font-size: 1rem;
203
+ font-weight: 400;
204
+ line-height: 1.5;
205
+ outline: 0;
206
+ padding: 0.375rem 0.75rem;
207
+ width: 100%;
208
+ }
209
+
210
+ label {
211
+ font-size: 16px;
212
+ font-weight: 700;
213
+ margin-bottom: 5px;
214
+ }
215
+ }
216
+
217
+ .edith-modal-checkbox {
218
+ margin: 10px 0;
219
+
220
+ label {
221
+ display: flex;
222
+ }
223
+
224
+ input {
225
+ appearance: none;
226
+ background-color: $color-modal;
227
+ background-position: 50%;
228
+ background-repeat: no-repeat;
229
+ background-size: contain;
230
+ border: 1px solid $color-checkbox-border;
231
+ border-radius: 0.25em;
232
+ height: 1em;
233
+ margin-top: 0.25em;
234
+ vertical-align: top;
235
+ width: 1em;
236
+
237
+ &:checked {
238
+ background-color: $color-checkbox-background;
239
+ background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3E%3C/svg%3E");
240
+ border-color: $color-checkbox-background;
241
+ }
242
+ }
243
+ }
244
+
245
+ .edith-modal-footer {
246
+ border-top: 1px solid $color-modal-border;
247
+ display: flex;
248
+ justify-content: flex-end;
249
+ padding: 5px 10px;
250
+
251
+ :not(:last-child) {
252
+ margin-right: 10px;
253
+ }
254
+ }
255
+
256
+ .edith-modal-cancel,
257
+ .edith-modal-submit {
258
+ border: 1px solid transparent;
259
+ border-radius: 0.25rem;
260
+ cursor: pointer;
261
+ display: inline-block;
262
+ font-size: 1rem;
263
+ font-weight: 400;
264
+ line-height: 1.5;
265
+ padding: 0.375rem 0.75rem;
266
+ text-align: center;
267
+ text-decoration: none;
268
+ user-select: none;
269
+ vertical-align: middle;
270
+ }
271
+
272
+ .edith-modal-cancel {
273
+ background-color: $color-modal-cancel-background;
274
+ border-color: $color-modal-cancel-border;
275
+ color: $color-modal-cancel-color;
276
+ }
277
+
278
+ .edith-modal-submit {
279
+ background-color: $color-modal-submit-background;
280
+ border-color: $color-modal-submit-border;
281
+ color: $color-modal-submit-color;
282
+ }
283
+ }
@@ -0,0 +1,75 @@
1
+ import { Edith } from "./edith.js";
2
+ import { EdithButton } from "./ui/index.js";
3
+
4
+ export type EdithToolbarOption = [string, string[]][];
5
+
6
+ export type EdithButtonsOption = { [keyof: string]: (context: Edith) => EdithButton };
7
+
8
+ export type EdithOptions = {
9
+ height: number;
10
+ resizable: boolean;
11
+ toolbar: EdithToolbarOption;
12
+ buttons: EdithButtonsOption;
13
+ initialContent: string;
14
+ };
15
+
16
+ /**
17
+ * Edith default options
18
+ */
19
+ export const DefaultOptions: EdithOptions = {
20
+ /**
21
+ * The editor's height
22
+ *
23
+ * @type {number}
24
+ * @default 80
25
+ */
26
+ height: 80,
27
+
28
+ /**
29
+ * Control if the editor can be resized by the user
30
+ *
31
+ * @type {boolean}
32
+ * @default false
33
+ */
34
+ resizable: false,
35
+
36
+ /**
37
+ *
38
+ * An array that define which button is shown in the toolbar and how they are grouped.
39
+ *
40
+ * @type {EdithToolbarOption}
41
+ * @example
42
+ * toolbar: [
43
+ * ["style", ["bold", "italic", "underline", "strikethrough"]],
44
+ * ["extra", ["subscript", "superscript"]]
45
+ * ]
46
+ */
47
+ toolbar: [["style", ["bold", "italic", "underline", "strikethrough"]]],
48
+
49
+ /**
50
+ *
51
+ * Associative object with the button name and builder to create it.
52
+ *
53
+ * @type {EdithButtonsOption}
54
+ * @example
55
+ * buttons: {
56
+ * bold: (context: Edith) =>
57
+ * new EdithButton(context, {
58
+ * icon: "fa-solid fa-bold",
59
+ * title: "Gras",
60
+ * onclick: (ctx: Edith, event: Event) => {
61
+ * ctx.editor.wrapInsideTag("b");
62
+ * },
63
+ * }),
64
+ * }
65
+ */
66
+ buttons: {},
67
+
68
+ /**
69
+ * The editor's initial content
70
+ *
71
+ * @type {string}
72
+ * @default ""
73
+ */
74
+ initialContent: "",
75
+ };
package/src/edith.ts ADDED
@@ -0,0 +1,98 @@
1
+ import { Events, addClass, createNodeWith, off, on, removeClass, trigger } from "./core/index.js";
2
+ import { DefaultOptions, EdithButtonsOption, EdithOptions, EdithToolbarOption } from "./edith-options.js";
3
+ import { EdithButton, EdithButtons, EdithEditor } from "./ui/index.js";
4
+
5
+ declare global {
6
+ interface HTMLElement {
7
+ edith?: Edith;
8
+ }
9
+ }
10
+
11
+ export { EdithButton };
12
+
13
+ export class Edith {
14
+ private readonly element: HTMLElement;
15
+ public readonly toolbar: HTMLDivElement;
16
+ public readonly editor: EdithEditor;
17
+ public readonly modals: HTMLDivElement;
18
+
19
+ /**
20
+ * Create a new editor
21
+ * @constructor
22
+ * @param {HTMLElement} element - The <input> element to add the Wysiwyg to.
23
+ * @param {Object} options - Options for the editor.
24
+ */
25
+ constructor(element: HTMLElement, options: Partial<EdithOptions>) {
26
+ // Render the editor in the element
27
+ this.element = element;
28
+ addClass(this.element, "edith");
29
+
30
+ // Create the toolbar
31
+ this.toolbar = createNodeWith("div", { attributes: { class: "edith-toolbar" } });
32
+ this.element.append(this.toolbar);
33
+
34
+ // Create buttons
35
+ const buttons: EdithButtonsOption = options.buttons ?? DefaultOptions.buttons;
36
+ const toolbar: EdithToolbarOption = options.toolbar ?? DefaultOptions.toolbar;
37
+ for (const { 0: group, 1: btns } of toolbar) {
38
+ // Create the button group
39
+ const btnGroup = document.createElement("div");
40
+ btnGroup.setAttribute("id", group);
41
+ btnGroup.setAttribute("class", "edith-btn-group");
42
+ this.toolbar.append(btnGroup);
43
+
44
+ // Add buttons
45
+ for (const btnId of btns) {
46
+ // Render the button
47
+ const buttonBuilder = buttons[btnId] ?? EdithButtons[btnId];
48
+ btnGroup.append(buttonBuilder(this).render());
49
+ }
50
+ }
51
+
52
+ // Create the editor
53
+ this.editor = new EdithEditor(this, {
54
+ initialContent: options.initialContent ?? DefaultOptions.initialContent,
55
+ height: options.height ?? DefaultOptions.height,
56
+ resizable: options.resizable ?? DefaultOptions.resizable,
57
+ });
58
+ this.element.append(this.editor.render());
59
+
60
+ // Create the modals
61
+ this.modals = createNodeWith("div", { attributes: { class: "edith-modals" } });
62
+ this.element.append(this.modals);
63
+
64
+ // Add the Edith instance to the DOM
65
+ this.element.edith = this;
66
+
67
+ // Trigger the initialized event once its initialized
68
+ this.trigger(Events.initialized);
69
+ }
70
+
71
+ public on(type: string, listener: EventListenerOrEventListenerObject): void {
72
+ on(this.element, type, listener);
73
+ }
74
+
75
+ public off(type: string, listener: EventListenerOrEventListenerObject): void {
76
+ off(this.element, type, listener);
77
+ }
78
+
79
+ public trigger(type: Events, payload?: { [key: string]: unknown }): void {
80
+ trigger(this.element, type, payload);
81
+ }
82
+
83
+ public setContent(content: string): void {
84
+ this.editor.setContent(content);
85
+ }
86
+
87
+ public getContent(): string {
88
+ return this.editor.getContent();
89
+ }
90
+
91
+ public destroy(): void {
92
+ removeClass(this.element, "edith");
93
+ this.modals.remove();
94
+ this.editor.destroy();
95
+ this.toolbar.remove();
96
+ this.element.remove();
97
+ }
98
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./edith.js";