@juit/vue-tiny-editor 0.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.md +211 -0
- package/README.md +8 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +670 -0
- package/dist/index.js.map +1 -0
- package/dist/style.css +1 -0
- package/package.json +63 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Apache License
|
|
2
|
+
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
|
|
5
|
+
[http://www.apache.org/licenses/](http://www.apache.org/licenses/)
|
|
6
|
+
|
|
7
|
+
##### TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
8
|
+
|
|
9
|
+
### 1. Definitions.
|
|
10
|
+
|
|
11
|
+
"**License**" shall mean the terms and conditions for use, reproduction,
|
|
12
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
13
|
+
|
|
14
|
+
"**Licensor**" shall mean the copyright owner or entity authorized by
|
|
15
|
+
the copyright owner that is granting the License.
|
|
16
|
+
|
|
17
|
+
"**Legal Entity**" shall mean the union of the acting entity and all
|
|
18
|
+
other entities that control, are controlled by, or are under common
|
|
19
|
+
control with that entity. For the purposes of this definition,
|
|
20
|
+
"**control**" means **(i)** the power, direct or indirect, to cause the
|
|
21
|
+
direction or management of such entity, whether by contract or
|
|
22
|
+
otherwise, _or_ **(ii)** ownership of fifty percent (50%) or more of
|
|
23
|
+
the outstanding shares, _or_ **(iii)** beneficial ownership of such
|
|
24
|
+
entity.
|
|
25
|
+
|
|
26
|
+
"**You**" (or "**Your**") shall mean an individual or Legal Entity
|
|
27
|
+
exercising permissions granted by this License.
|
|
28
|
+
|
|
29
|
+
"**Source**" form shall mean the preferred form for making
|
|
30
|
+
modifications, including but not limited to software source code,
|
|
31
|
+
documentation source, and configuration files.
|
|
32
|
+
|
|
33
|
+
"**Object**" form shall mean any form resulting from mechanical
|
|
34
|
+
transformation or translation of a Source form, including but not
|
|
35
|
+
limited to compiled object code, generated documentation, and
|
|
36
|
+
conversions to other media types.
|
|
37
|
+
|
|
38
|
+
"**Work**" shall mean the work of authorship, whether in Source or
|
|
39
|
+
Object form, made available under the License, as indicated by a
|
|
40
|
+
copyright notice that is included in or attached to the work (an example
|
|
41
|
+
is provided in the Appendix below).
|
|
42
|
+
|
|
43
|
+
"**Derivative Works**" shall mean any work, whether in Source or Object
|
|
44
|
+
form, that is based on (or derived from) the Work and for which the
|
|
45
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
46
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
47
|
+
of this License, Derivative Works shall not include works that remain
|
|
48
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
49
|
+
the Work and Derivative Works thereof.
|
|
50
|
+
|
|
51
|
+
"**Contribution**" shall mean any work of authorship, including the
|
|
52
|
+
original version of the Work and any modifications or additions to that
|
|
53
|
+
Work or Derivative Works thereof, that is intentionally submitted to
|
|
54
|
+
Licensor for inclusion in the Work by the copyright owner or by an
|
|
55
|
+
individual or Legal Entity authorized to submit on behalf of the
|
|
56
|
+
copyright owner. For the purposes of this definition, "**submitted**"
|
|
57
|
+
means any form of electronic, verbal, or written communication sent to
|
|
58
|
+
the Licensor or its representatives, including but not limited to
|
|
59
|
+
communication on electronic mailing lists, source code control systems,
|
|
60
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
61
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
62
|
+
excluding communication that is conspicuously marked or otherwise
|
|
63
|
+
designated in writing by the copyright owner as "**Not a
|
|
64
|
+
Contribution**".
|
|
65
|
+
|
|
66
|
+
"**Contributor**" shall mean Licensor and any individual or Legal Entity
|
|
67
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
68
|
+
subsequently incorporated within the Work.
|
|
69
|
+
|
|
70
|
+
### 2. Grant of Copyright License.
|
|
71
|
+
|
|
72
|
+
Subject to the terms and conditions of this License, each Contributor
|
|
73
|
+
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
|
|
74
|
+
royalty-free, irrevocable copyright license to reproduce, prepare
|
|
75
|
+
Derivative Works of, publicly display, publicly perform, sublicense,
|
|
76
|
+
and distribute the Work and such Derivative Works in Source or Object
|
|
77
|
+
form.
|
|
78
|
+
|
|
79
|
+
### 3. Grant of Patent License.
|
|
80
|
+
|
|
81
|
+
Subject to the terms and conditions of this License, each Contributor
|
|
82
|
+
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
|
|
83
|
+
royalty-free, irrevocable (except as stated in this section) patent
|
|
84
|
+
license to make, have made, use, offer to sell, sell, import, and
|
|
85
|
+
otherwise transfer the Work, where such license applies only to those
|
|
86
|
+
patent claims licensable by such Contributor that are necessarily
|
|
87
|
+
infringed by their Contribution(s) alone or by combination of their
|
|
88
|
+
Contribution(s) with the Work to which such Contribution(s) was
|
|
89
|
+
submitted. If You institute patent litigation against any entity
|
|
90
|
+
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
|
91
|
+
the Work or a Contribution incorporated within the Work constitutes
|
|
92
|
+
direct or contributory patent infringement, then any patent licenses
|
|
93
|
+
granted to You under this License for that Work shall terminate as of
|
|
94
|
+
the date such litigation is filed.
|
|
95
|
+
|
|
96
|
+
### 4. Redistribution.
|
|
97
|
+
|
|
98
|
+
You may reproduce and distribute copies of the Work or Derivative Works
|
|
99
|
+
thereof in any medium, with or without modifications, and in Source or
|
|
100
|
+
Object form, provided that You meet the following conditions:
|
|
101
|
+
|
|
102
|
+
<ol type="a">
|
|
103
|
+
<li>You must give any other recipients of the Work or Derivative Works
|
|
104
|
+
a copy of this License; and
|
|
105
|
+
<li>You must cause any modified files to carry prominent notices
|
|
106
|
+
stating that You changed the files; and
|
|
107
|
+
<li>You must retain, in the Source form of any Derivative Works that
|
|
108
|
+
You distribute, all copyright, patent, trademark, and attribution
|
|
109
|
+
notices from the Source form of the Work, excluding those notices
|
|
110
|
+
that do not pertain to any part of the Derivative Works; and
|
|
111
|
+
<li>If the Work includes a "NOTICE" text file as part of its
|
|
112
|
+
distribution, then any Derivative Works that You distribute must
|
|
113
|
+
include a readable copy of the attribution notices contained within
|
|
114
|
+
such NOTICE file, excluding those notices that do not pertain to any
|
|
115
|
+
part of the Derivative Works, in at least one of the following
|
|
116
|
+
places: within a NOTICE text file distributed as part of the
|
|
117
|
+
Derivative Works; within the Source form or documentation, if
|
|
118
|
+
provided along with the Derivative Works; or, within a display
|
|
119
|
+
generated by the Derivative Works, if and wherever such third-party
|
|
120
|
+
notices normally appear. The contents of the NOTICE file are for
|
|
121
|
+
informational purposes only and do not modify the License. You may
|
|
122
|
+
add Your own attribution notices within Derivative Works that You
|
|
123
|
+
distribute, alongside or as an addendum to the NOTICE text from the
|
|
124
|
+
Work, provided that such additional attribution notices cannot be
|
|
125
|
+
construed as modifying the License.
|
|
126
|
+
</ol>
|
|
127
|
+
|
|
128
|
+
You may add Your own copyright statement to Your modifications and may
|
|
129
|
+
provide additional or different license terms and conditions for use,
|
|
130
|
+
reproduction, or distribution of Your modifications, or for any such
|
|
131
|
+
Derivative Works as a whole, provided Your use, reproduction, and
|
|
132
|
+
distribution of the Work otherwise complies with the conditions stated
|
|
133
|
+
in this License.
|
|
134
|
+
|
|
135
|
+
### 5. Submission of Contributions.
|
|
136
|
+
|
|
137
|
+
Unless You explicitly state otherwise, any Contribution intentionally
|
|
138
|
+
submitted for inclusion in the Work by You to the Licensor shall be
|
|
139
|
+
under the terms and conditions of this License, without any additional
|
|
140
|
+
terms or conditions. Notwithstanding the above, nothing herein shall
|
|
141
|
+
supersede or modify the terms of any separate license agreement you may
|
|
142
|
+
have executed with Licensor regarding such Contributions.
|
|
143
|
+
|
|
144
|
+
### 6. Trademarks.
|
|
145
|
+
|
|
146
|
+
This License does not grant permission to use the trade names,
|
|
147
|
+
trademarks, service marks, or product names of the Licensor, except as
|
|
148
|
+
required for reasonable and customary use in describing the origin of
|
|
149
|
+
the Work and reproducing the content of the NOTICE file.
|
|
150
|
+
|
|
151
|
+
### 7. Disclaimer of Warranty.
|
|
152
|
+
|
|
153
|
+
Unless required by applicable law or agreed to in writing, Licensor
|
|
154
|
+
provides the Work (and each Contributor provides its Contributions) on
|
|
155
|
+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
|
156
|
+
express or implied, including, without limitation, any warranties or
|
|
157
|
+
conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR
|
|
158
|
+
A PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
159
|
+
appropriateness of using or redistributing the Work and assume any risks
|
|
160
|
+
associated with Your exercise of permissions under this License.
|
|
161
|
+
|
|
162
|
+
### 8. Limitation of Liability.
|
|
163
|
+
|
|
164
|
+
In no event and under no legal theory, whether in tort (including
|
|
165
|
+
negligence), contract, or otherwise, unless required by applicable law
|
|
166
|
+
(such as deliberate and grossly negligent acts) or agreed to in writing,
|
|
167
|
+
shall any Contributor be liable to You for damages, including any
|
|
168
|
+
direct, indirect, special, incidental, or consequential damages of any
|
|
169
|
+
character arising as a result of this License or out of the use or
|
|
170
|
+
inability to use the Work (including but not limited to damages for loss
|
|
171
|
+
of goodwill, work stoppage, computer failure or malfunction, or any and
|
|
172
|
+
all other commercial damages or losses), even if such Contributor has
|
|
173
|
+
been advised of the possibility of such damages.
|
|
174
|
+
|
|
175
|
+
### 9. Accepting Warranty or Additional Liability.
|
|
176
|
+
|
|
177
|
+
While redistributing the Work or Derivative Works thereof, You may
|
|
178
|
+
choose to offer, and charge a fee for, acceptance of support, warranty,
|
|
179
|
+
indemnity, or other liability obligations and/or rights consistent with
|
|
180
|
+
this License. However, in accepting such obligations, You may act only
|
|
181
|
+
on Your own behalf and on Your sole responsibility, not on behalf of any
|
|
182
|
+
other Contributor, and only if You agree to indemnify, defend, and hold
|
|
183
|
+
each Contributor harmless for any liability incurred by, or claims
|
|
184
|
+
asserted against, such Contributor by reason of your accepting any
|
|
185
|
+
such warranty or additional liability.
|
|
186
|
+
|
|
187
|
+
##### END OF TERMS AND CONDITIONS
|
|
188
|
+
|
|
189
|
+
## APPENDIX: How to apply the Apache License to your work.
|
|
190
|
+
|
|
191
|
+
To apply the Apache License to your work, attach the following
|
|
192
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
193
|
+
replaced with your own identifying information. (Don't include the
|
|
194
|
+
brackets!) The text should be enclosed in the appropriate comment
|
|
195
|
+
syntax for the file format. We also recommend that a file or class name
|
|
196
|
+
and description of purpose be included on the same "printed page" as the
|
|
197
|
+
copyright notice for easier identification within third-party archives.
|
|
198
|
+
|
|
199
|
+
> #### Copyright [yyyy] [name of copyright owner]
|
|
200
|
+
>
|
|
201
|
+
> Licensed under the Apache License, Version 2.0 (the "License");
|
|
202
|
+
> you may not use this file except in compliance with the License.
|
|
203
|
+
> You may obtain a copy of the License at
|
|
204
|
+
>
|
|
205
|
+
> [http://www.apache.org/licenses/](http://www.apache.org/licenses/)
|
|
206
|
+
>
|
|
207
|
+
> Unless required by applicable law or agreed to in writing, software
|
|
208
|
+
> distributed under the License is distributed on an "AS IS" BASIS,
|
|
209
|
+
> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
210
|
+
> See the License for the specific language governing permissions and
|
|
211
|
+
> limitations under the License.
|
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ComponentOptionsMixin } from 'vue';
|
|
2
|
+
import { ComponentProvideOptions } from 'vue';
|
|
3
|
+
import { DefineComponent } from 'vue';
|
|
4
|
+
import { ExtractPropTypes } from 'vue';
|
|
5
|
+
import { PropType } from 'vue';
|
|
6
|
+
import { PublicProps } from 'vue';
|
|
7
|
+
|
|
8
|
+
declare const _default: DefineComponent<ExtractPropTypes< {
|
|
9
|
+
/** A flag indicating if content can be edited (default: `true`) */
|
|
10
|
+
editable: {
|
|
11
|
+
type: BooleanConstructor;
|
|
12
|
+
required: false;
|
|
13
|
+
default: boolean;
|
|
14
|
+
};
|
|
15
|
+
/** A flag indicating whether dark mode is enabled or not */
|
|
16
|
+
dark: {
|
|
17
|
+
type: BooleanConstructor;
|
|
18
|
+
required: false;
|
|
19
|
+
default: boolean;
|
|
20
|
+
};
|
|
21
|
+
/** A placeholder for when no text is available */
|
|
22
|
+
placeholder: {
|
|
23
|
+
type: StringConstructor;
|
|
24
|
+
required: false;
|
|
25
|
+
default: string;
|
|
26
|
+
};
|
|
27
|
+
/** A list of mentions to be used in the editor */
|
|
28
|
+
mentions: {
|
|
29
|
+
type: PropType<Record<string, string> | null>;
|
|
30
|
+
required: false;
|
|
31
|
+
default: () => {};
|
|
32
|
+
};
|
|
33
|
+
modelValue: {
|
|
34
|
+
type: PropType<string>;
|
|
35
|
+
};
|
|
36
|
+
}>, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
|
|
37
|
+
mention: (args_0: string) => any;
|
|
38
|
+
submit: (args_0: string) => any;
|
|
39
|
+
"update:modelValue": (modelValue: string) => any;
|
|
40
|
+
}, string, PublicProps, Readonly<ExtractPropTypes< {
|
|
41
|
+
/** A flag indicating if content can be edited (default: `true`) */
|
|
42
|
+
editable: {
|
|
43
|
+
type: BooleanConstructor;
|
|
44
|
+
required: false;
|
|
45
|
+
default: boolean;
|
|
46
|
+
};
|
|
47
|
+
/** A flag indicating whether dark mode is enabled or not */
|
|
48
|
+
dark: {
|
|
49
|
+
type: BooleanConstructor;
|
|
50
|
+
required: false;
|
|
51
|
+
default: boolean;
|
|
52
|
+
};
|
|
53
|
+
/** A placeholder for when no text is available */
|
|
54
|
+
placeholder: {
|
|
55
|
+
type: StringConstructor;
|
|
56
|
+
required: false;
|
|
57
|
+
default: string;
|
|
58
|
+
};
|
|
59
|
+
/** A list of mentions to be used in the editor */
|
|
60
|
+
mentions: {
|
|
61
|
+
type: PropType<Record<string, string> | null>;
|
|
62
|
+
required: false;
|
|
63
|
+
default: () => {};
|
|
64
|
+
};
|
|
65
|
+
modelValue: {
|
|
66
|
+
type: PropType<string>;
|
|
67
|
+
};
|
|
68
|
+
}>> & Readonly<{
|
|
69
|
+
onMention?: ((args_0: string) => any) | undefined;
|
|
70
|
+
onSubmit?: ((args_0: string) => any) | undefined;
|
|
71
|
+
"onUpdate:modelValue"?: ((modelValue: string) => any) | undefined;
|
|
72
|
+
}>, {
|
|
73
|
+
editable: boolean;
|
|
74
|
+
dark: boolean;
|
|
75
|
+
placeholder: string;
|
|
76
|
+
mentions: Record<string, string> | null;
|
|
77
|
+
}, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
|
|
78
|
+
export default _default;
|
|
79
|
+
|
|
80
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
import { openBlock as w, createElementBlock as y, createElementVNode as x, defineComponent as Qe, mergeModels as je, useModel as Ye, watch as T, shallowRef as q, ref as E, computed as H, onMounted as Ze, nextTick as Le, onUnmounted as et, normalizeClass as M, Fragment as tt, renderList as nt, createTextVNode as ot, toDisplayString as V, createCommentVNode as Me, createVNode as P, unref as A, withDirectives as De, vModelText as Be } from "vue";
|
|
2
|
+
function ie(n, e) {
|
|
3
|
+
return n instanceof Node && (n = I(n)), n.isPointInRange(e.startContainer, e.startOffset) && n.isPointInRange(e.endContainer, e.endOffset) ? e : null;
|
|
4
|
+
}
|
|
5
|
+
function rt(n) {
|
|
6
|
+
const e = document.createRange();
|
|
7
|
+
return e.selectNode(n), e;
|
|
8
|
+
}
|
|
9
|
+
function I(n) {
|
|
10
|
+
const e = document.createRange();
|
|
11
|
+
return e.selectNodeContents(n), e;
|
|
12
|
+
}
|
|
13
|
+
function be(n, e, a) {
|
|
14
|
+
if (!I(n).isPointInRange(e, a))
|
|
15
|
+
throw new Error("Range out of bounds");
|
|
16
|
+
const i = document.createRange();
|
|
17
|
+
return i.setStart(n, 0), i.setEnd(e, a), i.toString();
|
|
18
|
+
}
|
|
19
|
+
function W(n, e) {
|
|
20
|
+
const a = { start: 0, end: 0, backwards: e.backwards };
|
|
21
|
+
return a.start = a.end = be(n, e.startContainer, e.startOffset).length, e.collapsed || (a.end = be(n, e.endContainer, e.endOffset).length), a;
|
|
22
|
+
}
|
|
23
|
+
function U(n, e) {
|
|
24
|
+
const a = I(n);
|
|
25
|
+
e.backwards && (a.backwards = !0);
|
|
26
|
+
let { start: s, end: i } = e, l = null, r = null, f = !1, m = !1;
|
|
27
|
+
const c = document.createNodeIterator(n, NodeFilter.SHOW_TEXT);
|
|
28
|
+
for (let g = c.nextNode(); g && (s >= 0 || i > 0); g = c.nextNode()) {
|
|
29
|
+
l || (l = g), r = g;
|
|
30
|
+
const v = g.nodeValue.length;
|
|
31
|
+
if (s >= 0 && s < v && (f = !0, a.setStart(g, s), s === i))
|
|
32
|
+
return m = "extra", a.setEnd(g, i), a;
|
|
33
|
+
if (i > 0 && i <= v && (m = !0, a.setEnd(g, i), i === s))
|
|
34
|
+
return f = "extra", a.setStart(g, s), a;
|
|
35
|
+
s -= v, i -= v;
|
|
36
|
+
}
|
|
37
|
+
return !f && l && a.setStart(l, 0), !m && r && a.setEnd(r, r.length), a;
|
|
38
|
+
}
|
|
39
|
+
const st = /* @__PURE__ */ new Set([
|
|
40
|
+
"button",
|
|
41
|
+
"embed",
|
|
42
|
+
"form",
|
|
43
|
+
"iframe",
|
|
44
|
+
"input",
|
|
45
|
+
"object",
|
|
46
|
+
"script",
|
|
47
|
+
"select",
|
|
48
|
+
"style",
|
|
49
|
+
"textarea"
|
|
50
|
+
]);
|
|
51
|
+
function $e(n) {
|
|
52
|
+
if (!n || !n.match(/^https?:\/\/[^\s]+$/)) return null;
|
|
53
|
+
try {
|
|
54
|
+
return new URL(n).href;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function qe(n, e, a = !1) {
|
|
60
|
+
if (a) return n;
|
|
61
|
+
const s = document.createElement(e);
|
|
62
|
+
return s.append(n), s;
|
|
63
|
+
}
|
|
64
|
+
function Z(n, e = {}) {
|
|
65
|
+
var s;
|
|
66
|
+
const a = document.createDocumentFragment();
|
|
67
|
+
for (const i of n.childNodes)
|
|
68
|
+
if (i.nodeType === Node.TEXT_NODE)
|
|
69
|
+
i.nodeValue && a.append(i.cloneNode());
|
|
70
|
+
else if (i.nodeType === Node.ELEMENT_NODE) {
|
|
71
|
+
const l = i, r = l.tagName.toLowerCase();
|
|
72
|
+
if (st.has(r)) continue;
|
|
73
|
+
const f = ((s = a.lastChild) == null ? void 0 : s.nodeType) === Node.ELEMENT_NODE ? {
|
|
74
|
+
element: a.lastChild,
|
|
75
|
+
tagName: a.lastChild.tagName.toLowerCase()
|
|
76
|
+
} : null, m = document.createDocumentFragment();
|
|
77
|
+
if (r === "b" || r === "strong") {
|
|
78
|
+
const c = Z(l, { ...e, bold: !0 });
|
|
79
|
+
if (!c.hasChildNodes()) continue;
|
|
80
|
+
(f == null ? void 0 : f.tagName) === "b" ? f.element.append(c) : m.append(qe(c, "b", e.bold));
|
|
81
|
+
} else if (r === "i" || r === "em") {
|
|
82
|
+
const c = Z(l, { ...e, italic: !0 });
|
|
83
|
+
if (!c.hasChildNodes()) continue;
|
|
84
|
+
(f == null ? void 0 : f.tagName) === "i" ? f.element.append(c) : m.append(qe(c, "i", e.italic));
|
|
85
|
+
} else if (r === "br")
|
|
86
|
+
m.append(document.createElement("br"));
|
|
87
|
+
else if (r === "div" || r === "p" || r === "pre" || r === "h1" || r === "h2" || r === "h3" || r === "h4" || r === "h5" || r === "h6" || r === "ul" || r === "ol" || r === "li") {
|
|
88
|
+
m.append(document.createElement("br"));
|
|
89
|
+
const c = Z(l, e);
|
|
90
|
+
c.hasChildNodes() && (m.append(c), m.append(document.createElement("br")));
|
|
91
|
+
} else if (r === "link") {
|
|
92
|
+
if (l.getAttribute("rel") !== "mention") continue;
|
|
93
|
+
const g = l.getAttribute("name"), v = l.getAttribute("title");
|
|
94
|
+
if (!(g && v)) continue;
|
|
95
|
+
const h = document.createElement("link");
|
|
96
|
+
h.setAttribute("rel", "mention"), h.setAttribute("title", v), h.setAttribute("name", g), m.append(h);
|
|
97
|
+
} else
|
|
98
|
+
m.append(Z(l, e));
|
|
99
|
+
m.hasChildNodes() && a.append(m);
|
|
100
|
+
}
|
|
101
|
+
return a;
|
|
102
|
+
}
|
|
103
|
+
function lt(n) {
|
|
104
|
+
const e = n.map((s, i) => ({ ...s, index: i }));
|
|
105
|
+
e.sort((s, i) => s.start < i.start ? -1 : s.start > i.start || s.end < i.end ? 1 : s.end > i.end || /* v8 ignore next 3 */
|
|
106
|
+
s.index < i.index ? -1 : s.index > i.index ? 1 : 0);
|
|
107
|
+
const a = /* @__PURE__ */ new Set();
|
|
108
|
+
for (let s = 0; s < e.length; s++) {
|
|
109
|
+
const i = e[s];
|
|
110
|
+
for (let l = s + 1; l < e.length; l++) {
|
|
111
|
+
const r = e[l];
|
|
112
|
+
a.has(r) || (r.start === r.end || r.start >= i.start && r.start < i.end || r.end >= i.start && r.end < i.end) && a.add(r);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return e.filter((s) => !a.has(s)).filter((s, i, l) => {
|
|
116
|
+
const r = l[i - 1];
|
|
117
|
+
return (r == null ? void 0 : r.end) === s.start && r.href === s.href ? (r.end = s.end, !1) : !0;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function at(n) {
|
|
121
|
+
const e = [], a = /(https?:\/\/[^\s]+)/gm, s = n.textContent;
|
|
122
|
+
for (let r = a.exec(s); r; r = a.exec(s)) {
|
|
123
|
+
const f = r[1], m = $e(f);
|
|
124
|
+
m && e.push({ start: r.index, end: r.index + f.length, href: m });
|
|
125
|
+
}
|
|
126
|
+
const i = [], l = document.createNodeIterator(n, NodeFilter.SHOW_ELEMENT, (r) => r.nodeName.toLowerCase() === "a" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP);
|
|
127
|
+
for (let r = l.nextNode(); r; r = l.nextNode()) {
|
|
128
|
+
const f = r, m = $e(f.getAttribute("href"));
|
|
129
|
+
if (m) {
|
|
130
|
+
const c = I(f);
|
|
131
|
+
i.push({ ...W(n, c), href: m });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return lt([...e, ...i]);
|
|
135
|
+
}
|
|
136
|
+
function it(n) {
|
|
137
|
+
var a;
|
|
138
|
+
n.normalize();
|
|
139
|
+
const e = document.createNodeIterator(n, NodeFilter.SHOW_ELEMENT);
|
|
140
|
+
for (let s = e.nextNode(); s; s = e.nextNode())
|
|
141
|
+
s !== n && s.nodeName.toLowerCase() !== "link" && (s.hasChildNodes() || (s.nodeName.toLowerCase() === "br" && s.parentNode.replaceChild(document.createTextNode(`
|
|
142
|
+
`), s), (a = s.parentNode) == null || a.removeChild(s)));
|
|
143
|
+
n.normalize(), n.hasChildNodes() || n.append(document.createElement("br"));
|
|
144
|
+
}
|
|
145
|
+
function ee(n) {
|
|
146
|
+
var s;
|
|
147
|
+
if (n.innerHTML === "<br>") return n;
|
|
148
|
+
n.normalize();
|
|
149
|
+
const e = at(n), a = Z(n);
|
|
150
|
+
return ((s = a.lastChild) == null ? void 0 : s.nodeName.toLowerCase()) === "link" && a.append(document.createTextNode(" ")), n.replaceChildren(a), e.forEach((i) => {
|
|
151
|
+
const l = U(n, i), r = l.extractContents(), f = document.createElement("a");
|
|
152
|
+
f.setAttribute("href", i.href), f.append(r), l.insertNode(f);
|
|
153
|
+
}), it(n), n;
|
|
154
|
+
}
|
|
155
|
+
function me(n, e, a) {
|
|
156
|
+
var v, h;
|
|
157
|
+
if (typeof a == "string") {
|
|
158
|
+
if (!ie(n, e)) throw new Error("Range out of bounds");
|
|
159
|
+
const S = new DOMParser().parseFromString(a, "text/html").body;
|
|
160
|
+
a = document.createDocumentFragment(), a.append(...S.childNodes);
|
|
161
|
+
}
|
|
162
|
+
const s = W(n, e), i = ((v = n.textContent) == null ? void 0 : v.length) || 0, l = I(n), r = document.createRange();
|
|
163
|
+
r.setStart(l.startContainer, l.startOffset), r.setEnd(e.startContainer, e.startOffset);
|
|
164
|
+
const f = document.createRange();
|
|
165
|
+
f.setStart(e.endContainer, e.endOffset), f.setEnd(l.endContainer, l.endOffset);
|
|
166
|
+
const m = r.extractContents(), c = f.extractContents();
|
|
167
|
+
n.replaceChildren(), n.append(m, a, c), ee(n);
|
|
168
|
+
const g = ((h = n.textContent) == null ? void 0 : h.length) || 0;
|
|
169
|
+
return { ...s, end: s.end + g - i };
|
|
170
|
+
}
|
|
171
|
+
function Ce(n) {
|
|
172
|
+
const e = document.getSelection();
|
|
173
|
+
if (!(e != null && e.rangeCount)) return null;
|
|
174
|
+
const a = e.getRangeAt(0);
|
|
175
|
+
if (!ie(n, a)) return null;
|
|
176
|
+
const s = e.isCollapsed ? !1 : e.anchorNode === e.focusNode ? e.anchorOffset > e.focusOffset : e.anchorNode.compareDocumentPosition(e.focusNode) === Node.DOCUMENT_POSITION_PRECEDING;
|
|
177
|
+
return Object.assign(a, { backwards: s });
|
|
178
|
+
}
|
|
179
|
+
function he(n) {
|
|
180
|
+
const e = Ce(n);
|
|
181
|
+
return e ? W(n, e) : null;
|
|
182
|
+
}
|
|
183
|
+
function ge(n, e, a = !1) {
|
|
184
|
+
a && (e = { start: e.end, end: e.end });
|
|
185
|
+
const s = U(n, e), { startContainer: i, startOffset: l, endContainer: r, endOffset: f, backwards: m } = s, c = document.getSelection();
|
|
186
|
+
return c && (m ? c.setBaseAndExtent(r, f, i, l) : c.setBaseAndExtent(i, l, r, f)), s;
|
|
187
|
+
}
|
|
188
|
+
function ae(n, e, a) {
|
|
189
|
+
const s = I(n);
|
|
190
|
+
if (!ie(n, e)) return null;
|
|
191
|
+
let i = e.commonAncestorContainer;
|
|
192
|
+
for (; i && s.intersectsNode(i); ) {
|
|
193
|
+
if (i.nodeType === Node.ELEMENT_NODE && i.nodeName.toLowerCase() === a)
|
|
194
|
+
return i;
|
|
195
|
+
i = i.parentNode;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
function Pe(n, e, a, s = {}) {
|
|
200
|
+
if (e.collapsed || !ie(n, e)) return;
|
|
201
|
+
const i = ae(n, e, a), l = i ? rt(i) : I(e.commonAncestorContainer), r = document.createRange();
|
|
202
|
+
r.setStart(l.startContainer, l.startOffset), r.setEnd(e.startContainer, e.startOffset);
|
|
203
|
+
const f = document.createRange();
|
|
204
|
+
f.setStart(e.endContainer, e.endOffset), f.setEnd(l.endContainer, l.endOffset);
|
|
205
|
+
const m = f.extractContents(), c = e.extractContents(), g = r.extractContents();
|
|
206
|
+
l.deleteContents();
|
|
207
|
+
const v = document.createDocumentFragment();
|
|
208
|
+
if (v.append(g), i)
|
|
209
|
+
v.append(c);
|
|
210
|
+
else {
|
|
211
|
+
const h = document.createElement(a);
|
|
212
|
+
Object.entries(s).forEach((S) => h.setAttribute(...S)), h.append(c), v.append(h);
|
|
213
|
+
}
|
|
214
|
+
v.append(m), l.insertNode(v), ee(n);
|
|
215
|
+
}
|
|
216
|
+
const ut = {
|
|
217
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
218
|
+
fill: "currentColor",
|
|
219
|
+
viewBox: "0 -960 960 960"
|
|
220
|
+
};
|
|
221
|
+
function ct(n, e) {
|
|
222
|
+
return w(), y("svg", ut, e[0] || (e[0] = [
|
|
223
|
+
x("path", { d: "M272-200v-560h221q65 0 120 40t55 111q0 51-23 78.5T602-491q25 11 55.5 41t30.5 90q0 89-65 124.5T501-200zm121-112h104q48 0 58.5-24.5T566-372t-10.5-35.5T494-432H393zm0-228h93q33 0 48-17t15-38q0-24-17-39t-44-15h-95z" }, null, -1)
|
|
224
|
+
]));
|
|
225
|
+
}
|
|
226
|
+
const dt = { render: ct }, ft = {
|
|
227
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
228
|
+
fill: "currentColor",
|
|
229
|
+
viewBox: "0 -960 960 960"
|
|
230
|
+
};
|
|
231
|
+
function pt(n, e) {
|
|
232
|
+
return w(), y("svg", ft, e[0] || (e[0] = [
|
|
233
|
+
x("path", { d: "M200-200v-100h160l120-360H320v-100h400v100H580L460-300h140v100z" }, null, -1)
|
|
234
|
+
]));
|
|
235
|
+
}
|
|
236
|
+
const vt = { render: pt }, mt = {
|
|
237
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
238
|
+
fill: "currentColor",
|
|
239
|
+
viewBox: "0 -960 960 960"
|
|
240
|
+
};
|
|
241
|
+
function ht(n, e) {
|
|
242
|
+
return w(), y("svg", mt, e[0] || (e[0] = [
|
|
243
|
+
x("path", { d: "M440-280H280q-83 0-141.5-58.5T80-480t58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85 35 85 85 35h160zM320-440v-80h320v80zm200 160v-80h160q50 0 85-35t35-85-35-85-85-35H520v-80h160q83 0 141.5 58.5T880-480t-58.5 141.5T680-280z" }, null, -1)
|
|
244
|
+
]));
|
|
245
|
+
}
|
|
246
|
+
const Ae = { render: ht }, gt = {
|
|
247
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
248
|
+
fill: "currentColor",
|
|
249
|
+
viewBox: "0 -960 960 960"
|
|
250
|
+
};
|
|
251
|
+
function bt(n, e) {
|
|
252
|
+
return w(), y("svg", gt, e[0] || (e[0] = [
|
|
253
|
+
x("path", { d: "M480-80q-83 0-156-31.5T197-197t-85.5-127T80-480t31.5-156T197-763t127-85.5T480-880t156 31.5T763-763t85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480t58.5-141.5T480-680t141.5 58.5T680-480v58q0 26 17 44t43 18 43-18 17-44v-58q0-134-93-227t-227-93-227 93-93 227 93 227 227 93h200v80zm0-280q50 0 85-35t35-85-35-85-85-35-85 35-35 85 35 85 85 35" }, null, -1)
|
|
254
|
+
]));
|
|
255
|
+
}
|
|
256
|
+
const Ct = { render: bt }, xt = {
|
|
257
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
258
|
+
fill: "currentColor",
|
|
259
|
+
viewBox: "0 -960 960 960"
|
|
260
|
+
};
|
|
261
|
+
function wt(n, e) {
|
|
262
|
+
return w(), y("svg", xt, e[0] || (e[0] = [
|
|
263
|
+
x("path", { d: "M792-443 176-183q-20 8-38-3.5T120-220v-520q0-22 18-33.5t38-3.5l616 260q25 11 25 37t-25 37M200-280l474-200-474-200v140l240 60-240 60zm0 0v-400z" }, null, -1)
|
|
264
|
+
]));
|
|
265
|
+
}
|
|
266
|
+
const yt = { render: wt }, Nt = {
|
|
267
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
268
|
+
fill: "currentColor",
|
|
269
|
+
viewBox: "0 -960 960 960"
|
|
270
|
+
};
|
|
271
|
+
function Tt(n, e) {
|
|
272
|
+
return w(), y("svg", Nt, e[0] || (e[0] = [
|
|
273
|
+
x("path", { d: "M280-160v-520H80v-120h520v120H400v520zm360 0v-320H520v-120h360v120H760v320z" }, null, -1)
|
|
274
|
+
]));
|
|
275
|
+
}
|
|
276
|
+
const Et = { render: Tt }, Rt = ["onMousedown"], kt = { class: "-jte-mentions-entry" }, St = { class: "-jte-mentions-ref" }, Ot = {
|
|
277
|
+
key: 0,
|
|
278
|
+
class: "-jte-mentions-waiting"
|
|
279
|
+
}, jt = {
|
|
280
|
+
key: 1,
|
|
281
|
+
class: "-jte-mentions-none"
|
|
282
|
+
}, Lt = {
|
|
283
|
+
key: 0,
|
|
284
|
+
class: "-jte-placeholder"
|
|
285
|
+
}, Mt = ["contenteditable"], Dt = { class: "-jte-toolbox" }, $t = /* @__PURE__ */ Qe({
|
|
286
|
+
name: "JuitTinyEditor",
|
|
287
|
+
__name: "editor",
|
|
288
|
+
props: /* @__PURE__ */ je({
|
|
289
|
+
/** A flag indicating if content can be edited (default: `true`) */
|
|
290
|
+
editable: {
|
|
291
|
+
type: Boolean,
|
|
292
|
+
required: !1,
|
|
293
|
+
default: !0
|
|
294
|
+
},
|
|
295
|
+
/** A flag indicating whether dark mode is enabled or not */
|
|
296
|
+
dark: {
|
|
297
|
+
type: Boolean,
|
|
298
|
+
required: !1,
|
|
299
|
+
default: !1
|
|
300
|
+
},
|
|
301
|
+
/** A placeholder for when no text is available */
|
|
302
|
+
placeholder: {
|
|
303
|
+
type: String,
|
|
304
|
+
required: !1,
|
|
305
|
+
default: ""
|
|
306
|
+
},
|
|
307
|
+
/** A list of mentions to be used in the editor */
|
|
308
|
+
mentions: {
|
|
309
|
+
type: Object,
|
|
310
|
+
required: !1,
|
|
311
|
+
default: () => ({})
|
|
312
|
+
}
|
|
313
|
+
}, {
|
|
314
|
+
modelValue: {
|
|
315
|
+
type: String,
|
|
316
|
+
required: !1,
|
|
317
|
+
default: ""
|
|
318
|
+
},
|
|
319
|
+
modelModifiers: {}
|
|
320
|
+
}),
|
|
321
|
+
emits: /* @__PURE__ */ je(["mention", "submit"], ["update:modelValue"]),
|
|
322
|
+
setup(n, { emit: e }) {
|
|
323
|
+
const a = Ye(n, "modelValue"), s = n;
|
|
324
|
+
T(() => s.dark, (o) => {
|
|
325
|
+
var t;
|
|
326
|
+
(t = i.value) == null || t.style.setProperty("color-scheme", o ? "dark light" : "light dark");
|
|
327
|
+
});
|
|
328
|
+
const i = q(null), l = q(null), r = q(null), f = q(null), m = q(null), c = q(null), g = E(""), v = E(0), h = q(null), S = H(() => {
|
|
329
|
+
var o;
|
|
330
|
+
return ((o = h.value) == null ? void 0 : o.toString()) || "";
|
|
331
|
+
}), xe = H(() => !!s.mentions), D = H(() => {
|
|
332
|
+
const o = S.value.substring(1).toLowerCase();
|
|
333
|
+
return s.mentions ? Object.entries(s.mentions).filter(([t, p]) => p.toLowerCase().startsWith(o)).sort(([t, p], [u, b]) => p.localeCompare(b)).map(([t, p]) => ({ name: t, value: p })) : [];
|
|
334
|
+
}), K = E(null), G = E(null), te = E(null), _ = H(() => K.value || G.value);
|
|
335
|
+
T(K, (o) => o && (te.value = o));
|
|
336
|
+
const ne = E(""), O = E(""), Ie = H(() => {
|
|
337
|
+
if (!O.value) return !0;
|
|
338
|
+
try {
|
|
339
|
+
return new URL(O.value), !0;
|
|
340
|
+
} catch {
|
|
341
|
+
return !1;
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
T([ne, O], ([o, t]) => {
|
|
345
|
+
_.value && (_.value.textContent = o, _.value.setAttribute("href", t), $());
|
|
346
|
+
});
|
|
347
|
+
const ue = E(null), ce = E(null), oe = E(0), _e = H(() => "." + ".".repeat(oe.value % 5));
|
|
348
|
+
let X = null;
|
|
349
|
+
const B = E("");
|
|
350
|
+
T(c, (o, t) => {
|
|
351
|
+
t !== null && o !== null && (t.startContainer == o.startContainer && t.startOffset == o.startOffset && t.endContainer == o.endContainer && t.endOffset == o.endOffset && (t == null ? void 0 : t.backwards) == (o == null ? void 0 : o.backwards) || (B.value = ""));
|
|
352
|
+
});
|
|
353
|
+
const F = [];
|
|
354
|
+
function de(o, t) {
|
|
355
|
+
return c.value ? t ? o && B.value === t ? "" : o || B.value === t ? "-jte-active" : "" : o ? "-jte-active" : c.value.collapsed ? "-jte-disabled" : "" : "-jte-disabled";
|
|
356
|
+
}
|
|
357
|
+
function $() {
|
|
358
|
+
if (!l.value) return void (g.value = "");
|
|
359
|
+
const o = l.value.innerHTML;
|
|
360
|
+
g.value = o === "<br>" ? "" : (
|
|
361
|
+
// edge case: empty editor
|
|
362
|
+
o.replaceAll(" ", " ").trimEnd()
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
function fe() {
|
|
366
|
+
var o;
|
|
367
|
+
if (l.value && ((o = F[F.length - 1]) == null ? void 0 : o.content) !== g.value) {
|
|
368
|
+
const t = he(l.value) || { start: 0, end: 0 };
|
|
369
|
+
F.push({ ...t, content: g.value }), F.length > 100 && F.shift();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function Fe() {
|
|
373
|
+
if (!l.value || !c.value) return;
|
|
374
|
+
const o = F.pop();
|
|
375
|
+
if (!o) return;
|
|
376
|
+
const t = new DOMParser().parseFromString(o.content, "text/html").body;
|
|
377
|
+
l.value.replaceChildren(...t.childNodes), ge(l.value, o);
|
|
378
|
+
}
|
|
379
|
+
function we() {
|
|
380
|
+
var o;
|
|
381
|
+
if (h.value && ((o = c.value) != null && o.collapsed)) {
|
|
382
|
+
const t = h.value.cloneRange();
|
|
383
|
+
if (t.setEnd(c.value.startContainer, c.value.startOffset), t.toString().match(/^@[^\r\n]*$/)) {
|
|
384
|
+
h.value = t;
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
h.value = null;
|
|
389
|
+
}
|
|
390
|
+
function ye(o, t) {
|
|
391
|
+
if (!l.value || !h.value) return;
|
|
392
|
+
const p = l.value;
|
|
393
|
+
fe();
|
|
394
|
+
const u = document.createDocumentFragment(), b = document.createElement("link");
|
|
395
|
+
b.setAttribute("rel", "mention"), b.setAttribute("name", o), b.setAttribute("title", t), u.append(b, " ");
|
|
396
|
+
const d = me(p, h.value, u);
|
|
397
|
+
z(d, !0), h.value = null, $();
|
|
398
|
+
}
|
|
399
|
+
function J(o, t = {}) {
|
|
400
|
+
if (!l.value || !c.value) return;
|
|
401
|
+
const p = l.value;
|
|
402
|
+
if (c.value.collapsed) {
|
|
403
|
+
B.value = o, p.focus();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
fe();
|
|
407
|
+
const u = W(p, c.value);
|
|
408
|
+
Pe(p, c.value, o, t), z(u), $();
|
|
409
|
+
}
|
|
410
|
+
function ze() {
|
|
411
|
+
var u;
|
|
412
|
+
if (!l.value || !((u = c.value) != null && u.collapsed)) return;
|
|
413
|
+
const o = l.value, t = W(o, c.value), p = document.createTextNode("@");
|
|
414
|
+
c.value.insertNode(p), t.end += 1, h.value = U(o, t), t.start += 1, z(t);
|
|
415
|
+
}
|
|
416
|
+
function z(o, t) {
|
|
417
|
+
if (!l.value) return;
|
|
418
|
+
const p = l.value;
|
|
419
|
+
c.value = ge(p, o, t), p.focus();
|
|
420
|
+
}
|
|
421
|
+
function He() {
|
|
422
|
+
g.value && Te("submit", g.value);
|
|
423
|
+
}
|
|
424
|
+
function Ve(o) {
|
|
425
|
+
if (o.metaKey || o.ctrlKey)
|
|
426
|
+
switch (o.key) {
|
|
427
|
+
case "b":
|
|
428
|
+
J("b"), o.preventDefault();
|
|
429
|
+
break;
|
|
430
|
+
case "i":
|
|
431
|
+
J("i"), o.preventDefault();
|
|
432
|
+
break;
|
|
433
|
+
case "z":
|
|
434
|
+
Fe(), o.preventDefault();
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
else if (h.value)
|
|
438
|
+
switch (o.key) {
|
|
439
|
+
case "Home":
|
|
440
|
+
v.value = 0, o.preventDefault();
|
|
441
|
+
break;
|
|
442
|
+
case "ArrowUp":
|
|
443
|
+
v.value = Math.max(v.value - 1, 0), o.preventDefault();
|
|
444
|
+
break;
|
|
445
|
+
case "PageUp":
|
|
446
|
+
v.value = Math.max(v.value - 4, 0), o.preventDefault();
|
|
447
|
+
break;
|
|
448
|
+
case "ArrowDown":
|
|
449
|
+
v.value = Math.min(v.value + 1, D.value.length - 1), o.preventDefault();
|
|
450
|
+
break;
|
|
451
|
+
case "PageDown":
|
|
452
|
+
v.value = Math.min(v.value + 4, D.value.length - 1), o.preventDefault();
|
|
453
|
+
break;
|
|
454
|
+
case "End":
|
|
455
|
+
v.value = D.value.length - 1, o.preventDefault();
|
|
456
|
+
break;
|
|
457
|
+
case "Escape":
|
|
458
|
+
h.value = null, o.preventDefault();
|
|
459
|
+
break;
|
|
460
|
+
case "Enter": {
|
|
461
|
+
const t = D.value[v.value];
|
|
462
|
+
t && ye(t.name, t.value), o.preventDefault();
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function Ue(o) {
|
|
468
|
+
if (o.preventDefault(), !l.value || !c.value) return;
|
|
469
|
+
const t = o.clipboardData;
|
|
470
|
+
if (!t) return;
|
|
471
|
+
const p = t.getData("text/html") || t.getData("text/plain");
|
|
472
|
+
if (!p) return;
|
|
473
|
+
const u = me(l.value, c.value, p);
|
|
474
|
+
z(u, !0), $();
|
|
475
|
+
}
|
|
476
|
+
function We(o) {
|
|
477
|
+
if (l.value && c.value && (fe(), !c.value.collapsed && (o.inputType === "deleteContentBackward" || o.inputType === "deleteContentForward"))) {
|
|
478
|
+
o.preventDefault(), o.stopPropagation();
|
|
479
|
+
const t = me(l.value, c.value, "");
|
|
480
|
+
z(t, !0), $();
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function Ke(o) {
|
|
484
|
+
var b;
|
|
485
|
+
const t = l.value;
|
|
486
|
+
if (!t) return;
|
|
487
|
+
const p = h.value ? W(t, h.value) : null, u = he(t);
|
|
488
|
+
if (B.value && u && u.start === u.end && o.inputType === "insertText" && o.data !== "@" && !h.value) {
|
|
489
|
+
const d = U(t, { start: u.start - 1, end: u.end });
|
|
490
|
+
Pe(t, d, B.value);
|
|
491
|
+
}
|
|
492
|
+
if (B.value = "", ee(t), p && (h.value = U(t, p)), u && (t.innerHTML === "<br>" ? ((b = document.getSelection()) == null || b.setBaseAndExtent(t, 0, t, 0), t.focus()) : z(u)), !_.value && !h.value && o.data === "@" && o.inputType === "insertText") {
|
|
493
|
+
const d = document.getSelection();
|
|
494
|
+
if (!(d != null && d.anchorNode)) return;
|
|
495
|
+
const C = be(t, d.anchorNode, d.anchorOffset);
|
|
496
|
+
if (C.match(/(\s|^)@$/)) {
|
|
497
|
+
const N = C.length === 1 ? 0 : C.length - 1, j = C.length, R = { start: N, end: j };
|
|
498
|
+
h.value = U(t, R);
|
|
499
|
+
}
|
|
500
|
+
} else
|
|
501
|
+
we();
|
|
502
|
+
$();
|
|
503
|
+
}
|
|
504
|
+
function Ne(o) {
|
|
505
|
+
i.value && Ce(i.value) || (K.value = null, te.value = null, G.value = null), c.value = l.value ? Ce(l.value) : null, we();
|
|
506
|
+
}
|
|
507
|
+
Ze(() => {
|
|
508
|
+
if (document.addEventListener("selectionchange", Ne), !i.value) throw new Error("Root not referenced");
|
|
509
|
+
if (!l.value) throw new Error("Editor not referenced");
|
|
510
|
+
if (!r.value) throw new Error("Popup not referenced");
|
|
511
|
+
if (!f.value) throw new Error("List not referenced");
|
|
512
|
+
if (!m.value) throw new Error("Popup not referenced");
|
|
513
|
+
const o = i.value, t = l.value, p = r.value, u = f.value, b = m.value;
|
|
514
|
+
T(g, (d) => {
|
|
515
|
+
d !== a.value && (a.value = d);
|
|
516
|
+
}), T(a, (d) => {
|
|
517
|
+
if (d === g.value) return;
|
|
518
|
+
const C = new DOMParser().parseFromString(d, "text/html").body;
|
|
519
|
+
ee(C), t.replaceChildren(...C.childNodes), $();
|
|
520
|
+
}, { immediate: !0 }), T(xe, (d) => {
|
|
521
|
+
oe.value = 0, d ? X && clearInterval(X) : X = setInterval(() => oe.value = oe.value + 1, 200);
|
|
522
|
+
}, { immediate: !0 }), T(_, (d) => {
|
|
523
|
+
if (ne.value = (d == null ? void 0 : d.textContent) || "", O.value = (d == null ? void 0 : d.getAttribute("href")) || "", O.value === "http://__placeholder__/" && (d == null || d.setAttribute("href", ""), O.value = ""), !d) {
|
|
524
|
+
const C = he(t);
|
|
525
|
+
ee(t), C && ge(t, C), b.style.display = "none", b.style.top = "0", b.style.left = "0";
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
Le(() => {
|
|
529
|
+
b.style.display = "flex";
|
|
530
|
+
const C = o.getBoundingClientRect(), N = d.getBoundingClientRect(), j = t.getBoundingClientRect(), R = getComputedStyle(b), k = parseInt(R.width), L = parseInt(R.height), re = N.top - L - 6 > j.top, se = N.bottom + L + 3 < j.bottom, Q = N.left + k < j.right;
|
|
531
|
+
b.style.top = !se && re ? `${N.top - L - C.top - 6}px` : (
|
|
532
|
+
// over the link
|
|
533
|
+
`${N.bottom - C.top + 3}px`
|
|
534
|
+
), b.style.left = Q ? `${N.left - C.left}px` : (
|
|
535
|
+
// right of the link
|
|
536
|
+
`${j.right - k - C.left}px`
|
|
537
|
+
);
|
|
538
|
+
});
|
|
539
|
+
}, { immediate: !0 }), T([v, h, D], ([d, C, N], [j = -1]) => {
|
|
540
|
+
if (d >= N.length && (d = Math.max(N.length - 1, 0)), v.value = d, !!p && u) {
|
|
541
|
+
if (!C) {
|
|
542
|
+
v.value = 0, p.style.display = "none", p.style.top = "0", p.style.left = "0", u.scrollTop = 0;
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
Le(() => {
|
|
546
|
+
p.style.display = "block";
|
|
547
|
+
const R = o.getBoundingClientRect(), k = C.getBoundingClientRect(), L = t.getBoundingClientRect(), re = getComputedStyle(p), se = parseInt(re.width), Q = parseInt(re.height), Ge = k.top - Q - 6 > L.top, Xe = k.bottom + Q + 3 < L.bottom, Je = k.left + se < L.right;
|
|
548
|
+
p.style.top = !Xe && Ge ? `${k.top - Q - R.top - 6}px` : (
|
|
549
|
+
// over the link
|
|
550
|
+
`${k.bottom - R.top + 3}px`
|
|
551
|
+
), p.style.left = Je ? `${k.left - R.left}px` : (
|
|
552
|
+
// right of the link
|
|
553
|
+
`${L.right - se - R.left}px`
|
|
554
|
+
);
|
|
555
|
+
const pe = u.querySelectorAll("li")[v.value];
|
|
556
|
+
if (!pe) return;
|
|
557
|
+
const Ee = getComputedStyle(pe), Re = parseFloat(Ee.getPropertyValue("border-top-width")) || 0, ke = parseFloat(Ee.getPropertyValue("border-bottom-width")) || 0, Y = u.getBoundingClientRect(), ve = pe.getBoundingClientRect(), le = ve.top - Y.top, Se = ve.bottom - Y.bottom, Oe = ve.bottom - Y.top;
|
|
558
|
+
d > j ? Oe > Y.height ? u.scrollTop += Se - ke : le < 0 && (u.scrollTop += le + Re) : le < 0 ? u.scrollTop += le + Re : Se > 0 && (u.scrollTop += Oe - Y.height - ke);
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}, { immediate: !0 }), T(c, (d) => {
|
|
562
|
+
d ? (K.value = ae(t, d, "a"), ue.value = ae(t, d, "b"), ce.value = ae(t, d, "i")) : (K.value = null, ue.value = null, ce.value = null);
|
|
563
|
+
}, { immediate: !0 });
|
|
564
|
+
}), et(() => {
|
|
565
|
+
document.removeEventListener("selectionchange", Ne), X && clearInterval(X);
|
|
566
|
+
});
|
|
567
|
+
const Te = e;
|
|
568
|
+
return T(S, (o) => Te("mention", o.substring(1)), { immediate: !0 }), (o, t) => {
|
|
569
|
+
var p;
|
|
570
|
+
return w(), y("div", {
|
|
571
|
+
class: M(["juit-tiny-edit", { "-jte-editable": n.editable, "-jte-dark": n.dark }])
|
|
572
|
+
}, [
|
|
573
|
+
x("div", {
|
|
574
|
+
ref_key: "rootRef",
|
|
575
|
+
ref: i,
|
|
576
|
+
class: "-jte-root"
|
|
577
|
+
}, [
|
|
578
|
+
x("div", {
|
|
579
|
+
ref_key: "mentionsPopupRef",
|
|
580
|
+
ref: r,
|
|
581
|
+
class: "-jte-mentions-popup"
|
|
582
|
+
}, [
|
|
583
|
+
x("ul", {
|
|
584
|
+
ref_key: "mentionsListRef",
|
|
585
|
+
ref: f
|
|
586
|
+
}, [
|
|
587
|
+
(w(!0), y(tt, null, nt(D.value, (u, b) => (w(), y("li", {
|
|
588
|
+
key: b,
|
|
589
|
+
class: M({ "-jte-mentions-selected": v.value === b }),
|
|
590
|
+
onMousedown: (d) => ye(u.name, u.value)
|
|
591
|
+
}, [
|
|
592
|
+
x("div", kt, [
|
|
593
|
+
ot(V(u.value) + " ", 1),
|
|
594
|
+
x("span", St, V(u.name), 1)
|
|
595
|
+
])
|
|
596
|
+
], 42, Rt))), 128))
|
|
597
|
+
], 512),
|
|
598
|
+
xe.value ? D.value.length ? Me("", !0) : (w(), y("div", jt, V(S.value), 1)) : (w(), y("div", Ot, V(S.value) + V(_e.value), 1))
|
|
599
|
+
], 512),
|
|
600
|
+
x("div", {
|
|
601
|
+
ref_key: "linksPopupRef",
|
|
602
|
+
ref: m,
|
|
603
|
+
class: "-jte-links-popup",
|
|
604
|
+
onClick: t[2] || (t[2] = (u) => G.value = te.value),
|
|
605
|
+
onFocusin: t[3] || (t[3] = (u) => G.value = te.value)
|
|
606
|
+
}, [
|
|
607
|
+
x("div", null, [
|
|
608
|
+
P(A(Et), { class: "-jte-icon" }),
|
|
609
|
+
De(x("input", {
|
|
610
|
+
"onUpdate:modelValue": t[0] || (t[0] = (u) => ne.value = u),
|
|
611
|
+
type: "text"
|
|
612
|
+
}, null, 512), [
|
|
613
|
+
[Be, ne.value]
|
|
614
|
+
])
|
|
615
|
+
]),
|
|
616
|
+
x("div", {
|
|
617
|
+
class: M({ "-jte-error": !Ie.value })
|
|
618
|
+
}, [
|
|
619
|
+
P(A(Ae), { class: "-jte-icon" }),
|
|
620
|
+
De(x("input", {
|
|
621
|
+
"onUpdate:modelValue": t[1] || (t[1] = (u) => O.value = u),
|
|
622
|
+
type: "text"
|
|
623
|
+
}, null, 512), [
|
|
624
|
+
[Be, O.value]
|
|
625
|
+
])
|
|
626
|
+
], 2)
|
|
627
|
+
], 544),
|
|
628
|
+
n.placeholder && !g.value ? (w(), y("div", Lt, V(n.placeholder), 1)) : Me("", !0),
|
|
629
|
+
x("div", {
|
|
630
|
+
ref_key: "editorRef",
|
|
631
|
+
ref: l,
|
|
632
|
+
class: "-jte-editor",
|
|
633
|
+
contenteditable: n.editable ? "plaintext-only" : "false",
|
|
634
|
+
onBeforeinput: t[4] || (t[4] = (u) => We(u)),
|
|
635
|
+
onInput: t[5] || (t[5] = (u) => Ke(u)),
|
|
636
|
+
onKeydown: t[6] || (t[6] = (u) => Ve(u)),
|
|
637
|
+
onPaste: t[7] || (t[7] = (u) => Ue(u)),
|
|
638
|
+
onFocusin: t[8] || (t[8] = (u) => G.value = null)
|
|
639
|
+
}, null, 40, Mt),
|
|
640
|
+
x("div", Dt, [
|
|
641
|
+
P(A(Ct), {
|
|
642
|
+
class: M(["-jte-icon -jte-icon-mention", { "-jte-disabled": !((p = c.value) != null && p.collapsed) }]),
|
|
643
|
+
onClick: ze
|
|
644
|
+
}, null, 8, ["class"]),
|
|
645
|
+
P(A(dt), {
|
|
646
|
+
class: M(["-jte-icon -jte-icon-bold", de(ue.value, "b")]),
|
|
647
|
+
onClick: t[9] || (t[9] = (u) => J("b"))
|
|
648
|
+
}, null, 8, ["class"]),
|
|
649
|
+
P(A(vt), {
|
|
650
|
+
class: M(["-jte-icon -jte-icon-italic", de(ce.value, "i")]),
|
|
651
|
+
onClick: t[10] || (t[10] = (u) => J("i"))
|
|
652
|
+
}, null, 8, ["class"]),
|
|
653
|
+
P(A(Ae), {
|
|
654
|
+
class: M(["-jte-icon -jte-icon-href", de(_.value)]),
|
|
655
|
+
onClick: t[11] || (t[11] = (u) => J("a", { href: "http://__placeholder__/" }))
|
|
656
|
+
}, null, 8, ["class"]),
|
|
657
|
+
P(A(yt), {
|
|
658
|
+
class: M(["-jte-icon -jte-icon-send", { "-jte-disabled": !g.value, "-jte-active": !!g.value }]),
|
|
659
|
+
onClick: He
|
|
660
|
+
}, null, 8, ["class"])
|
|
661
|
+
])
|
|
662
|
+
], 512)
|
|
663
|
+
], 2);
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
export {
|
|
668
|
+
$t as default
|
|
669
|
+
};
|
|
670
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../lib/range.ts","../lib/sanitize.ts","../lib/replace.ts","../lib/selection.ts","../lib/tags.ts","../lib/svg/bold.svg","../lib/svg/italic.svg","../lib/svg/link.svg","../lib/svg/mention.svg","../lib/svg/send.svg","../lib/svg/text.svg","../lib/editor.vue"],"sourcesContent":["/** A DOM {@link Range} with an optional flag indicating its direction */\nexport interface DirectedRange extends Range {\n /** If `end` should come before `start` (for user selection) */\n backwards?: boolean\n}\n\n/** Offsets for a selected region (in characters) */\nexport interface Offsets {\n /** The start character position */\n start: number\n /** The end character position */\n end: number\n /** If the selection is backwards */\n backwards?: boolean\n}\n\n/**\n * Figure out if the specified `needle` {@link Range} is entirely enclosed in\n * the `haystack` {@link Range} or {@link Node} and return the `haystack` if\n * true.\n *\n * @param haystack The outer range\n * @param needle The inner range\n * @returns The `needle` if it is entirely enclosed in the `haystack`\n */\nexport function containsRange(\n haystack: Node | Range,\n needle: Range,\n): Range | null {\n if (haystack instanceof Node) haystack = rangeFromContents(haystack)\n\n // Check if the selection is entirely within the tag\n const inRange =\n haystack.isPointInRange(needle.startContainer, needle.startOffset) &&\n haystack.isPointInRange(needle.endContainer, needle.endOffset)\n\n // Return the needle if it is within the haystack\n return inRange ? needle : null\n}\n\n/**\n * Create a new {@link Range} object that selects the specified node\n *\n * @param node The node to select\n * @returns A new {@link Range} created with `selectNode(...)`\n */\nexport function rangeFromNode(node: Node): Range {\n const range = document.createRange()\n range.selectNode(node)\n return range\n}\n\n/**\n * Create a new {@link Range} object that selects the _contents_ of the\n * specified node\n *\n * @param node The node to select\n * @returns A new {@link Range} created with `selectNodeContents(...)`\n */\nexport function rangeFromContents(node: Node): Range {\n const range = document.createRange()\n range.selectNodeContents(node)\n return range\n}\n\n/**\n * Retrieve the characters contained in a parent {@link Node} up to the\n * specified {@link Node} and offset.\n */\nexport function getCharacters(\n parent: Node,\n child: Node,\n offset: number,\n): string {\n const parentRange = rangeFromContents(parent)\n\n if (! parentRange.isPointInRange(child, offset)) {\n throw new Error('Range out of bounds')\n }\n\n const range = document.createRange()\n range.setStart(parent, 0)\n range.setEnd(child, offset)\n\n return range.toString()\n}\n\n/**\n * Retrieve the character {@link Offsets} for the specified {@link Range}.\n *\n * @param parent The container {@link Node} for the {@link Range}\n * @param range The {@link Range} to calculate offsets for\n * @returns The {@link Offsets} for the specified {@link Range}\n */\nexport function getRangeOffsets(\n parent: Node,\n range: DirectedRange,\n): Offsets {\n const result = { start: 0, end: 0, backwards: range.backwards }\n\n result.start = result.end = getCharacters(parent, range.startContainer, range.startOffset).length\n if (range.collapsed) return result\n\n result.end = getCharacters(parent, range.endContainer, range.endOffset).length\n return result\n}\n\n/**\n * Return a {@link Range} for the specified character {@link Offsets}.\n *\n * @param parent The container {@link Node} for the {@link Range}\n * @returns The {@link Range} for the specified {@link Offsets}\n */\nexport function getOffsetsRange(\n parent: Node,\n offsets: Offsets,\n): DirectedRange {\n const range: DirectedRange = rangeFromContents(parent)\n if (offsets.backwards) range.backwards = true\n let { start, end } = offsets\n\n let firstNode: Text | null = null\n let lastNode: Text | null = null\n let foundStart: boolean | string = false\n let foundEnd: boolean | string = false\n\n const iterator = document.createNodeIterator(parent, NodeFilter.SHOW_TEXT)\n for (\n let text = iterator.nextNode() as Text | null;\n text && ((start >= 0) || (end > 0));\n text = iterator.nextNode() as Text | null\n ) {\n if (! firstNode) firstNode = text\n lastNode = text\n\n const length = text.nodeValue!.length\n\n // start is always calculated at the *start* of the text node\n if ((start >= 0) && (start < length)) {\n foundStart = true\n range.setStart(text, start)\n if (start === end) {\n foundEnd = 'extra'\n range.setEnd(text, end)\n return range\n }\n }\n\n // end is always calculated at the *end* of the text node\n if ((end > 0) && (end <= length)) {\n foundEnd = true\n range.setEnd(text, end)\n if (end === start) {\n foundStart = 'extra'\n range.setStart(text, start)\n return range\n }\n }\n\n start -= length\n end -= length\n }\n\n if ((! foundStart) && (firstNode)) range.setStart(firstNode, 0)\n if ((! foundEnd) && (lastNode)) range.setEnd(lastNode, lastNode.length)\n\n return range\n}\n","import { getOffsetsRange, getRangeOffsets, rangeFromContents } from './range'\n\nimport type { Offsets } from './range'\n\n/** Tags that will be stripped from output */\nconst STRIP_TAGS = new Set([\n 'button',\n 'embed',\n 'form',\n 'iframe',\n 'input',\n 'object',\n 'script',\n 'select',\n 'style',\n 'textarea',\n])\n\n/** Parse an URL and return the normalized HREF if it's a valid HTTP url */\nfunction parseURL(string?: string | null): string | null {\n if (! string) return null\n if (! string.match(/^https?:\\/\\/[^\\s]+$/)) return null\n try {\n return new URL(string).href\n } catch {\n return null\n }\n}\n\n/**\n * Wrap a document fragment in a tag unless `noWrap` is `true`\n *\n * @param fragment The fragment to wrap\n * @param tagName The tag to wrap the fragment in\n * @param noWrap If `true`, the fragment will not be wrapped\n * @param attribues Additional attributes to add to the tag\n * @returns The wrapped fragment or the fragment itself\n */\nfunction maybeWrap(\n fragment: DocumentFragment,\n tagName: string,\n noWrap: boolean = false,\n): DocumentFragment | Element {\n if (noWrap) return fragment\n\n const tag = document.createElement(tagName)\n tag.append(fragment)\n return tag\n}\n\n/**\n * Sanitize a {@link Node}'s content for styles (bold, italic, ...)\n *\n * The `state` object is used to track the current state of the sanitizer: if\n * `bold` is `true`, then all nested `<b>` and `<strong>` tags will be stripped,\n * the same works for `italic` with `<i>` and `<em>`.\n *\n * @param fragment The fragment to sanitize\n * @param state The current state of the sanitizer\n * @returns The sanitized fragment\n */\nfunction sanitizeStyles(\n parent: Element,\n state: {\n bold?: boolean,\n italic?: boolean,\n } = {},\n): DocumentFragment {\n const result = document.createDocumentFragment()\n\n for (const node of parent.childNodes) {\n // Text nodes are cloned if they have contents\n if (node.nodeType === Node.TEXT_NODE) {\n if ((node as Text).nodeValue) result.append(node.cloneNode())\n\n // Elements are processed recursively\n } else if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element\n const tagName = element.tagName.toLowerCase()\n\n // Strip unwanted tags\n if (STRIP_TAGS.has(tagName)) continue\n\n const previous = result.lastChild?.nodeType === Node.ELEMENT_NODE ? {\n element: result.lastChild as Element,\n tagName: (result.lastChild as Element).tagName.toLowerCase(),\n } : null\n\n const child = document.createDocumentFragment()\n\n // Bold / strong\n if ((tagName === 'b') || (tagName === 'strong')) {\n const fragment = sanitizeStyles(element, { ...state, bold: true })\n if (! fragment.hasChildNodes()) continue\n\n if (previous?.tagName === 'b') previous.element.append(fragment)\n else child.append(maybeWrap(fragment, 'b', state.bold))\n\n // Italic / emphasis\n } else if ((tagName === 'i') || (tagName === 'em')) {\n const fragment = sanitizeStyles(element, { ...state, italic: true })\n if (! fragment.hasChildNodes()) continue\n\n if (previous?.tagName === 'i') previous.element.append(fragment)\n else child.append(maybeWrap(fragment, 'i', state.italic))\n\n // Paragraphs, divs, line breaks\n } else if (tagName === 'br') {\n child.append(document.createElement('br'))\n } else if ((tagName === 'div') || (tagName === 'p') || (tagName === 'pre') ||\n (tagName === 'h1') || (tagName === 'h2') || (tagName === 'h3') ||\n (tagName === 'h4') || (tagName === 'h5') || (tagName === 'h6') ||\n (tagName === 'ul') || (tagName === 'ol') || (tagName === 'li')) {\n child.append(document.createElement('br'))\n const fragment = sanitizeStyles(element, state)\n if (fragment.hasChildNodes()) {\n child.append(fragment)\n child.append(document.createElement('br'))\n }\n\n // Mentions are implemented as <link rel=\"mention\" name=\"...\" title=\"...\">\n } else if (tagName === 'link') {\n const rel = element.getAttribute('rel')\n if (rel !== 'mention') continue\n\n const name = element.getAttribute('name')\n const title = element.getAttribute('title')\n if (!(name && title)) continue\n\n const link = document.createElement('link')\n link.setAttribute('rel', 'mention')\n link.setAttribute('title', title)\n link.setAttribute('name', name)\n child.append(link)\n\n // Anything else is processed recursively (strip tag, keep contents)\n } else {\n child.append(sanitizeStyles(element, state))\n }\n\n if (child.hasChildNodes()) result.append(child)\n }\n }\n\n return result\n}\n\n/** Merge offsets with HREF (exported for tests) */\nexport function mergeOffsets(list: (Offsets & { href: string })[]): (Offsets & { href: string })[] {\n // Map all our offsets to an array of objects with an index\n const offsets = list.map((offset, i) => ({ ...offset, index: i }))\n\n // Sort by start, keep longest first, if seame, the first one has priority\n offsets.sort((a, b) => {\n return a.start < b.start ? -1 :\n a.start > b.start ? 1 :\n a.end < b.end ? 1 :\n a.end > b.end ? -1 :\n /* v8 ignore next 3 */\n a.index < b.index ? -1 :\n a.index > b.index ? 1 :\n 0\n })\n\n // Find all ranges that are nested/overlapping within another range\n const nested = new Set<Offsets>()\n\n for (let i = 0; i < offsets.length; i++) {\n const outer = offsets[i]!\n for (let j = i + 1; j < offsets.length; j++) {\n const inner = offsets[j]!\n if (nested.has(inner)) continue\n if (inner.start === inner.end) nested.add(inner)\n else if ((inner.start >= outer.start) && (inner.start < outer.end)) nested.add(inner)\n else if ((inner.end >= outer.start) && (inner.end < outer.end)) nested.add(inner)\n }\n }\n\n // All our anchor offsets\n return offsets\n // Filter out any nested/overlapping offsets\n .filter((offset) => ! nested.has(offset))\n // Join consecutive offsets with the same href\n .filter((offset, i, offsets) => {\n const previous = offsets[i - 1]\n if ((previous?.end === offset.start) && (previous.href === offset.href)) {\n previous.end = offset.end\n return false\n } else {\n return true\n }\n })\n}\n\n/**\n * Find the various links in a {@link Node}, and return their offsets and hrefs.\n *\n * @param parent The {@link Node} for which links will be sanitized\n * @returns An array of offsets and related hrefs of all links\n */\nfunction sanitizeLinks(parent: Element): (Offsets & { href: string })[] {\n // Process links parsed directly from the text ()\n const textRanges: (Offsets & { href: string })[] = []\n\n const re = /(https?:\\/\\/[^\\s]+)/gm\n const plain = parent.textContent!\n for (let match = re.exec(plain); match; match = re.exec(plain)) {\n const text = match[1]!\n const href = parseURL(text)\n if (href) textRanges.push({ start: match.index, end: match.index + text.length, href })\n }\n\n // Process links parsed from <a> elements\n const linkRanges: (Offsets & { href: string })[] = []\n\n const iterator = document.createNodeIterator(parent, NodeFilter.SHOW_ELEMENT, (node) => {\n return node.nodeName.toLowerCase() === 'a' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP\n })\n\n for (let node = iterator.nextNode(); node; node = iterator.nextNode()) {\n const element = node as Element\n\n const href = parseURL(element.getAttribute('href'))\n if (href) {\n const range = rangeFromContents(element)\n linkRanges.push({ ...getRangeOffsets(parent, range), href })\n }\n }\n\n return mergeOffsets([ ...textRanges, ...linkRanges ])\n}\n\n/**\n * Strip all empty elements out from the children of a specified {@link Node}.\n *\n * @param parent The {@link Element} to sanitize\n */\nfunction sanitizeEmpty(parent: Element): void {\n parent.normalize()\n\n const iterator = document.createNodeIterator(parent, NodeFilter.SHOW_ELEMENT)\n for (let node = iterator.nextNode(); node; node = iterator.nextNode()) {\n if (node === parent) continue // obviously skip the parent node\n if (node.nodeName.toLowerCase() === 'link') continue // mentions\n if (node.hasChildNodes()) continue // non-empty elements\n if (node.nodeName.toLowerCase() === 'br') { // keep line breaks\n node.parentNode!.replaceChild(document.createTextNode('\\n'), node)\n }\n // remove empty elements\n node.parentNode?.removeChild(node)\n }\n\n parent.normalize()\n // Edge case, empty editor\n if (! parent.hasChildNodes()) {\n parent.append(document.createElement('br'))\n }\n}\n\n/**\n * Sanitize the contents of a {@link Element} normalizing styles (bold,\n * italic, ...) and links.\n *\n * @param parent The {@link Element} to sanitize\n * @returns The same `parent` specified as an argument\n */\nexport function sanitize(parent: Element): Element {\n // Edge case: empty editor\n if (parent.innerHTML === '<br>') return parent\n\n // Cleanup before we start\n parent.normalize()\n\n // Save any and all links\n const offsets = sanitizeLinks(parent)\n\n // Sanitize the content\n const sanitized = sanitizeStyles(parent)\n // Append a space if the last child was a link (mention)\n if (sanitized.lastChild?.nodeName.toLowerCase() === 'link') {\n sanitized.append(document.createTextNode(' '))\n }\n\n // Replace our contents\n parent.replaceChildren(sanitized)\n\n // Restore the links\n offsets.forEach((offsets) => {\n const range = getOffsetsRange(parent, offsets)\n const wrapped = range.extractContents()\n const link = document.createElement('a')\n link.setAttribute('href', offsets.href)\n link.append(wrapped)\n range.insertNode(link)\n })\n\n // Cleanup any empty elements which might have been introduced\n // when restoring the links above\n sanitizeEmpty(parent)\n\n // Return the same element we were given\n return parent\n}\n","import { containsRange, getRangeOffsets, rangeFromContents } from './range'\nimport { sanitize } from './sanitize'\n\nimport type { Offsets } from './range'\n\nexport function replaceRange(\n parent: Element,\n range: Range,\n html: DocumentFragment | string,\n): Offsets {\n if (typeof html === 'string') {\n if (! containsRange(parent, range)) throw new Error('Range out of bounds')\n\n const body = new DOMParser().parseFromString(html, 'text/html').body\n html = document.createDocumentFragment()\n html.append(...body.childNodes)\n }\n\n // Save our selection offsets\n const offsets = getRangeOffsets(parent, range)\n const beforeLength = parent.textContent?.length || 0\n\n // Split into ranges (before and after)\n const parentRange = rangeFromContents(parent)\n\n const beforeRange = document.createRange()\n beforeRange.setStart(parentRange.startContainer, parentRange.startOffset)\n beforeRange.setEnd(range.startContainer, range.startOffset)\n\n const afterRange = document.createRange()\n afterRange.setStart(range.endContainer, range.endOffset)\n afterRange.setEnd(parentRange.endContainer, parentRange.endOffset)\n\n // Extract the content to keep\n const before = beforeRange.extractContents()\n const after = afterRange.extractContents()\n\n // Re-add all our content and sanitize\n parent.replaceChildren() // wipe everything\n parent.append(before, html, after)\n sanitize(parent)\n\n // Adjust our selection offsets depending on pasted data\n const afterLength = parent.textContent?.length || 0\n return { ...offsets, end: offsets.end + afterLength - beforeLength }\n}\n","import { containsRange, getOffsetsRange, getRangeOffsets } from './range'\n\nimport type { DirectedRange, Offsets } from './range'\n\n/**\n * Retrieve the current selection as a {@link DirectedRange} within a parent\n * {@link Element}\n *\n * @param parent The element defining the boundaries of accepatable selections\n * @returns The current selection as a {@link DirectedRange} if within bounds\n */\nexport function getSelectionRange(\n parent: Element,\n): DirectedRange | null {\n const selection = document.getSelection()\n if (! selection?.rangeCount) return null\n\n const selectionRange = selection.getRangeAt(0)\n if (! containsRange(parent, selectionRange)) return null\n\n const backwards =\n selection.isCollapsed ? false :\n selection.anchorNode === selection.focusNode ? selection.anchorOffset > selection.focusOffset :\n selection.anchorNode!.compareDocumentPosition(selection.focusNode!) === Node.DOCUMENT_POSITION_PRECEDING\n return Object.assign(selectionRange, { backwards })\n}\n\n/**\n * Retrieve the current selection as character {@link Offsets} within a parent\n * {@link Element}\n *\n * @param parent The element defining the boundaries of accepatable selections\n * @returns The current selection as a {@link DirectedRange} if within bounds\n */\nexport function getSelectionOffsets(\n parent: Element,\n): Offsets | null {\n const range = getSelectionRange(parent)\n return range ? getRangeOffsets(parent, range) : null\n}\n\n/**\n * Restore the current selection from character {@link Offsets} in the\n * specified {@link Element}\n *\n * @param parent The element defining the boundaries of accepatable selections\n * @param offsets The character offsets to restore\n */\nexport function restoreSelection(\n parent: Element,\n offsets: Offsets,\n collapse = false,\n): DirectedRange {\n if (collapse) offsets = { start: offsets.end, end: offsets.end }\n const range = getOffsetsRange(parent, offsets)\n const { startContainer, startOffset, endContainer, endOffset, backwards } = range\n\n // Selection can be forwards or backwards... restore correctly\n const selection = document.getSelection()\n\n if (selection) {\n if (backwards) {\n selection.setBaseAndExtent(endContainer, endOffset, startContainer, startOffset)\n } else {\n selection.setBaseAndExtent(startContainer, startOffset, endContainer, endOffset)\n }\n }\n\n return range\n}\n","import { containsRange, rangeFromContents, rangeFromNode } from './range'\nimport { sanitize } from './sanitize'\n\n/**\n * Figure out if the current selection entirely enclosed in an element\n * with the specified tag name (normally bold, italic, ...)\n *\n * @param parent The element whose contents we are searching\n * @param range The current selection (if any)\n * @param tagName The tag name to check for (e.g. 'b', 'i', ...)\n * @returns The parent node with the specified tag name if found\n */\nexport function isTagged(\n parent: Node,\n range: Range,\n tagName: string,\n): Element | null {\n // Get the parent range, and make sure the selection is within it\n const parentRange = rangeFromContents(parent)\n if (! containsRange(parent, range)) return null\n\n // Walk up from our common ancestor to to the parent to see if we find\n // an element with the specified tag name\n let ancestor: Node | null = range.commonAncestorContainer\n while (ancestor && parentRange.intersectsNode(ancestor)) {\n if ((ancestor.nodeType === Node.ELEMENT_NODE) &&\n (ancestor.nodeName.toLowerCase() === tagName)) {\n return ancestor as Element\n }\n ancestor = ancestor.parentNode\n }\n\n // Nothing found!\n return null\n}\n\n/**\n * Toggle a tag within the specified {@link Element}, around the specified\n * selection.\n *\n * @param parent The element whose contents we are modifying\n * @param range The range to toggle the tag around\n * @param tagName The tag name (e.g. 'b', 'i', ...) to toggle\n * @param attributes Any attributes to set on the tag (will be sanitized)\n */\nexport function toggleTag(\n parent: Element,\n range: Range,\n tagName: string,\n attributes: Record<string, string> = {},\n): void {\n if (range.collapsed) return\n if (! containsRange(parent, range)) return\n\n const ancestor = isTagged(parent, range, tagName)\n\n const ancestorRange = ancestor ?\n rangeFromNode(ancestor) :\n rangeFromContents(range.commonAncestorContainer)\n\n const before = document.createRange()\n before.setStart(ancestorRange.startContainer, ancestorRange.startOffset)\n before.setEnd(range.startContainer, range.startOffset)\n\n const after = document.createRange()\n after.setStart(range.endContainer, range.endOffset)\n after.setEnd(ancestorRange.endContainer, ancestorRange.endOffset)\n\n // Remove back to front...\n const afterFragment = after.extractContents()\n const selectedFragment = range.extractContents()\n const beforeFragment = before.extractContents()\n ancestorRange.deleteContents() // wipe everything, just in case\n\n // Create our fragment\n const fragment = document.createDocumentFragment()\n fragment.append(beforeFragment)\n\n // Append our selected fragment, possibly wrapped in a tag\n if (ancestor) {\n fragment.append(selectedFragment)\n } else {\n const tag = document.createElement(tagName)\n Object.entries(attributes).forEach((attr) => tag.setAttribute(...attr))\n tag.append(selectedFragment)\n fragment.append(tag)\n }\n\n // Surround with before and after fragments\n // fragment.insertBefore(beforeFragment, fragment.firstChild)\n fragment.append(afterFragment)\n\n // Insert and sanitize\n ancestorRange.insertNode(fragment)\n sanitize(parent)\n}\n","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nconst _hoisted_1 = {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"currentColor\",\n viewBox: \"0 -960 960 960\"\n}\n\nexport function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", _hoisted_1, _cache[0] || (_cache[0] = [\n _createElementVNode(\"path\", { d: \"M272-200v-560h221q65 0 120 40t55 111q0 51-23 78.5T602-491q25 11 55.5 41t30.5 90q0 89-65 124.5T501-200zm121-112h104q48 0 58.5-24.5T566-372t-10.5-35.5T494-432H393zm0-228h93q33 0 48-17t15-38q0-24-17-39t-44-15h-95z\" }, null, -1)\n ])))\n}\nexport default { render: render }","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nconst _hoisted_1 = {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"currentColor\",\n viewBox: \"0 -960 960 960\"\n}\n\nexport function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", _hoisted_1, _cache[0] || (_cache[0] = [\n _createElementVNode(\"path\", { d: \"M200-200v-100h160l120-360H320v-100h400v100H580L460-300h140v100z\" }, null, -1)\n ])))\n}\nexport default { render: render }","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nconst _hoisted_1 = {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"currentColor\",\n viewBox: \"0 -960 960 960\"\n}\n\nexport function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", _hoisted_1, _cache[0] || (_cache[0] = [\n _createElementVNode(\"path\", { d: \"M440-280H280q-83 0-141.5-58.5T80-480t58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85 35 85 85 35h160zM320-440v-80h320v80zm200 160v-80h160q50 0 85-35t35-85-35-85-85-35H520v-80h160q83 0 141.5 58.5T880-480t-58.5 141.5T680-280z\" }, null, -1)\n ])))\n}\nexport default { render: render }","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nconst _hoisted_1 = {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"currentColor\",\n viewBox: \"0 -960 960 960\"\n}\n\nexport function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", _hoisted_1, _cache[0] || (_cache[0] = [\n _createElementVNode(\"path\", { d: \"M480-80q-83 0-156-31.5T197-197t-85.5-127T80-480t31.5-156T197-763t127-85.5T480-880t156 31.5T763-763t85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480t58.5-141.5T480-680t141.5 58.5T680-480v58q0 26 17 44t43 18 43-18 17-44v-58q0-134-93-227t-227-93-227 93-93 227 93 227 227 93h200v80zm0-280q50 0 85-35t35-85-35-85-85-35-85 35-35 85 35 85 85 35\" }, null, -1)\n ])))\n}\nexport default { render: render }","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nconst _hoisted_1 = {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"currentColor\",\n viewBox: \"0 -960 960 960\"\n}\n\nexport function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", _hoisted_1, _cache[0] || (_cache[0] = [\n _createElementVNode(\"path\", { d: \"M792-443 176-183q-20 8-38-3.5T120-220v-520q0-22 18-33.5t38-3.5l616 260q25 11 25 37t-25 37M200-280l474-200-474-200v140l240 60-240 60zm0 0v-400z\" }, null, -1)\n ])))\n}\nexport default { render: render }","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nconst _hoisted_1 = {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"currentColor\",\n viewBox: \"0 -960 960 960\"\n}\n\nexport function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", _hoisted_1, _cache[0] || (_cache[0] = [\n _createElementVNode(\"path\", { d: \"M280-160v-520H80v-120h520v120H400v520zm360 0v-320H520v-120h360v120H760v320z\" }, null, -1)\n ])))\n}\nexport default { render: render }","<template>\n <div class=\"juit-tiny-edit\" :class=\"{ '-jte-editable': editable, '-jte-dark': dark }\">\n <div ref=\"rootRef\" class=\"-jte-root\">\n <!-- our mentions popup -->\n <div ref=\"mentionsPopupRef\" class=\"-jte-mentions-popup\">\n <ul ref=\"mentionsListRef\">\n <li\n v-for=\"(mention, i) in mentionsEntries\"\n :key=\"i\"\n :class=\"{ '-jte-mentions-selected': mentionsIndex === i }\"\n @mousedown=\"applyMention(mention.name, mention.value)\"\n >\n <div class=\"-jte-mentions-entry\">\n {{ mention.value }}\n <span class=\"-jte-mentions-ref\">{{ mention.name }}</span>\n </div>\n </li>\n </ul>\n <div v-if=\"! mentionsReady\" class=\"-jte-mentions-waiting\">\n {{ mentionsText }}{{ dotsString }}\n </div>\n <div v-else-if=\"! mentionsEntries.length\" class=\"-jte-mentions-none\">\n {{ mentionsText }}\n </div>\n </div>\n\n <!-- our links popup -->\n <div\n ref=\"linksPopupRef\"\n class=\"-jte-links-popup\"\n @click=\"activeLink = previousLink\"\n @focusin=\"activeLink = previousLink\"\n >\n <div>\n <iconText class=\"-jte-icon\" />\n <input v-model=\"linkText\" type=\"text\">\n </div>\n <div :class=\"{ '-jte-error': !linkValid }\">\n <iconLink class=\"-jte-icon\" />\n <input v-model=\"linkHref\" type=\"text\">\n </div>\n </div>\n\n <div v-if=\"placeholder && (! html)\" class=\"-jte-placeholder\">\n {{ placeholder }}\n </div>\n\n <!-- our editor -->\n <div\n ref=\"editorRef\"\n class=\"-jte-editor\"\n :contenteditable=\"editable ? 'plaintext-only' : 'false'\"\n @beforeinput=\"onBeforeInput($event as InputEvent)\"\n @input=\"onInput($event as InputEvent)\"\n @keydown=\"onKeydown($event)\"\n @paste=\"onPaste($event)\"\n @focusin=\"activeLink = null\"\n />\n\n <div class=\"-jte-toolbox\">\n <iconMention class=\"-jte-icon -jte-icon-mention\" :class=\"{ '-jte-disabled': ! selected?.collapsed }\" @click=\"insertMention\" />\n <iconBold class=\"-jte-icon -jte-icon-bold\" :class=\"iconClass(isBold, 'b')\" @click=\"applyTag('b')\" />\n <iconItalic class=\"-jte-icon -jte-icon-italic\" :class=\"iconClass(isItalic, 'i')\" @click=\"applyTag('i')\" />\n <iconLink class=\"-jte-icon -jte-icon-href\" :class=\"iconClass(isLink)\" @click=\"applyTag('a', { href: 'http://__placeholder__/' })\" />\n <iconSend class=\"-jte-icon -jte-icon-send\" :class=\"{ '-jte-disabled': !html, '-jte-active': !!html }\" @click=\"submit\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'\n\nimport { getCharacters, getOffsetsRange, getRangeOffsets } from './range'\nimport { replaceRange } from './replace'\nimport { sanitize } from './sanitize'\nimport { getSelectionOffsets, getSelectionRange, restoreSelection } from './selection'\nimport { isTagged, toggleTag } from './tags'\n// SVGs\nimport iconBold from './svg/bold.svg'\nimport iconItalic from './svg/italic.svg'\nimport iconLink from './svg/link.svg'\nimport iconMention from './svg/mention.svg'\nimport iconSend from './svg/send.svg'\nimport iconText from './svg/text.svg'\n\nimport type { PropType } from 'vue'\nimport type { Offsets } from './range'\n\n/* ==== DEFS ================================================================ */\n\ndefineOptions({ name: 'JuitTinyEditor' })\n\nconst model = defineModel({\n type: String,\n required: false,\n default: '',\n})\n\nconst props = defineProps({\n /** A flag indicating if content can be edited (default: `true`) */\n editable: {\n type: Boolean,\n required: false,\n default: true,\n },\n /** A flag indicating whether dark mode is enabled or not */\n dark: {\n type: Boolean,\n required: false,\n default: false,\n },\n /** A placeholder for when no text is available */\n placeholder: {\n type: String,\n required: false,\n default: '',\n },\n /** A list of mentions to be used in the editor */\n mentions: {\n type: Object as PropType<Record<string, string> | null>,\n required: false,\n default: () => ({}),\n },\n})\n\nwatch(() => props.dark, (dark) => {\n rootRef.value?.style.setProperty('color-scheme', dark ? 'dark light' : 'light dark')\n // document.body.classList.toggle('dark', dark)\n})\n\n/* ==== REFS ================================================================ */\n\n/** Reference to our root container */\nconst rootRef = shallowRef<HTMLDivElement | null>(null)\n/** Reference to our content editable DIV */\nconst editorRef = shallowRef<HTMLDivElement | null>(null)\n/** Reference to our popup */\nconst mentionsPopupRef = shallowRef<HTMLDivElement | null>(null)\n/** Reference to our mentions list (in the popup) DIV */\nconst mentionsListRef = shallowRef<HTMLDivElement | null>(null)\n/** Reference to our links popup */\nconst linksPopupRef = shallowRef<HTMLDivElement | null>(null)\n\n/** The `Range` of the current selection, if within our editor */\nconst selected = shallowRef<Range & { backwards?: boolean } | null>(null)\n\n/** The HTML string contained by the editor */\nconst html = ref('')\n\n/** Index of the current mention */\nconst mentionsIndex = ref(0)\n/** Range of the current mention (while being inserted) */\nconst mentionsRange = shallowRef<Range | null>(null)\n/** Text of the current mention (including the leading \"@\") */\nconst mentionsText = computed(() => mentionsRange.value?.toString() || '')\n/** Computed to see if mentions (from our prop) are ready */\nconst mentionsReady = computed(() => !! props.mentions)\n/** Computed list of mentions that match the current text */\nconst mentionsEntries = computed(() => {\n const text = mentionsText.value.substring(1).toLowerCase()\n\n if (! props.mentions) return []\n return Object.entries(props.mentions)\n .filter(([ _, name ]) => name.toLowerCase().startsWith(text))\n .sort(([ _a, a ], [ _b, b ]) => a.localeCompare(b))\n .map(([ name, value ]) => ({ name, value }))\n})\n\n/** The link associated with the *current* selection in the editor */\nconst selectedLink = ref<Element | null>(null)\n/** The link currently being handled by the links popup */\nconst activeLink = ref(null as any)\n/** The last link highlighted by the editor (updated by the watch below) */\nconst previousLink = ref<Element | null>(null)\n/** The current link, either in the editor or in the links popup */\nconst isLink = computed<Element | null>(() => selectedLink.value || activeLink.value)\n/* Watch the selected link and remember the last viable one */\nwatch(selectedLink, (link) => link && (previousLink.value = link))\n/** The text of the current link */\nconst linkText = ref('')\n/** The href of the current link */\nconst linkHref = ref('')\n/** Validate the link's own href (must be empty or an URL) */\nconst linkValid = computed(() => {\n if (! linkHref.value) return true\n try {\n new URL(linkHref.value)\n return true\n } catch {\n return false\n }\n})\n/** Update the content and href of the current link from the popup */\nwatch([ linkText, linkHref ], ([ text, href ]) => {\n if (! isLink.value) return\n isLink.value.textContent = text\n isLink.value.setAttribute('href', href)\n commit()\n})\n\n\n/** Whether the current selection is all bold */\nconst isBold = ref<Element | null>(null)\n/** Whether the current selection is all italic */\nconst isItalic = ref<Element | null>(null)\n\n/** How many dots are we displaying (in \"waiting for mentions\" animation) */\nconst dots = ref(0)\n/** String of dots to display (in \"waiting for mentions\" animation) */\nconst dotsString = computed(() => '.' + '.'.repeat(dots.value % 5))\n/** Interval animating our \"waiting for mentions\" text */\nlet dotsInterval: ReturnType<typeof setInterval> | null = null\n/** The _next_ tag to wrap our input into (when enabling bold on collapsed) */\nconst nextTag = ref('')\n/** When the selected range changes, reset the next tag */\nwatch((selected), (newRange, oldRange) => {\n if (oldRange === null) return\n if (newRange === null) return\n\n if ((oldRange.startContainer == newRange.startContainer) &&\n (oldRange.startOffset == newRange.startOffset) &&\n (oldRange.endContainer == newRange.endContainer) &&\n (oldRange.endOffset == newRange.endOffset) &&\n (oldRange?.backwards == newRange?.backwards)) return\n nextTag.value = ''\n})\n\n/** Our history (for undo) */\nconst history: (Offsets & { content: string })[] = []\n\n/* ==== FUNCTIONS =========================================================== */\n\n/** Compute the class for an icon (bold or italic) */\nfunction iconClass(element: Element | null, tag?: string): string {\n if (! selected.value) return '-jte-disabled'\n\n // If we don't have a tag, we're not processing zero-width selections\n if (! tag) {\n if (element) return '-jte-active'\n if (selected.value.collapsed) return '-jte-disabled'\n return ''\n }\n\n // styled, but next input will remove the style\n if (element && (nextTag.value === tag)) return ''\n // either styled *or* next input will add the style\n if (element || nextTag.value === tag) return '-jte-active'\n // not styled, but can be styled\n return ''\n}\n\n/** Commit changes to the model converting our editor into HTML */\nfunction commit(): void {\n if (! editorRef.value) return void (html.value = '')\n\n // Clean our HTML, then update the model\n const string = editorRef.value.innerHTML\n html.value = string === '<br>' ? '' : // edge case: empty editor\n string.replaceAll(' ', ' ').trimEnd() // remove trailing whitespace\n}\n\n/** Mark a new entry in our history */\nfunction historySave(): void {\n if (! editorRef.value) return\n\n // Save our history\n if (history[history.length - 1]?.content !== html.value) {\n const offsets = getSelectionOffsets(editorRef.value) || { start: 0, end: 0 }\n history.push({ ...offsets, content: html.value })\n if (history.length > 100) history.shift()\n }\n}\n\n/** Undo the last change */\nfunction historyUndo(): void {\n if (! editorRef.value) return\n if (! selected.value) return\n\n const entry = history.pop()\n if (! entry) return\n\n const body = new DOMParser().parseFromString(entry.content, 'text/html').body\n editorRef.value.replaceChildren(...body.childNodes)\n restoreSelection(editorRef.value, entry)\n}\n\n/** Compute the mention range according to changes th the selection */\nfunction computeMention(): void {\n if (mentionsRange.value && selected.value?.collapsed) {\n const range = mentionsRange.value.cloneRange()\n range.setEnd(selected.value.startContainer, selected.value.startOffset)\n if (range.toString().match(/^@[^\\r\\n]*$/)) {\n mentionsRange.value = range\n return\n }\n }\n mentionsRange.value = null\n}\n\n/** Replace the current mentions range with a proper mention */\nfunction applyMention(name: string, value: string): void {\n if (! editorRef.value) return\n if (! mentionsRange.value) return\n\n const editor = editorRef.value\n\n historySave()\n const fragment = document.createDocumentFragment()\n const element = document.createElement('link')\n element.setAttribute('rel', 'mention')\n element.setAttribute('name', name)\n element.setAttribute('title', value)\n fragment.append(element, ' ')\n\n const offsets = replaceRange(editor, mentionsRange.value, fragment)\n selectWithOffsets(offsets, true)\n mentionsRange.value = null\n commit()\n}\n\n/** Apply a \"tag\" (bold, italic, ...) */\nfunction applyTag(tagName: string, attributes: Record<string, string> = {}): void {\n if (! editorRef.value) return\n if (! selected.value) return\n\n const editor = editorRef.value\n\n // If nothing is selected, this is reserved for the next tag\n if (selected.value.collapsed) {\n nextTag.value = tagName\n editor.focus()\n return\n }\n\n historySave()\n const offsets = getRangeOffsets(editor, selected.value)\n toggleTag(editor, selected.value, tagName, attributes)\n selectWithOffsets(offsets)\n commit()\n}\n\nfunction insertMention(): void {\n if (! editorRef.value) return\n if (! selected.value?.collapsed) return\n\n const editor = editorRef.value\n\n const offsets = getRangeOffsets(editor, selected.value)\n\n const at = document.createTextNode('@')\n selected.value.insertNode(at)\n\n offsets.end += 1\n mentionsRange.value = getOffsetsRange(editor, offsets)\n offsets.start += 1\n selectWithOffsets(offsets)\n}\n\nfunction selectWithOffsets(offsets: Offsets, collapse?: boolean): void {\n if (! editorRef.value) return\n\n const editor = editorRef.value\n selected.value = restoreSelection(editor, offsets, collapse)\n editor.focus()\n}\n\nfunction submit(): void {\n if (! html.value) return\n emit('submit', html.value)\n}\n\n/* ==== DOM EVENTS ========================================================== */\n\n/** Trigger shortcuts for applying bold, italic, ... */\nfunction onKeydown(event: KeyboardEvent): void {\n if (event.metaKey || event.ctrlKey) {\n switch (event.key) {\n case 'b':\n applyTag('b')\n event.preventDefault()\n break\n case 'i':\n applyTag('i')\n event.preventDefault()\n break\n case 'z':\n historyUndo()\n event.preventDefault()\n break\n }\n } else if (mentionsRange.value) {\n switch (event.key) {\n case 'Home':\n mentionsIndex.value = 0\n event.preventDefault()\n break\n case 'ArrowUp':\n mentionsIndex.value = Math.max(mentionsIndex.value - 1, 0)\n event.preventDefault()\n break\n case 'PageUp':\n mentionsIndex.value = Math.max(mentionsIndex.value - 4, 0)\n event.preventDefault()\n break\n case 'ArrowDown':\n mentionsIndex.value = Math.min(mentionsIndex.value + 1, mentionsEntries.value.length - 1)\n event.preventDefault()\n break\n case 'PageDown':\n mentionsIndex.value = Math.min(mentionsIndex.value + 4, mentionsEntries.value.length - 1)\n event.preventDefault()\n break\n case 'End':\n mentionsIndex.value = mentionsEntries.value.length - 1\n event.preventDefault()\n break\n case 'Escape':\n mentionsRange.value = null\n event.preventDefault()\n break\n case 'Enter': {\n const mention = mentionsEntries.value[mentionsIndex.value]\n if (mention) applyMention(mention.name, mention.value)\n event.preventDefault()\n break\n }\n }\n }\n}\n\n/** Replace the current selection with the (sanitized) content from clipboard */\nfunction onPaste(event: ClipboardEvent): void {\n event.preventDefault()\n if (! editorRef.value) return\n if (! selected.value) return\n\n const data = event.clipboardData\n if (! data) return\n\n const text = data.getData('text/html') || data.getData('text/plain')\n if (! text) return\n\n const offsets = replaceRange(editorRef.value, selected.value, text)\n\n selectWithOffsets(offsets, true)\n commit()\n}\n\n/** If deleting a bunch of content, ensure links are removed, too... */\nfunction onBeforeInput(event: InputEvent): void {\n if (! editorRef.value) return\n if (! selected.value) return\n\n historySave()\n\n if ((!selected.value.collapsed) &&\n ((event.inputType === 'deleteContentBackward') ||\n (event.inputType === 'deleteContentForward'))) {\n event.preventDefault()\n event.stopPropagation()\n const offsets = replaceRange(editorRef.value, selected.value, '')\n selectWithOffsets(offsets, true)\n commit()\n }\n}\n\n/** Someone typed something in our editor, let's process it */\nfunction onInput(event: InputEvent): void {\n const editor = editorRef.value\n if (! editor) return\n\n // Save the current selection and mentions\n const mentions = mentionsRange.value ? getRangeOffsets(editor, mentionsRange.value) : null\n const selection = getSelectionOffsets(editor)\n\n // See if we need to process this event with style (bold, italic, ...)\n if (nextTag.value &&\n selection &&\n (selection.start === selection.end) &&\n (event.inputType === 'insertText') &&\n (event.data !== '@') &&\n (! mentionsRange.value)) {\n const range = getOffsetsRange(editor, { start: selection.start - 1, end: selection.end })\n toggleTag(editor, range, nextTag.value)\n }\n nextTag.value = ''\n\n // First thing first: sanitize the content (this will also take care of links)\n sanitize(editor)\n\n // Restore mentions range *after* sanitization\n if (mentions) mentionsRange.value = getOffsetsRange(editor, mentions)\n\n // Restore the selection range\n if (selection) {\n // Edge case when all content is deleted: insert a single \"<br>\"\n if (editor.innerHTML === '<br>') {\n document.getSelection()?.setBaseAndExtent(editor, 0, editor, 0)\n editor.focus()\n } else {\n selectWithOffsets(selection)\n }\n }\n\n // When the input is \"@\" we might want to trigger a mention\n if ((! isLink.value) &&\n (! mentionsRange.value) &&\n (event.data === '@') &&\n (event.inputType === 'insertText')) {\n const selection = document.getSelection()\n if (! selection?.anchorNode) return\n const text = getCharacters(editor, selection.anchorNode, selection.anchorOffset)\n if (text.match(/(\\s|^)@$/)) {\n const start = text.length === 1 ? 0 : text.length - 1\n const end = text.length\n const offsets: Offsets = { start, end }\n mentionsRange.value = getOffsetsRange(editor, offsets)\n }\n } else {\n computeMention()\n }\n\n // Commit the changes\n commit()\n}\n\n\n/* Update the `selected` Range when the document selection changes */\nfunction onSelectionChange(_event: Event): void {\n // Wipe all link information if we leave our component\n if (!(rootRef.value && getSelectionRange(rootRef.value))) {\n selectedLink.value = null\n previousLink.value = null\n activeLink.value = null\n }\n\n // Handle the current editor selection and remember its range (if any)\n selected.value = editorRef.value ? getSelectionRange(editorRef.value) : null\n\n // Process any changes to mentions\n computeMention()\n}\n\n/* ===== WATCHES AND COMPONENT LIFECYCLE ==================================== */\n\nonMounted(() => {\n // Bind DOM events\n document.addEventListener('selectionchange', onSelectionChange)\n\n // References to our editor, popup, and list\n if (! rootRef.value) throw new Error('Root not referenced')\n if (! editorRef.value) throw new Error('Editor not referenced')\n if (! mentionsPopupRef.value) throw new Error('Popup not referenced')\n if (! mentionsListRef.value) throw new Error('List not referenced')\n if (! linksPopupRef.value) throw new Error('Popup not referenced')\n const root = rootRef.value\n const editor = editorRef.value\n const mentionsPopup = mentionsPopupRef.value\n const mentionsList = mentionsListRef.value\n const linksPopup = linksPopupRef.value\n\n /* On changes to the _local_ HTML, trigger an emit on our model value */\n watch(html, (html) => {\n if (html !== model.value) model.value = html // no changes, ignore!\n })\n\n /* Watch our model, when the value is different from the local HTML, parse */\n watch(model, (model) => {\n if (model === html.value) return // no changes, ignore!\n\n const body = new DOMParser().parseFromString(model, 'text/html').body\n sanitize(body)\n editor.replaceChildren(...body.childNodes)\n commit()\n }, { immediate: true })\n\n /* Watch our mentions availability and trigger a waiting animation */\n watch(mentionsReady, (available) => {\n dots.value = 0\n if (! available) dotsInterval = setInterval(() => dots.value = dots.value + 1, 200)\n else if (dotsInterval) clearInterval(dotsInterval)\n }, { immediate: true })\n\n /* Watch our links and update the popup */\n watch(isLink, (link) => {\n // Always update (or reset?) the link text and href of a link\n linkText.value = link?.textContent || ''\n linkHref.value = link?.getAttribute('href') || ''\n\n // If the link is a placeholder, consider this to be an empty link\n if (linkHref.value === 'http://__placeholder__/') {\n link?.setAttribute('href', '')\n linkHref.value = ''\n }\n\n // If we are no longer editing a link, sanitize and hide the popup\n if (! link) {\n const offsets = getSelectionOffsets(editor)\n sanitize(editor)\n if (offsets) restoreSelection(editor, offsets)\n\n linksPopup.style.display = 'none'\n linksPopup.style.top = '0'\n linksPopup.style.left = '0'\n return\n }\n\n // Wait for the next tick to make sure the DOM is updated\n nextTick(() => {\n // Position and show the popup\n linksPopup.style.display = 'flex'\n\n const rootRect = root.getBoundingClientRect()\n const rangeRect = link.getBoundingClientRect()\n const editorRect = editor.getBoundingClientRect()\n\n const popupStyle = getComputedStyle(linksPopup)\n const popupWidth = parseInt(popupStyle.width)\n const popupHeight = parseInt(popupStyle.height)\n\n const fitsTop = (rangeRect.top - popupHeight) - 6 > editorRect.top\n const fitsBottom = (rangeRect.bottom + popupHeight + 3) < editorRect.bottom\n const fitsRight = rangeRect.left + popupWidth < editorRect.right\n\n linksPopup.style.top = (! fitsBottom && fitsTop) ?\n `${rangeRect.top - popupHeight - rootRect.top - 6}px` : // over the link\n `${rangeRect.bottom - rootRect.top + 3}px` // below the link\n linksPopup.style.left = fitsRight ?\n `${rangeRect.left - rootRect.left}px` : // right of the link\n `${editorRect.right - popupWidth - rootRect.left}px` // aligned to the right of the editor\n })\n }, { immediate: true })\n\n /* Watch our mentions index and update the popup */\n watch([ mentionsIndex, mentionsRange, mentionsEntries ], ([ index, range, entries ], [ oldIndex = -1 ]) => {\n // Make sure that our index is within acceptable bounds\n if (index >= entries.length) index = Math.max(entries.length - 1, 0)\n mentionsIndex.value = index\n\n // No list or no popup, nothing to do\n if (! mentionsPopup) return\n if (! mentionsList) return\n\n // No range, hide and reset the popup\n if (! range) {\n mentionsIndex.value = 0\n mentionsPopup.style.display = 'none'\n mentionsPopup.style.top = '0'\n mentionsPopup.style.left = '0'\n mentionsList.scrollTop = 0\n return\n }\n\n // Wait for the next tick to make sure the DOM is updated\n nextTick(() => {\n // Position and show the popup\n mentionsPopup.style.display = 'block'\n\n const rootRect = root.getBoundingClientRect()\n const rangeRect = range.getBoundingClientRect()\n const editorRect = editor.getBoundingClientRect()\n\n const popupStyle = getComputedStyle(mentionsPopup)\n const popupWidth = parseInt(popupStyle.width)\n const popupHeight = parseInt(popupStyle.height)\n\n const fitsTop = (rangeRect.top - popupHeight) - 6 > editorRect.top\n const fitsBottom = (rangeRect.bottom + popupHeight + 3) < editorRect.bottom\n const fitsRight = rangeRect.left + popupWidth < editorRect.right\n\n mentionsPopup.style.top = (! fitsBottom && fitsTop) ?\n `${rangeRect.top - popupHeight - rootRect.top - 6}px` : // over the link\n `${rangeRect.bottom - rootRect.top + 3}px` // below the link\n mentionsPopup.style.left = fitsRight ?\n `${rangeRect.left - rootRect.left}px` : // right of the link\n `${editorRect.right - popupWidth - rootRect.left}px` // aligned to the right of the editor\n\n // Scroll the list to make sure the selected item is visible\n const item = mentionsList.querySelectorAll('li')[mentionsIndex.value]\n if (! item) return\n\n const style = getComputedStyle(item)\n const topBorder = parseFloat(style.getPropertyValue('border-top-width')) || 0\n const bottomBorder= parseFloat(style.getPropertyValue('border-bottom-width')) || 0\n\n // Get the bounding rectangles for the list and the selected item\n const listRect = mentionsList.getBoundingClientRect()\n const itemRect = item.getBoundingClientRect()\n\n // Some CSS magic to scroll the list\n const topTop = itemRect.top - listRect.top\n const btmBtm = itemRect.bottom - listRect.bottom\n const relativeBtm = itemRect.bottom - listRect.top\n\n // Going down\n if (index > oldIndex) {\n // Element is hidden below the visible list\n if (relativeBtm > listRect.height) {\n mentionsList.scrollTop += btmBtm - bottomBorder\n // Element is hidden above the visible list\n } else if (topTop < 0) {\n mentionsList.scrollTop += topTop + topBorder\n }\n // Going up\n } else {\n // Element is hidden above the visible list\n if (topTop < 0) {\n mentionsList.scrollTop += topTop + topBorder\n // Element is hidden below the visible list\n } else if (btmBtm > 0) {\n mentionsList.scrollTop += relativeBtm - listRect.height - bottomBorder\n }\n }\n })\n }, { immediate: true })\n\n /* Watch the selected range and infer state for bold, italic, ... */\n watch(selected, (selected) => {\n if (selected) {\n selectedLink.value = isTagged(editor, selected, 'a')\n isBold.value = isTagged(editor, selected, 'b')\n isItalic.value = isTagged(editor, selected, 'i')\n } else {\n selectedLink.value = null\n isBold.value = null\n isItalic.value = null\n }\n }, { immediate: true })\n})\n\nonUnmounted(() => {\n document.removeEventListener('selectionchange', onSelectionChange)\n if (dotsInterval) clearInterval(dotsInterval)\n})\n\n/* ===== EMITS AND EXPOSED METHODS ========================================== */\n\nconst emit = defineEmits<{\n mention: [ string ],\n submit: [ string ],\n}>()\n\n/* Watch our local refs and emit */\nwatch(mentionsText, (text) => emit('mention', text.substring(1)), { immediate: true })\n</script>\n\n<style lang=\"pcss\">\n:root {\n --jte-border-radius: 4px;\n\n /* ===== LIGHT THEME ====================================================== */\n\n /* main colors */\n --jte-text: currentColor;\n --jte-background: transparent;\n --jte-active: #009;\n --jte-mention: #99f;\n --jte-popup: #fff;\n --jte-link: #009;\n --jte-error: #933;\n\n /* derived colors */\n --jte-inactive: color-mix(in srgb, var(--jte-text) 50%, transparent);\n --jte-border: color-mix(in srgb, var(--jte-text) 12.5%, transparent);\n --jte-shade: color-mix(in srgb, var(--jte-text) 6.25%, transparent);\n --jte-active-shade: color-mix(in srgb, var(--jte-active) 6.25%, transparent);\n\n /* button colors */\n --jte-button-text: var(--jte-text);\n --jte-button-border: var(--jte-border);\n --jte-button-background: var(--jte-background);\n\n --jte-button-hovered-text: var(--jte-text);\n --jte-button-hovered-border: var(--jte-border);\n --jte-button-hovered-background: var(--jte-shade);\n\n --jte-button-active-text: var(--jte-active);\n --jte-button-active-border: var(--jte-border);\n --jte-button-active-background: var(--jte-active-shade);\n\n --jte-button-disabled-text: var(--jte-inactive);\n --jte-button-disabled-border: var(--jte-border);\n --jte-button-disabled-background: var(--jte-shade);\n\n /* inputs within popup */\n --jte-input-text: var(--jte-text);\n --jte-input-background: transparent;\n --jte-input-border: var(--jte-border);\n --jte-input-shade: var(--jte-shade);\n\n /* ===== DARK THEME ======================================================= */\n\n /* main colors */\n --jte-dark-text: currentColor;\n --jte-dark-background: transparent;\n --jte-dark-active: #99f;\n --jte-dark-mention: #669;\n --jte-dark-popup: #111;\n --jte-dark-link: #99f;\n --jte-dark-error: #f99;\n --jte-dark-input: transparent;\n\n /* derived colors */\n --jte-dark-inactive: color-mix(in srgb, var(--jte-dark-text) 50%, transparent);\n --jte-dark-border: color-mix(in srgb, var(--jte-dark-text) 12.5%, transparent);\n --jte-dark-shade: color-mix(in srgb, var(--jte-dark-text) 12.5%, transparent);\n --jte-dark-active-shade: color-mix(in srgb, var(--jte-dark-active) 6.25%, transparent);\n\n /* button colors */\n --jte-dark-button-text: var(--jte-dark-text);\n --jte-dark-button-border: var(--jte-dark-border);\n --jte-dark-button-background: var(--jte-dark-background);\n\n --jte-dark-button-hovered-text: var(--jte-dark-text);\n --jte-dark-button-hovered-border: var(--jte-dark-border);\n --jte-dark-button-hovered-background: var(--jte-dark-shade);\n\n --jte-dark-button-active-text: var(--jte-dark-active);\n --jte-dark-button-active-border: var(--jte-dark-border);\n --jte-dark-button-active-background: var(--jte-dark-active-shade);\n\n --jte-dark-button-disabled-text: var(--jte-dark-inactive);\n --jte-dark-button-disabled-border: var(--jte-dark-border);\n --jte-dark-button-disabled-background: var(--jte-dark-shade);\n\n /* inputs within popup */\n --jte-dark-input-text: var(--jte-dark-text);\n --jte-dark-input-background: transparent;\n --jte-dark-input-border: var(--jte-dark-border);\n --jte-dark-input-shade: var(--jte-dark-shade);\n}\n\n/* ========================================================================== *\n * EDITOR STYLING *\n * ========================================================================== */\n\n.juit-tiny-edit {\n\n /* ===== APPLY DARK THEME ================================================= */\n\n &.-jte-dark .-jte-root {\n /* main colors */\n --jte-text: var(--jte-dark-text);\n --jte-background: var(--jte-dark-background);\n --jte-active: var(--jte-dark-active);\n --jte-mention: var(--jte-dark-mention);\n --jte-popup: var(--jte-dark-popup);\n --jte-link: var(--jte-dark-link);\n --jte-error: var(--jte-dark-error);\n\n /* derived colors */\n --jte-inactive: var(--jte-dark-inactive);\n --jte-border: var(--jte-dark-border);\n --jte-shade: var(--jte-dark-shade);\n --jte-active-shade: var(--jte-dark-active-shade);\n\n /* button colors */\n --jte-button-text: var(--jte-dark-button-text);\n --jte-button-border: var(--jte-dark-button-border);\n --jte-button-background: var(--jte-dark-button-background);\n\n --jte-button-hovered-text: var(--jte-dark-button-hovered-text);\n --jte-button-hovered-border: var(--jte-dark-button-hovered-border);\n --jte-button-hovered-background: var(--jte-dark-button-hovered-background);\n\n --jte-button-active-text: var(--jte-dark-button-active-text);\n --jte-button-active-border: var(--jte-dark-button-active-border);\n --jte-button-active-background: var(--jte-dark-button-active-background);\n\n --jte-button-disabled-text: var(--jte-dark-button-disabled-text);\n --jte-button-disabled-border: var(--jte-dark-button-disabled-border);\n --jte-button-disabled-background: var(--jte-dark-button-disabled-background);\n\n /* inputs within popup */\n --jte-input-text: var(--jte-dark-input-text);\n --jte-input-background: var(--jte-dark-input-background);\n --jte-input-border: var(--jte-dark-input-border);\n --jte-input-shade: var(--jte-dark-input-shade);\n }\n\n /* ===== STYLE OUR EDITOR ================================================= */\n\n .-jte-root {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n position: relative;\n box-sizing: content-box;\n\n color: var(--jte-text);\n background-color: var(--jte-background);\n\n /* ===== TOOLBAR ======================================================== */\n\n .-jte-toolbox {\n display: none;\n user-select: none;\n position: relative;\n height: 1.75em;\n overflow: hidden;\n margin-top: 0.75em;\n padding: 0 0.25em 0 0.25em;\n\n .-jte-icon {\n width: 1em;\n height: 1em;\n padding: 0.125em 0.25em;\n margin: 0 0.25em;\n vertical-align: top;\n\n color: var(--jte-button-text);\n background-color: var(--jte-button-background);\n border: 1px solid var(--jte-button-border);\n border-radius: var(--jte-border-radius);\n\n &.-jte-icon-mention {\n margin-left: 0.125em;\n }\n\n &.-jte-icon-send {\n margin-left: auto;\n margin-right: 0.125em;\n padding-left: 1em;\n padding-right: 1em;\n }\n\n &.-jte-disabled {\n color: var(--jte-button-disabled-text);\n border-color: var(--jte-button-disabled-border);\n background-color: var(--jte-button-disabled-background);\n }\n\n &.-jte-active {\n color: var(--jte-button-active-text);\n border-color: var(--jte-button-active-border);\n background-color: var(--jte-button-active-background);\n }\n\n &:active:not(.-jte-disabled) {\n color: var(--jte-button-active-text);\n border-color: var(--jte-button-active-border);\n background-color: var(--jte-button-active-background);\n }\n\n &:hover:not(.-jte-disabled):not(.-jte-active):not(:active) {\n color: var(--jte-button-hovered-text);\n border-color: var(--jte-button-hovered-border);\n background-color: var(--jte-button-hovered-background);\n }\n }\n }\n\n /* ===== PLACEHOLDER ==================================================== */\n\n .-jte-placeholder {\n position: absolute;\n font-style: italic;\n padding: 0.25em;\n pointer-events: none;\n color: var(--jte-inactive);\n }\n\n /* ===== EDITOR ========================================================= */\n\n .-jte-editor {\n width: 100%;\n box-sizing: border-box;\n padding: 0.25em;\n white-space: pre-wrap;\n\n border-radius: var(--jte-border-radius);\n border: 1px solid var(--jte-border);\n\n &:focus {\n outline: 1px solid var(--jte-active);\n border: 1px solid var(--jte-active);\n }\n\n /** Links */\n a {\n color: var(--jte-link);\n }\n\n /** Our mentions (this needs to be \"inline\" for contenteditable to work) */\n link[rel=\"mention\"] {\n display: inline;\n\n cursor: default;\n user-select: none;\n font-weight: normal;\n font-style: normal;\n text-decoration: none;\n\n font-size: 0.85em;\n overflow: hidden;\n\n &:before {\n padding: 0 0.25em;\n content: '\\200b'; /* zero width space */\n\n background-color: var(--jte-mention);\n border: 1px solid var(--jte-border);\n border-radius: var(--jte-border-radius);\n border-right: none;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n\n &:after {\n content: attr(title);\n padding: 0 0.5em 0 0.25em;\n\n background-color: var(--jte-shade);\n border: 1px solid var(--jte-border);\n border-radius: var(--jte-border-radius);\n border-left: none;\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n }\n }\n\n /* ===== POPUPS ========================================================= */\n\n /** Popups basics */\n .-jte-mentions-popup, .-jte-links-popup {\n display: none;\n position: absolute;\n z-index: 1000;\n\n font-size: 0.85em;\n background-color: var(--jte-popup);\n border-radius: var(--jte-border-radius);\n border: 1px solid var(--jte-border);\n }\n\n /* ===== MENTIONS POPUP ================================================= */\n\n /** Our mentions popup */\n .-jte-mentions-popup {\n overflow: hidden;\n cursor: pointer;\n min-width: 8em;\n\n /** Our list of mentions */\n ul {\n overflow: scroll;\n max-height: calc(7.5em + 3px);\n white-space: nowrap;\n margin: 0;\n padding: 0;\n }\n\n /** Each mention entry */\n li {\n list-style-type: none;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n color: var(--jte-inactive);\n\n /** Inside of our mentions entry */\n .-jte-mentions-entry {\n padding-block: 0.125em;\n padding-left: 0.25em;\n padding-right: 1.5em;\n border-left-width: 0.5em;\n border-left-style: solid;\n border-left-color: transparent;\n\n /** Refs want a smaller font */\n .-jte-mentions-ref {\n margin-left: 0.5em;\n font-size: 0.85em;\n font-style: italic;\n }\n }\n\n /** Selected list item */\n &.-jte-mentions-selected {\n color: var(--jte-input-text);\n background-color: var(--jte-shade);\n border-color: var(--jte-input-border);\n\n /** Apply our \"tag\" to the slected mention */\n .-jte-mentions-entry {\n border-color: var(--jte-mention);\n }\n }\n\n /** No borders for first and last item */\n &:first-child { border-top: none; }\n &:last-child { border-bottom: none; }\n }\n\n /** Basic style for waiting / no item found */\n .-jte-mentions-waiting, .-jte-mentions-none {\n font-size: 0.85em;\n color: var(--jte-inactive);\n border-left-width: 0.5em;\n border-left-style: solid;\n border-color: transparent;\n padding-block: 0.125em;\n padding-inline: 0.25em;\n }\n\n /** Waiting is in italic */\n .-jte-mentions-waiting {\n font-style: italic;\n }\n\n /** None has a grey tab */\n .-jte-mentions-none {\n border-color: var(--jte-shade);\n }\n }\n\n /* ===== LINKS POPUP ==================================================== */\n\n .-jte-links-popup {\n user-select: none;\n padding: 0.125em;\n width: 20em;\n flex-direction: column;\n\n div {\n display: flex;\n flex-direction: row;\n margin: 0.25em;\n position: relative;\n background-color: var(--jte-input-background);\n border-radius: var(--jte-border-radius);\n }\n\n .-jte-icon {\n display: block;\n position: absolute;\n\n top: 1px;\n left: 1px;\n\n width: 1.25em;\n height: 1.25em;\n padding: 0.125em 0.25em;\n margin: 0;\n\n color: var(--jte-input-text);\n background-color: var(--jte-input-shade);\n border: none;\n border-radius: calc(var(--jte-border-radius) - 1px);\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-right: none;\n }\n\n input {\n display: block;\n flex-grow: 1;\n\n font-size: 1em;\n height: 1.25em;\n padding: 0.125em 0.25em 0.125em 2.25em;\n margin: 0;\n\n color: var(--jte-input-text);\n border: 1px solid var(--jte-input-border);\n border-radius: var(--jte-border-radius);\n background-color: transparent;\n\n &:focus {\n outline: 1px solid var(--jte-active);\n border: 1px solid var(--jte-active);\n }\n }\n\n .-jte-error {\n .-jte-icon, input {\n color: var(--jte-error);\n }\n }\n }\n }\n\n /* ===== TOOLBOX VISIBLE ONLY ON EDITABLE ================================= */\n\n &.-jte-editable {\n .-jte-editor {\n padding-bottom: 2.5em;\n margin-bottom: -2.5em;\n }\n\n .-jte-toolbox {\n display: flex;\n }\n }\n}\n</style>\n"],"names":["containsRange","haystack","needle","rangeFromContents","rangeFromNode","node","range","getCharacters","parent","child","offset","getRangeOffsets","result","getOffsetsRange","offsets","start","end","firstNode","lastNode","foundStart","foundEnd","iterator","text","length","STRIP_TAGS","parseURL","string","maybeWrap","fragment","tagName","noWrap","tag","sanitizeStyles","state","element","previous","_a","name","title","link","mergeOffsets","list","a","b","nested","i","outer","j","inner","sanitizeLinks","textRanges","re","plain","match","href","linkRanges","sanitizeEmpty","sanitize","sanitized","wrapped","replaceRange","html","body","beforeLength","parentRange","beforeRange","afterRange","before","after","afterLength","_b","getSelectionRange","selection","selectionRange","backwards","getSelectionOffsets","restoreSelection","collapse","startContainer","startOffset","endContainer","endOffset","isTagged","ancestor","toggleTag","attributes","ancestorRange","afterFragment","selectedFragment","beforeFragment","attr","_hoisted_1","render","_ctx","_cache","_openBlock","_createElementBlock","_createElementVNode","iconBold","iconItalic","iconLink","iconMention","iconSend","iconText","model","_useModel","__props","props","watch","dark","rootRef","shallowRef","editorRef","mentionsPopupRef","mentionsListRef","linksPopupRef","selected","ref","mentionsIndex","mentionsRange","mentionsText","computed","mentionsReady","mentionsEntries","_","value","selectedLink","activeLink","previousLink","isLink","linkText","linkHref","linkValid","commit","isBold","isItalic","dots","dotsString","dotsInterval","nextTag","newRange","oldRange","history","iconClass","historySave","historyUndo","entry","computeMention","applyMention","editor","selectWithOffsets","applyTag","insertMention","at","submit","emit","onKeydown","event","mention","onPaste","data","onBeforeInput","onInput","mentions","onSelectionChange","_event","onMounted","root","mentionsPopup","mentionsList","linksPopup","available","nextTick","rootRect","rangeRect","editorRect","popupStyle","popupWidth","popupHeight","fitsTop","fitsBottom","fitsRight","index","entries","oldIndex","item","style","topBorder","bottomBorder","listRect","itemRect","topTop","btmBtm","relativeBtm","onUnmounted","__emit"],"mappings":"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"}
|
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--jte-border-radius: 4px;--jte-text: currentColor;--jte-background: transparent;--jte-active: #009;--jte-mention: #99f;--jte-popup: #fff;--jte-link: #009;--jte-error: #933;--jte-inactive: color-mix(in srgb, var(--jte-text) 50%, transparent);--jte-border: color-mix(in srgb, var(--jte-text) 12.5%, transparent);--jte-shade: color-mix(in srgb, var(--jte-text) 6.25%, transparent);--jte-active-shade: color-mix(in srgb, var(--jte-active) 6.25%, transparent);--jte-button-text: var(--jte-text);--jte-button-border: var(--jte-border);--jte-button-background: var(--jte-background);--jte-button-hovered-text: var(--jte-text);--jte-button-hovered-border: var(--jte-border);--jte-button-hovered-background: var(--jte-shade);--jte-button-active-text: var(--jte-active);--jte-button-active-border: var(--jte-border);--jte-button-active-background: var(--jte-active-shade);--jte-button-disabled-text: var(--jte-inactive);--jte-button-disabled-border: var(--jte-border);--jte-button-disabled-background: var(--jte-shade);--jte-input-text: var(--jte-text);--jte-input-background: transparent;--jte-input-border: var(--jte-border);--jte-input-shade: var(--jte-shade);--jte-dark-text: currentColor;--jte-dark-background: transparent;--jte-dark-active: #99f;--jte-dark-mention: #669;--jte-dark-popup: #111;--jte-dark-link: #99f;--jte-dark-error: #f99;--jte-dark-input: transparent;--jte-dark-inactive: color-mix(in srgb, var(--jte-dark-text) 50%, transparent);--jte-dark-border: color-mix(in srgb, var(--jte-dark-text) 12.5%, transparent);--jte-dark-shade: color-mix(in srgb, var(--jte-dark-text) 12.5%, transparent);--jte-dark-active-shade: color-mix(in srgb, var(--jte-dark-active) 6.25%, transparent);--jte-dark-button-text: var(--jte-dark-text);--jte-dark-button-border: var(--jte-dark-border);--jte-dark-button-background: var(--jte-dark-background);--jte-dark-button-hovered-text: var(--jte-dark-text);--jte-dark-button-hovered-border: var(--jte-dark-border);--jte-dark-button-hovered-background: var(--jte-dark-shade);--jte-dark-button-active-text: var(--jte-dark-active);--jte-dark-button-active-border: var(--jte-dark-border);--jte-dark-button-active-background: var(--jte-dark-active-shade);--jte-dark-button-disabled-text: var(--jte-dark-inactive);--jte-dark-button-disabled-border: var(--jte-dark-border);--jte-dark-button-disabled-background: var(--jte-dark-shade);--jte-dark-input-text: var(--jte-dark-text);--jte-dark-input-background: transparent;--jte-dark-input-border: var(--jte-dark-border);--jte-dark-input-shade: var(--jte-dark-shade)}.juit-tiny-edit.-jte-dark .-jte-root{--jte-text: var(--jte-dark-text);--jte-background: var(--jte-dark-background);--jte-active: var(--jte-dark-active);--jte-mention: var(--jte-dark-mention);--jte-popup: var(--jte-dark-popup);--jte-link: var(--jte-dark-link);--jte-error: var(--jte-dark-error);--jte-inactive: var(--jte-dark-inactive);--jte-border: var(--jte-dark-border);--jte-shade: var(--jte-dark-shade);--jte-active-shade: var(--jte-dark-active-shade);--jte-button-text: var(--jte-dark-button-text);--jte-button-border: var(--jte-dark-button-border);--jte-button-background: var(--jte-dark-button-background);--jte-button-hovered-text: var(--jte-dark-button-hovered-text);--jte-button-hovered-border: var(--jte-dark-button-hovered-border);--jte-button-hovered-background: var(--jte-dark-button-hovered-background);--jte-button-active-text: var(--jte-dark-button-active-text);--jte-button-active-border: var(--jte-dark-button-active-border);--jte-button-active-background: var(--jte-dark-button-active-background);--jte-button-disabled-text: var(--jte-dark-button-disabled-text);--jte-button-disabled-border: var(--jte-dark-button-disabled-border);--jte-button-disabled-background: var(--jte-dark-button-disabled-background);--jte-input-text: var(--jte-dark-input-text);--jte-input-background: var(--jte-dark-input-background);--jte-input-border: var(--jte-dark-input-border);--jte-input-shade: var(--jte-dark-input-shade)}.juit-tiny-edit .-jte-root{display:flex;flex-direction:column;align-items:stretch;position:relative;box-sizing:content-box;color:var(--jte-text);background-color:var(--jte-background)}.juit-tiny-edit .-jte-root .-jte-toolbox{display:none;-webkit-user-select:none;user-select:none;position:relative;height:1.75em;overflow:hidden;margin-top:.75em;padding:0 .25em}.juit-tiny-edit .-jte-root .-jte-toolbox .-jte-icon{width:1em;height:1em;padding:.125em .25em;margin:0 .25em;vertical-align:top;color:var(--jte-button-text);background-color:var(--jte-button-background);border:1px solid var(--jte-button-border);border-radius:var(--jte-border-radius)}.juit-tiny-edit .-jte-root .-jte-toolbox .-jte-icon.-jte-icon-mention{margin-left:.125em}.juit-tiny-edit .-jte-root .-jte-toolbox .-jte-icon.-jte-icon-send{margin-left:auto;margin-right:.125em;padding-left:1em;padding-right:1em}.juit-tiny-edit .-jte-root .-jte-toolbox .-jte-icon.-jte-disabled{color:var(--jte-button-disabled-text);border-color:var(--jte-button-disabled-border);background-color:var(--jte-button-disabled-background)}.juit-tiny-edit .-jte-root .-jte-toolbox .-jte-icon.-jte-active{color:var(--jte-button-active-text);border-color:var(--jte-button-active-border);background-color:var(--jte-button-active-background)}.juit-tiny-edit .-jte-root .-jte-toolbox .-jte-icon:active:not(.-jte-disabled){color:var(--jte-button-active-text);border-color:var(--jte-button-active-border);background-color:var(--jte-button-active-background)}.juit-tiny-edit .-jte-root .-jte-toolbox .-jte-icon:hover:not(.-jte-disabled):not(.-jte-active):not(:active){color:var(--jte-button-hovered-text);border-color:var(--jte-button-hovered-border);background-color:var(--jte-button-hovered-background)}.juit-tiny-edit .-jte-root .-jte-placeholder{position:absolute;font-style:italic;padding:.25em;pointer-events:none;color:var(--jte-inactive)}.juit-tiny-edit .-jte-root .-jte-editor{width:100%;box-sizing:border-box;padding:.25em;white-space:pre-wrap;border-radius:var(--jte-border-radius);border:1px solid var(--jte-border)}.juit-tiny-edit .-jte-root .-jte-editor:focus{outline:1px solid var(--jte-active);border:1px solid var(--jte-active)}.juit-tiny-edit .-jte-root .-jte-editor a{color:var(--jte-link)}.juit-tiny-edit .-jte-root .-jte-editor link[rel=mention]{display:inline;cursor:default;-webkit-user-select:none;user-select:none;font-weight:400;font-style:normal;text-decoration:none;font-size:.85em;overflow:hidden}.juit-tiny-edit .-jte-root .-jte-editor link[rel=mention]:before{padding:0 .25em;content:"";background-color:var(--jte-mention);border:1px solid var(--jte-border);border-radius:var(--jte-border-radius);border-right:none;border-top-right-radius:0;border-bottom-right-radius:0}.juit-tiny-edit .-jte-root .-jte-editor link[rel=mention]:after{content:attr(title);padding:0 .5em 0 .25em;background-color:var(--jte-shade);border:1px solid var(--jte-border);border-radius:var(--jte-border-radius);border-left:none;border-top-left-radius:0;border-bottom-left-radius:0}.juit-tiny-edit .-jte-root .-jte-mentions-popup,.juit-tiny-edit .-jte-root .-jte-links-popup{display:none;position:absolute;z-index:1000;font-size:.85em;background-color:var(--jte-popup);border-radius:var(--jte-border-radius);border:1px solid var(--jte-border)}.juit-tiny-edit .-jte-root .-jte-mentions-popup{overflow:hidden;cursor:pointer;min-width:8em}.juit-tiny-edit .-jte-root .-jte-mentions-popup ul{overflow:scroll;max-height:calc(7.5em + 3px);white-space:nowrap;margin:0;padding:0}.juit-tiny-edit .-jte-root .-jte-mentions-popup li{list-style-type:none;border-top:1px solid transparent;border-bottom:1px solid transparent;color:var(--jte-inactive)}.juit-tiny-edit .-jte-root .-jte-mentions-popup li .-jte-mentions-entry{padding-block:.125em;padding-left:.25em;padding-right:1.5em;border-left-width:.5em;border-left-style:solid;border-left-color:transparent}.juit-tiny-edit .-jte-root .-jte-mentions-popup li .-jte-mentions-entry .-jte-mentions-ref{margin-left:.5em;font-size:.85em;font-style:italic}.juit-tiny-edit .-jte-root .-jte-mentions-popup li.-jte-mentions-selected{color:var(--jte-input-text);background-color:var(--jte-shade);border-color:var(--jte-input-border)}.juit-tiny-edit .-jte-root .-jte-mentions-popup li.-jte-mentions-selected .-jte-mentions-entry{border-color:var(--jte-mention)}.juit-tiny-edit .-jte-root .-jte-mentions-popup li:first-child{border-top:none}.juit-tiny-edit .-jte-root .-jte-mentions-popup li:last-child{border-bottom:none}.juit-tiny-edit .-jte-root .-jte-mentions-popup .-jte-mentions-waiting,.juit-tiny-edit .-jte-root .-jte-mentions-popup .-jte-mentions-none{font-size:.85em;color:var(--jte-inactive);border-left-width:.5em;border-left-style:solid;border-color:transparent;padding-block:.125em;padding-inline:.25em}.juit-tiny-edit .-jte-root .-jte-mentions-popup .-jte-mentions-waiting{font-style:italic}.juit-tiny-edit .-jte-root .-jte-mentions-popup .-jte-mentions-none{border-color:var(--jte-shade)}.juit-tiny-edit .-jte-root .-jte-links-popup{-webkit-user-select:none;user-select:none;padding:.125em;width:20em;flex-direction:column}.juit-tiny-edit .-jte-root .-jte-links-popup div{display:flex;flex-direction:row;margin:.25em;position:relative;background-color:var(--jte-input-background);border-radius:var(--jte-border-radius)}.juit-tiny-edit .-jte-root .-jte-links-popup .-jte-icon{display:block;position:absolute;top:1px;left:1px;width:1.25em;height:1.25em;padding:.125em .25em;margin:0;color:var(--jte-input-text);background-color:var(--jte-input-shade);border:none;border-radius:calc(var(--jte-border-radius) - 1px);border-top-right-radius:0;border-bottom-right-radius:0;border-right:none}.juit-tiny-edit .-jte-root .-jte-links-popup input{display:block;flex-grow:1;font-size:1em;height:1.25em;padding:.125em .25em .125em 2.25em;margin:0;color:var(--jte-input-text);border:1px solid var(--jte-input-border);border-radius:var(--jte-border-radius);background-color:transparent}.juit-tiny-edit .-jte-root .-jte-links-popup input:focus{outline:1px solid var(--jte-active);border:1px solid var(--jte-active)}.juit-tiny-edit .-jte-root .-jte-links-popup .-jte-error .-jte-icon,.juit-tiny-edit .-jte-root .-jte-links-popup .-jte-error input{color:var(--jte-error)}.juit-tiny-edit.-jte-editable .-jte-editor{padding-bottom:2.5em;margin-bottom:-2.5em}.juit-tiny-edit.-jte-editable .-jte-toolbox{display:flex}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@juit/vue-tiny-editor",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"./style.css": "./dist/style.css"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "npm run check && vite build --mode=demo && vite build --mode=production",
|
|
14
|
+
"build-demo": "npm run check && vite build --mode=demo",
|
|
15
|
+
"build-lib": "npm run check && vite build --mode=production",
|
|
16
|
+
"check": "vue-tsc --build --force && vitest run --coverage && eslint",
|
|
17
|
+
"deploy-dev": "wrangler deploy",
|
|
18
|
+
"deploy-prod": "wrangler deploy --env=production",
|
|
19
|
+
"dev": "vite",
|
|
20
|
+
"test": "vitest"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"vue": "^3.5.12"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@plugjs/eslint-plugin": "^0.2.24",
|
|
27
|
+
"@types/node": "<21",
|
|
28
|
+
"@vitejs/plugin-vue": "^5.1.4",
|
|
29
|
+
"@vitest/coverage-v8": "^2.1.4",
|
|
30
|
+
"@vue/test-utils": "^2.4.6",
|
|
31
|
+
"eslint": "^9.14.0",
|
|
32
|
+
"eslint-plugin-vue": "^9.30.0",
|
|
33
|
+
"jsdom": "^25.0.1",
|
|
34
|
+
"typescript": "^5.6.3",
|
|
35
|
+
"vite": "^5.4.10",
|
|
36
|
+
"vite-plugin-dts": "^4.3.0",
|
|
37
|
+
"vite-svg-loader": "^5.1.0",
|
|
38
|
+
"vitest": "^2.1.4",
|
|
39
|
+
"vue": "^3.5.12",
|
|
40
|
+
"vue-tsc": "^2.1.10"
|
|
41
|
+
},
|
|
42
|
+
"author": "Juit Developers <developers@juit.com>",
|
|
43
|
+
"license": "Apache-2.0",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+ssh://git@github.com/juitnow/juit-vue-tiny-editor.git"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"vue",
|
|
50
|
+
"mentions",
|
|
51
|
+
"editor",
|
|
52
|
+
"rich-text"
|
|
53
|
+
],
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/juitnow/juit-vue-tiny-editor/issues"
|
|
56
|
+
},
|
|
57
|
+
"directories": {
|
|
58
|
+
"test": "test"
|
|
59
|
+
},
|
|
60
|
+
"files": [
|
|
61
|
+
"dist"
|
|
62
|
+
]
|
|
63
|
+
}
|