@connextar/house 0.1.1 → 0.3.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/README.md +286 -28
- package/dist/changelog/changelog.d.ts +60 -0
- package/dist/changelog/changelog.d.ts.map +1 -0
- package/dist/changelog/changelog.js +61 -0
- package/dist/changelog/changelog.js.map +1 -0
- package/dist/changelog/index.d.ts +2 -0
- package/dist/changelog/index.d.ts.map +1 -0
- package/dist/changelog/index.js +2 -0
- package/dist/changelog/index.js.map +1 -0
- package/dist/editor/block-handle.d.ts +15 -0
- package/dist/editor/block-handle.d.ts.map +1 -0
- package/dist/editor/block-handle.js +106 -0
- package/dist/editor/block-handle.js.map +1 -0
- package/dist/editor/blocks.d.ts +35 -0
- package/dist/editor/blocks.d.ts.map +1 -0
- package/dist/editor/blocks.js +114 -0
- package/dist/editor/blocks.js.map +1 -0
- package/dist/editor/bubble-toolbar.d.ts +11 -0
- package/dist/editor/bubble-toolbar.d.ts.map +1 -0
- package/dist/editor/bubble-toolbar.js +87 -0
- package/dist/editor/bubble-toolbar.js.map +1 -0
- package/dist/editor/content-index.d.ts +2 -0
- package/dist/editor/content-index.d.ts.map +1 -0
- package/dist/editor/content-index.js +2 -0
- package/dist/editor/content-index.js.map +1 -0
- package/dist/editor/content.d.ts +30 -0
- package/dist/editor/content.d.ts.map +1 -0
- package/dist/editor/content.js +256 -0
- package/dist/editor/content.js.map +1 -0
- package/dist/editor/editor.d.ts +52 -0
- package/dist/editor/editor.d.ts.map +1 -0
- package/dist/editor/editor.js +160 -0
- package/dist/editor/editor.js.map +1 -0
- package/dist/editor/emoji-index.d.ts +2 -0
- package/dist/editor/emoji-index.d.ts.map +1 -0
- package/dist/editor/emoji-index.js +2 -0
- package/dist/editor/emoji-index.js.map +1 -0
- package/dist/editor/emoji-picker.d.ts +16 -0
- package/dist/editor/emoji-picker.d.ts.map +1 -0
- package/dist/editor/emoji-picker.js +37 -0
- package/dist/editor/emoji-picker.js.map +1 -0
- package/dist/editor/emoji.d.ts +18 -0
- package/dist/editor/emoji.d.ts.map +1 -0
- package/dist/editor/emoji.js +418 -0
- package/dist/editor/emoji.js.map +1 -0
- package/dist/editor/index.d.ts +5 -0
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/editor/index.js +6 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/editor/suggestion-popup.d.ts +17 -0
- package/dist/editor/suggestion-popup.d.ts.map +1 -0
- package/dist/editor/suggestion-popup.js +57 -0
- package/dist/editor/suggestion-popup.js.map +1 -0
- package/dist/editor/suggestions.d.ts +93 -0
- package/dist/editor/suggestions.d.ts.map +1 -0
- package/dist/editor/suggestions.js +179 -0
- package/dist/editor/suggestions.js.map +1 -0
- package/dist/editor/ui.d.ts +54 -0
- package/dist/editor/ui.d.ts.map +1 -0
- package/dist/editor/ui.js +58 -0
- package/dist/editor/ui.js.map +1 -0
- package/dist/house.css +333 -0
- package/dist/internal/cx.d.ts +3 -0
- package/dist/internal/cx.d.ts.map +1 -0
- package/dist/internal/cx.js +5 -0
- package/dist/internal/cx.js.map +1 -0
- package/dist/internal/next-link.d.ts +23 -0
- package/dist/internal/next-link.d.ts.map +1 -0
- package/dist/internal/next-link.js +3 -0
- package/dist/internal/next-link.js.map +1 -0
- package/dist/markdown/guide.d.ts +98 -0
- package/dist/markdown/guide.d.ts.map +1 -0
- package/dist/markdown/guide.js +328 -0
- package/dist/markdown/guide.js.map +1 -0
- package/dist/markdown/index.d.ts +4 -0
- package/dist/markdown/index.d.ts.map +1 -0
- package/dist/markdown/index.js +4 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/markdown/table-view.d.ts +28 -0
- package/dist/markdown/table-view.d.ts.map +1 -0
- package/dist/markdown/table-view.js +25 -0
- package/dist/markdown/table-view.js.map +1 -0
- package/dist/markdown/table.d.ts +35 -0
- package/dist/markdown/table.d.ts.map +1 -0
- package/dist/markdown/table.js +79 -0
- package/dist/markdown/table.js.map +1 -0
- package/dist/release/changelog.d.ts +72 -0
- package/dist/release/changelog.d.ts.map +1 -0
- package/dist/release/changelog.js +92 -0
- package/dist/release/changelog.js.map +1 -0
- package/dist/release/index.d.ts +3 -0
- package/dist/release/index.d.ts.map +1 -0
- package/dist/release/index.js +3 -0
- package/dist/release/index.js.map +1 -0
- package/dist/release/version.d.ts +33 -0
- package/dist/release/version.d.ts.map +1 -0
- package/dist/release/version.js +57 -0
- package/dist/release/version.js.map +1 -0
- package/dist/trail/index.d.ts +3 -0
- package/dist/trail/index.d.ts.map +1 -0
- package/dist/trail/index.js +3 -0
- package/dist/trail/index.js.map +1 -0
- package/dist/trail/rules.d.ts +44 -0
- package/dist/trail/rules.d.ts.map +1 -0
- package/dist/trail/rules.js +74 -0
- package/dist/trail/rules.js.map +1 -0
- package/dist/trail/trail.d.ts +49 -0
- package/dist/trail/trail.d.ts.map +1 -0
- package/dist/trail/trail.js +188 -0
- package/dist/trail/trail.js.map +1 -0
- package/dist/ux/errors.d.ts +26 -0
- package/dist/ux/errors.d.ts.map +1 -0
- package/dist/ux/errors.js +33 -0
- package/dist/ux/errors.js.map +1 -0
- package/dist/ux/feedback.d.ts +23 -0
- package/dist/ux/feedback.d.ts.map +1 -0
- package/dist/ux/feedback.js +22 -0
- package/dist/ux/feedback.js.map +1 -0
- package/dist/ux/index.d.ts +20 -0
- package/dist/ux/index.d.ts.map +1 -0
- package/dist/ux/index.js +20 -0
- package/dist/ux/index.js.map +1 -0
- package/dist/ux/navigation-index.d.ts +10 -0
- package/dist/ux/navigation-index.d.ts.map +1 -0
- package/dist/ux/navigation-index.js +10 -0
- package/dist/ux/navigation-index.js.map +1 -0
- package/dist/ux/navigation-progress.d.ts +53 -0
- package/dist/ux/navigation-progress.d.ts.map +1 -0
- package/dist/ux/navigation-progress.js +245 -0
- package/dist/ux/navigation-progress.js.map +1 -0
- package/dist/ux/optimistic.d.ts +139 -0
- package/dist/ux/optimistic.d.ts.map +1 -0
- package/dist/ux/optimistic.js +242 -0
- package/dist/ux/optimistic.js.map +1 -0
- package/dist/ux/use-action.d.ts +60 -0
- package/dist/ux/use-action.d.ts.map +1 -0
- package/dist/ux/use-action.js +100 -0
- package/dist/ux/use-action.js.map +1 -0
- package/dist/ux/use-optimistic-list.d.ts +69 -0
- package/dist/ux/use-optimistic-list.d.ts.map +1 -0
- package/dist/ux/use-optimistic-list.js +115 -0
- package/dist/ux/use-optimistic-list.js.map +1 -0
- package/dist/ux/use-optimistic-value.d.ts +40 -0
- package/dist/ux/use-optimistic-value.d.ts.map +1 -0
- package/dist/ux/use-optimistic-value.js +68 -0
- package/dist/ux/use-optimistic-value.js.map +1 -0
- package/docs/ReleaseProcess.md +315 -0
- package/package.json +83 -11
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Link } from "../internal/next-link.js";
|
|
3
|
+
import { cx } from "../internal/cx.js";
|
|
4
|
+
/**
|
|
5
|
+
* Read-only display for what `RichTextEditor` writes — the Markdown dialect the
|
|
6
|
+
* editor produces (headings, lists incl. task lists, quotes, code fences,
|
|
7
|
+
* bold/italic/strike/inline code, links, `@`-references, `#hashtags`), with a
|
|
8
|
+
* bare newline shown as a line break so legacy plain-text values in the same
|
|
9
|
+
* columns read exactly as they always did.
|
|
10
|
+
*
|
|
11
|
+
* Separate from a guide renderer deliberately: a help centre wants guide-scale
|
|
12
|
+
* headings and link rewriting; a note wants task lists, fences and line breaks.
|
|
13
|
+
* Folding both dialects and both type scales into one would serve neither well.
|
|
14
|
+
*
|
|
15
|
+
* This is a trust boundary: whatever is stored in a text column is drawn here,
|
|
16
|
+
* often in front of a *different* person. Everything comes out as React
|
|
17
|
+
* elements with checked hrefs — never as markup.
|
|
18
|
+
*
|
|
19
|
+
* Server-safe: no hooks, so views can render bodies without a client boundary.
|
|
20
|
+
* Styled by `.house-prose` in `@connextar/house/house.css`.
|
|
21
|
+
*/
|
|
22
|
+
export function RichTextContent({ content, className }) {
|
|
23
|
+
return _jsx("div", { className: cx("house-prose", className), children: renderBlocks(content) });
|
|
24
|
+
}
|
|
25
|
+
/* ── Inline ───────────────────────────────────────────────────────────── */
|
|
26
|
+
// Link labels exclude `[` and both halves are bounded: `[^\]]+` let every `[`
|
|
27
|
+
// of a long run swallow the rest and backtrack, so a 100 KB run of `[` stalled
|
|
28
|
+
// a server for seconds (AltEd security round 8, 8.1).
|
|
29
|
+
const INLINE = /(`([^`\n]+)`)|(\[([^[\]\n]{1,500})\]\(([^)\s]{1,2048})\))|(\*\*([^*]+)\*\*)|(~~([^~]+)~~)|(\*([^*\n]+)\*)|(\b_([^_\n]+)_\b)|((?:^|(?<=[\s(]))#([a-z0-9][a-z0-9_-]{0,47})\b)/gim;
|
|
30
|
+
const ESCAPED = /\\([\\`*_{}[\]()#+\-.!>~|])/g;
|
|
31
|
+
/** The editor's serialiser backslash-escapes Markdown punctuation in literal text (`\#`, `\*`). */
|
|
32
|
+
function unescapeMarkdown(text) {
|
|
33
|
+
return text.replace(ESCAPED, "$1");
|
|
34
|
+
}
|
|
35
|
+
/** Only hrefs safe to hand a browser; anything else renders as plain text. */
|
|
36
|
+
export function safeHref(href) {
|
|
37
|
+
if (href.startsWith("/")) {
|
|
38
|
+
// "Internal" must mean same-origin after every normalisation a browser
|
|
39
|
+
// applies — `//evil.com` is protocol-relative and `/\evil.com` becomes
|
|
40
|
+
// `https://evil.com/` because browsers treat `\` as `/` in special-scheme
|
|
41
|
+
// URLs. Resolving against a sentinel origin catches all of these at once.
|
|
42
|
+
try {
|
|
43
|
+
return new URL(href, "https://internal.invalid").origin === "https://internal.invalid" ? href : null;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return ["http:", "https:", "mailto:"].includes(new URL(href).protocol) ? href : null;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function renderInline(text, keyPrefix) {
|
|
57
|
+
const nodes = [];
|
|
58
|
+
let last = 0;
|
|
59
|
+
let n = 0;
|
|
60
|
+
INLINE.lastIndex = 0;
|
|
61
|
+
for (const match of text.matchAll(INLINE)) {
|
|
62
|
+
const index = match.index ?? 0;
|
|
63
|
+
if (index > last)
|
|
64
|
+
nodes.push(unescapeMarkdown(text.slice(last, index)));
|
|
65
|
+
const key = `${keyPrefix}-${n++}`;
|
|
66
|
+
if (match[1]) {
|
|
67
|
+
nodes.push(_jsx("code", { children: match[2] }, key));
|
|
68
|
+
}
|
|
69
|
+
else if (match[3]) {
|
|
70
|
+
const href = safeHref(match[5] ?? "");
|
|
71
|
+
const label = renderInline(match[4] ?? "", key);
|
|
72
|
+
if (!href) {
|
|
73
|
+
nodes.push(_jsx("span", { children: label }, key));
|
|
74
|
+
}
|
|
75
|
+
else if (href.startsWith("/")) {
|
|
76
|
+
// Internal — an `@`-reference from the editor, or a hand-written app
|
|
77
|
+
// link. Marked so the navigation trail carries the way back across the
|
|
78
|
+
// jump: the trail provider listens for clicks on the attribute, which
|
|
79
|
+
// is what lets this renderer stay hook-free.
|
|
80
|
+
nodes.push(_jsx(Link, { href: href, "data-trail-carry": "", children: label }, key));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
nodes.push(_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", children: label }, key));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (match[6]) {
|
|
87
|
+
nodes.push(_jsx("strong", { children: renderInline(match[7] ?? "", key) }, key));
|
|
88
|
+
}
|
|
89
|
+
else if (match[8]) {
|
|
90
|
+
nodes.push(_jsx("s", { children: renderInline(match[9] ?? "", key) }, key));
|
|
91
|
+
}
|
|
92
|
+
else if (match[10]) {
|
|
93
|
+
nodes.push(_jsx("em", { children: renderInline(match[11] ?? "", key) }, key));
|
|
94
|
+
}
|
|
95
|
+
else if (match[12]) {
|
|
96
|
+
nodes.push(_jsx("em", { children: renderInline(match[13] ?? "", key) }, key));
|
|
97
|
+
}
|
|
98
|
+
else if (match[14]) {
|
|
99
|
+
nodes.push(_jsxs("span", { className: "house-hashtag", children: ["#", match[15]] }, key));
|
|
100
|
+
}
|
|
101
|
+
last = index + match[0].length;
|
|
102
|
+
}
|
|
103
|
+
if (last < text.length)
|
|
104
|
+
nodes.push(unescapeMarkdown(text.slice(last)));
|
|
105
|
+
return nodes;
|
|
106
|
+
}
|
|
107
|
+
/** Paragraph text where a bare newline is a visible break. */
|
|
108
|
+
function renderWithBreaks(lines, keyPrefix) {
|
|
109
|
+
return lines.flatMap((line, index) => {
|
|
110
|
+
const rendered = renderInline(line, `${keyPrefix}-l${index}`);
|
|
111
|
+
return index === 0 ? rendered : [_jsx("br", {}, `${keyPrefix}-b${index}`), ...rendered];
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const LIST_LINE = /^(\s*)(?:([-*+])|(\d+)[.)])\s+(?:\[([ xX])\]\s+)?(.*)$/;
|
|
115
|
+
function parseListLine(line) {
|
|
116
|
+
const match = line.match(LIST_LINE);
|
|
117
|
+
if (!match)
|
|
118
|
+
return null;
|
|
119
|
+
return {
|
|
120
|
+
indent: match[1]?.length ?? 0,
|
|
121
|
+
ordered: match[3] !== undefined,
|
|
122
|
+
task: match[4] === undefined ? null : match[4] !== " ",
|
|
123
|
+
text: match[5] ?? "",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** Build a (possibly nested) list from consecutive list lines. */
|
|
127
|
+
function renderList(items, keyPrefix) {
|
|
128
|
+
const render = (from, to, key) => {
|
|
129
|
+
const first = items[from];
|
|
130
|
+
if (!first)
|
|
131
|
+
return null;
|
|
132
|
+
const rows = [];
|
|
133
|
+
let i = from;
|
|
134
|
+
let n = 0;
|
|
135
|
+
while (i < to) {
|
|
136
|
+
const item = items[i];
|
|
137
|
+
if (!item)
|
|
138
|
+
break;
|
|
139
|
+
// Deeper lines nest under the previous row.
|
|
140
|
+
let childEnd = i + 1;
|
|
141
|
+
while (childEnd < to && (items[childEnd]?.indent ?? 0) > first.indent)
|
|
142
|
+
childEnd++;
|
|
143
|
+
const children = childEnd > i + 1 ? render(i + 1, childEnd, `${key}-c${n}`) : null;
|
|
144
|
+
rows.push(item.task !== null ? (_jsxs("li", { className: cx(item.task && "house-task-done"), children: [_jsx("input", { type: "checkbox", checked: item.task, readOnly: true, disabled: true, "aria-label": item.text }), _jsxs("span", { children: [renderInline(item.text, `${key}-i${n}`), children] })] }, n)) : (_jsxs("li", { children: [renderInline(item.text, `${key}-i${n}`), children] }, n)));
|
|
145
|
+
i = childEnd;
|
|
146
|
+
n++;
|
|
147
|
+
}
|
|
148
|
+
if (first.task !== null) {
|
|
149
|
+
return (_jsx("ul", { className: "house-task-list", children: rows }, key));
|
|
150
|
+
}
|
|
151
|
+
return first.ordered ? _jsx("ol", { children: rows }, key) : _jsx("ul", { children: rows }, key);
|
|
152
|
+
};
|
|
153
|
+
return render(0, items.length, keyPrefix);
|
|
154
|
+
}
|
|
155
|
+
/* ── Blocks ───────────────────────────────────────────────────────────── */
|
|
156
|
+
const RULE = /^(-{3,}|\*{3,}|_{3,})$/;
|
|
157
|
+
const HEADING = /^(#{1,6})\s+(.*)$/;
|
|
158
|
+
function renderBlocks(content) {
|
|
159
|
+
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
160
|
+
const at = (index) => lines[index] ?? "";
|
|
161
|
+
const blocks = [];
|
|
162
|
+
let i = 0;
|
|
163
|
+
let key = 0;
|
|
164
|
+
while (i < lines.length) {
|
|
165
|
+
const line = at(i);
|
|
166
|
+
const trimmed = line.trim();
|
|
167
|
+
if (trimmed === "") {
|
|
168
|
+
i++;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
// Code fence — verbatim until the closing fence (or the end).
|
|
172
|
+
if (trimmed.startsWith("```")) {
|
|
173
|
+
const body = [];
|
|
174
|
+
i++;
|
|
175
|
+
while (i < lines.length && !at(i).trim().startsWith("```")) {
|
|
176
|
+
body.push(at(i));
|
|
177
|
+
i++;
|
|
178
|
+
}
|
|
179
|
+
i++; // closing fence
|
|
180
|
+
blocks.push(_jsx("pre", { children: _jsx("code", { children: body.join("\n") }) }, key++));
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (RULE.test(trimmed)) {
|
|
184
|
+
blocks.push(_jsx("hr", {}, key++));
|
|
185
|
+
i++;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const heading = trimmed.match(HEADING);
|
|
189
|
+
if (heading) {
|
|
190
|
+
const level = Math.min(heading[1]?.length ?? 1, 3);
|
|
191
|
+
const nodes = renderInline(heading[2] ?? "", `h${key}`);
|
|
192
|
+
blocks.push(level === 1 ? (_jsx("h1", { children: nodes }, key++)) : level === 2 ? (_jsx("h2", { children: nodes }, key++)) : (_jsx("h3", { children: nodes }, key++)));
|
|
193
|
+
i++;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (trimmed.startsWith(">")) {
|
|
197
|
+
const quote = [];
|
|
198
|
+
while (i < lines.length && at(i).trim().startsWith(">")) {
|
|
199
|
+
quote.push(at(i).trim().replace(/^>\s?/, ""));
|
|
200
|
+
i++;
|
|
201
|
+
}
|
|
202
|
+
blocks.push(_jsx("blockquote", { children: renderWithBreaks(quote, `q${key}`) }, key++));
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (parseListLine(line)) {
|
|
206
|
+
const items = [];
|
|
207
|
+
while (i < lines.length) {
|
|
208
|
+
const item = parseListLine(at(i));
|
|
209
|
+
if (!item)
|
|
210
|
+
break;
|
|
211
|
+
items.push(item);
|
|
212
|
+
i++;
|
|
213
|
+
}
|
|
214
|
+
blocks.push(_jsx("div", { children: renderList(items, `list${key}`) }, key++));
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
// Paragraph — consecutive plain lines, each newline a visible break.
|
|
218
|
+
const para = [];
|
|
219
|
+
while (i < lines.length) {
|
|
220
|
+
const next = at(i);
|
|
221
|
+
const t = next.trim();
|
|
222
|
+
if (t === "" ||
|
|
223
|
+
t.startsWith("```") ||
|
|
224
|
+
t.startsWith(">") ||
|
|
225
|
+
HEADING.test(t) ||
|
|
226
|
+
RULE.test(t) ||
|
|
227
|
+
LIST_LINE.test(next)) {
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
para.push(next);
|
|
231
|
+
i++;
|
|
232
|
+
}
|
|
233
|
+
blocks.push(_jsx("p", { children: renderWithBreaks(para, `p${key}`) }, key++));
|
|
234
|
+
}
|
|
235
|
+
return blocks;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* A plain-text preview of a Markdown body — for list cards and search rows.
|
|
239
|
+
* Strips block and inline syntax rather than rendering it.
|
|
240
|
+
*/
|
|
241
|
+
export function richTextPreview(content, max = 160) {
|
|
242
|
+
// A preview needs the start of the note, not all 100 KB of it.
|
|
243
|
+
const text = content
|
|
244
|
+
.slice(0, Math.max(2000, max * 12))
|
|
245
|
+
.replace(/```[\s\S]*?(```|$)/g, " ")
|
|
246
|
+
.replace(/^#{1,6}\s+/gm, "")
|
|
247
|
+
.replace(/^\s*(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?/gm, "")
|
|
248
|
+
.replace(/^>\s?/gm, "")
|
|
249
|
+
.replace(/\[([^[\]\n]{1,500})\]\([^)\s]{1,2048}\)/g, "$1")
|
|
250
|
+
.replace(/(\*\*|~~|`|\*|_)/g, "")
|
|
251
|
+
.replace(ESCAPED, "$1")
|
|
252
|
+
.replace(/\s+/g, " ")
|
|
253
|
+
.trim();
|
|
254
|
+
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/editor/content.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type SuggestionSources } from "./suggestions.js";
|
|
2
|
+
/**
|
|
3
|
+
* Notion-style writing anywhere an app asks for prose: notes, descriptions,
|
|
4
|
+
* instructions, feedback.
|
|
5
|
+
*
|
|
6
|
+
* **Blocks, not a toolbar.** There is no standing formatting bar. Formatting
|
|
7
|
+
* appears as a bubble over a text selection (with Turn into); new blocks come
|
|
8
|
+
* from typing `/` — a filterable menu — or from the `+` beside any block; every
|
|
9
|
+
* block has a grip to drag it elsewhere or open its menu (turn into, duplicate,
|
|
10
|
+
* move up, move down, delete); `:` completes an emoji shortcode. `@` references
|
|
11
|
+
* and `#` tags work when the app passes `sources`. Markdown shortcuts still work
|
|
12
|
+
* while typing (`# `, `- `, `1. `, `[] `, `> `, `**bold**`, "```").
|
|
13
|
+
*
|
|
14
|
+
* It reads and writes **Markdown strings**, deliberately: the fields it replaces
|
|
15
|
+
* are plain-text columns, Markdown needs no sanitiser because nothing HTML-shaped
|
|
16
|
+
* is stored or rendered, and old plain-text values open unchanged (a bare
|
|
17
|
+
* newline stays a line break). That is also why the block vocabulary is exactly
|
|
18
|
+
* what Markdown can say. Read-only display is `RichTextContent`.
|
|
19
|
+
*/
|
|
20
|
+
export interface RichTextEditorProps {
|
|
21
|
+
value: string;
|
|
22
|
+
onChange: (markdown: string) => void;
|
|
23
|
+
onBlur?: () => void;
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
/**
|
|
26
|
+
* "field" sits inside a form like a big textarea (bubble and `/`, no gutter);
|
|
27
|
+
* "document" is a full-page editor (block grips, wider gutter, taller).
|
|
28
|
+
*/
|
|
29
|
+
variant?: "field" | "document";
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
autoFocus?: boolean;
|
|
32
|
+
/** Where `@` and `#` get their rows. Omit either and that trigger stays inert. */
|
|
33
|
+
sources?: SuggestionSources;
|
|
34
|
+
/**
|
|
35
|
+
* How a link in the text is followed. "click": an `@`-reference opens on a
|
|
36
|
+
* plain click, as in Notion — for a page that autosaves. "modifier" (the
|
|
37
|
+
* default): only Ctrl/⌘-click follows, so a form with unsaved fields is never
|
|
38
|
+
* left by a stray click on a mention. Any link opens with Ctrl/⌘.
|
|
39
|
+
*/
|
|
40
|
+
followLinks?: "click" | "modifier";
|
|
41
|
+
/** Which internal links count as references for `followLinks="click"`. Defaults to an `@` label. */
|
|
42
|
+
isReference?: (href: string, text: string) => boolean;
|
|
43
|
+
/** How today's date is written by the `/` menu. */
|
|
44
|
+
formatDate?: (date: Date) => string;
|
|
45
|
+
id?: string;
|
|
46
|
+
className?: string;
|
|
47
|
+
"aria-invalid"?: boolean;
|
|
48
|
+
"aria-describedby"?: string;
|
|
49
|
+
"aria-label"?: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function RichTextEditor({ value, onChange, onBlur, placeholder, variant, disabled, autoFocus, sources, followLinks, isReference, formatDate, id, className, ...aria }: RichTextEditorProps): import("react").JSX.Element;
|
|
52
|
+
//# sourceMappingURL=editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../src/editor/editor.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAML,KAAK,iBAAiB,EAEvB,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kFAAkF;IAClF,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IACnC,oGAAoG;IACpG,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACtD,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC;IACpC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,WAAgC,EAChC,OAAiB,EACjB,QAAgB,EAChB,SAAiB,EACjB,OAAO,EACP,WAAwB,EACxB,WAAgC,EAChC,UAAU,EACV,EAAE,EACF,SAAS,EACT,GAAG,IAAI,EACR,EAAE,mBAAmB,+BA2KrB"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { TaskItem } from "@tiptap/extension-task-item";
|
|
4
|
+
import { TaskList } from "@tiptap/extension-task-list";
|
|
5
|
+
import { Placeholder } from "@tiptap/extensions";
|
|
6
|
+
import { EditorContent, useEditor } from "@tiptap/react";
|
|
7
|
+
import { StarterKit } from "@tiptap/starter-kit";
|
|
8
|
+
import { useRouter } from "next/navigation.js";
|
|
9
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
10
|
+
import { Markdown } from "tiptap-markdown";
|
|
11
|
+
import { cx } from "../internal/cx.js";
|
|
12
|
+
import { carryTrail } from "../trail/trail.js";
|
|
13
|
+
import { BlockHandle } from "./block-handle.js";
|
|
14
|
+
import { BubbleToolbar } from "./bubble-toolbar.js";
|
|
15
|
+
import { SuggestionPopup } from "./suggestion-popup.js";
|
|
16
|
+
import { createSuggestions, DEFAULT_SUGGESTION_OPTIONS, } from "./suggestions.js";
|
|
17
|
+
const defaultIsReference = (_href, text) => text.startsWith("@");
|
|
18
|
+
export function RichTextEditor({ value, onChange, onBlur, placeholder = "Write something…", variant = "field", disabled = false, autoFocus = false, sources, followLinks = "modifier", isReference = defaultIsReference, formatDate, id, className, ...aria }) {
|
|
19
|
+
const router = useRouter();
|
|
20
|
+
// The last Markdown this editor emitted (or accepted). Guards the external
|
|
21
|
+
// value sync below from re-parsing the document on every keystroke echo.
|
|
22
|
+
const lastMarkdown = useRef(value);
|
|
23
|
+
const [session, setSession] = useState(null);
|
|
24
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
25
|
+
// The extension is built once per editor and reads these through refs, which
|
|
26
|
+
// an every-render effect keeps pointed at the current render's values.
|
|
27
|
+
const uiRef = useRef({ onOpen: () => { }, onClose: () => { }, onKeyDown: () => false });
|
|
28
|
+
const sourcesRef = useRef(sources ?? {});
|
|
29
|
+
const optionsRef = useRef({
|
|
30
|
+
...DEFAULT_SUGGESTION_OPTIONS,
|
|
31
|
+
...(formatDate ? { formatDate } : {}),
|
|
32
|
+
});
|
|
33
|
+
const placeholderRef = useRef(placeholder);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
sourcesRef.current = sources ?? {};
|
|
36
|
+
optionsRef.current = { ...DEFAULT_SUGGESTION_OPTIONS, ...(formatDate ? { formatDate } : {}) };
|
|
37
|
+
placeholderRef.current = placeholder;
|
|
38
|
+
uiRef.current = {
|
|
39
|
+
onOpen: (next) => {
|
|
40
|
+
setSession(next);
|
|
41
|
+
setActiveIndex((current) => Math.min(current, Math.max(0, next.items.length - 1)));
|
|
42
|
+
},
|
|
43
|
+
onClose: () => {
|
|
44
|
+
setSession(null);
|
|
45
|
+
setActiveIndex(0);
|
|
46
|
+
},
|
|
47
|
+
onKeyDown: (event) => {
|
|
48
|
+
if (!session || session.items.length === 0)
|
|
49
|
+
return false;
|
|
50
|
+
if (event.key === "ArrowDown") {
|
|
51
|
+
setActiveIndex((activeIndex + 1) % session.items.length);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (event.key === "ArrowUp") {
|
|
55
|
+
setActiveIndex((activeIndex - 1 + session.items.length) % session.items.length);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (event.key === "Enter" || event.key === "Tab") {
|
|
59
|
+
const item = session.items[activeIndex];
|
|
60
|
+
if (item)
|
|
61
|
+
session.command(item);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (event.key === "Escape") {
|
|
65
|
+
setSession(null);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
const extensions = useMemo(() => [
|
|
73
|
+
StarterKit.configure({
|
|
74
|
+
heading: { levels: [1, 2, 3] },
|
|
75
|
+
link: { openOnClick: false, autolink: true },
|
|
76
|
+
dropcursor: { color: "var(--house-primary, var(--primary))", width: 2 },
|
|
77
|
+
}),
|
|
78
|
+
TaskList,
|
|
79
|
+
TaskItem.configure({ nested: true }),
|
|
80
|
+
Placeholder.configure({
|
|
81
|
+
// The empty document says what the field is for; an empty block in a
|
|
82
|
+
// document that has content says how to get a block — the Notion hint.
|
|
83
|
+
placeholder: ({ editor: current, node }) => {
|
|
84
|
+
if (current.isEmpty)
|
|
85
|
+
return placeholderRef.current;
|
|
86
|
+
if (variant === "document" && node.type.name === "paragraph")
|
|
87
|
+
return "Type / for blocks";
|
|
88
|
+
return "";
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
Markdown.configure({
|
|
92
|
+
// No HTML in, no HTML out: pasted markup is flattened to text, so the
|
|
93
|
+
// stored string can never smuggle an element past a renderer.
|
|
94
|
+
html: false,
|
|
95
|
+
// A bare newline stays a visible line break, which is what every legacy
|
|
96
|
+
// plain-text value in these columns meant by one.
|
|
97
|
+
breaks: true,
|
|
98
|
+
linkify: true,
|
|
99
|
+
transformPastedText: true,
|
|
100
|
+
}),
|
|
101
|
+
createSuggestions(uiRef, sourcesRef, optionsRef),
|
|
102
|
+
],
|
|
103
|
+
// The extension set is per mount; everything that changes is read through a ref.
|
|
104
|
+
[]);
|
|
105
|
+
const editor = useEditor({
|
|
106
|
+
extensions,
|
|
107
|
+
content: value,
|
|
108
|
+
editable: !disabled,
|
|
109
|
+
autofocus: autoFocus ? "end" : false,
|
|
110
|
+
// The editor renders client-side only; the server markup comes from the page.
|
|
111
|
+
immediatelyRender: false,
|
|
112
|
+
onUpdate: ({ editor: current }) => {
|
|
113
|
+
// tiptap-markdown registers no Storage augmentation, hence the cast.
|
|
114
|
+
const markdown = current.storage.markdown.getMarkdown();
|
|
115
|
+
lastMarkdown.current = markdown;
|
|
116
|
+
onChange(markdown);
|
|
117
|
+
},
|
|
118
|
+
onBlur: () => onBlur?.(),
|
|
119
|
+
});
|
|
120
|
+
// External resets (a form reset, switching records) re-seed the document; our
|
|
121
|
+
// own keystrokes come back identical and are skipped.
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (!editor || value === lastMarkdown.current)
|
|
124
|
+
return;
|
|
125
|
+
lastMarkdown.current = value;
|
|
126
|
+
editor.commands.setContent(value, { emitUpdate: false });
|
|
127
|
+
}, [editor, value]);
|
|
128
|
+
// Only on a real change, and silently: `setEditable` emits an update by
|
|
129
|
+
// default, which reported a change on every mount — a form turned dirty and
|
|
130
|
+
// an autosave fired the moment a note was opened, before anyone typed.
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (editor && editor.isEditable === disabled)
|
|
133
|
+
editor.setEditable(!disabled, false);
|
|
134
|
+
}, [editor, disabled]);
|
|
135
|
+
const select = (item) => session?.command(item);
|
|
136
|
+
/** Follow a link in the text — see `followLinks`. Internal ones carry the trail. */
|
|
137
|
+
const onClickCapture = (event) => {
|
|
138
|
+
const anchor = event.target instanceof Element ? event.target.closest("a[href]") : null;
|
|
139
|
+
if (!anchor)
|
|
140
|
+
return;
|
|
141
|
+
const href = anchor.getAttribute("href") ?? "";
|
|
142
|
+
const modifier = event.metaKey || event.ctrlKey;
|
|
143
|
+
const internal = href.startsWith("/") && !href.startsWith("//") && !href.startsWith("/\\");
|
|
144
|
+
const reference = internal && isReference(href, anchor.textContent ?? "");
|
|
145
|
+
if (!modifier && !(followLinks === "click" && reference))
|
|
146
|
+
return;
|
|
147
|
+
event.preventDefault();
|
|
148
|
+
event.stopPropagation();
|
|
149
|
+
if (internal) {
|
|
150
|
+
carryTrail();
|
|
151
|
+
router.push(href);
|
|
152
|
+
}
|
|
153
|
+
else if (/^https?:\/\//i.test(href)) {
|
|
154
|
+
window.open(href, "_blank", "noopener,noreferrer");
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
return (_jsxs("div", { id: id, ...aria, onClickCapture: onClickCapture, className: cx("house-rich-text relative text-sm", variant === "field" &&
|
|
158
|
+
"rounded-md border border-input bg-background shadow-xs focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2", variant === "document" && "house-rich-text--document", aria["aria-invalid"] && "border-destructive", disabled && "opacity-60", className), children: [_jsx(EditorContent, { editor: editor, className: "house-prose" }), editor && !disabled ? _jsx(BubbleToolbar, { editor: editor }) : null, editor && !disabled && variant === "document" ? _jsx(BlockHandle, { editor: editor }) : null, session ? (_jsx(SuggestionPopup, { items: session.items, activeIndex: activeIndex, clientRect: session.clientRect, onSelect: select, onHover: setActiveIndex })) : null] }));
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.js","sourceRoot":"","sources":["../../src/editor/editor.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emoji-index.d.ts","sourceRoot":"","sources":["../../src/editor/emoji-index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emoji-index.js","sourceRoot":"","sources":["../../src/editor/emoji-index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAoC,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
interface EmojiPickerProps {
|
|
3
|
+
value: string | null;
|
|
4
|
+
onChange: (emoji: string | null) => void;
|
|
5
|
+
/** The trigger — whatever shows the current icon (or "Add icon"). Must accept a ref. */
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
align?: "start" | "center" | "end";
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A page-icon picker in the Notion mould: a searchable grid of emoji grouped by
|
|
11
|
+
* kind, with Remove when one is set. Backed by the curated set, so it opens
|
|
12
|
+
* instantly and never downloads a database.
|
|
13
|
+
*/
|
|
14
|
+
export declare function EmojiPicker({ value, onChange, children, align }: EmojiPickerProps): import("react").JSX.Element;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=emoji-picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emoji-picker.d.ts","sourceRoot":"","sources":["../../src/editor/emoji-picker.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAM1D,UAAU,gBAAgB;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzC,wFAAwF;IACxF,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;CACpC;AAID;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAe,EAAE,EAAE,gBAAgB,+BAyF3F"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Search } from "lucide-react";
|
|
4
|
+
import { useMemo, useState } from "react";
|
|
5
|
+
import { cx } from "../internal/cx.js";
|
|
6
|
+
import { EMOJI, EMOJI_GROUP_LABELS, searchEmoji } from "./emoji.js";
|
|
7
|
+
import { ghostButtonClass, PopoverContent, PopoverRoot, PopoverTrigger, TextInput } from "./ui.js";
|
|
8
|
+
const GROUP_ORDER = ["smileys", "people", "nature", "food", "activities", "travel", "objects", "symbols"];
|
|
9
|
+
/**
|
|
10
|
+
* A page-icon picker in the Notion mould: a searchable grid of emoji grouped by
|
|
11
|
+
* kind, with Remove when one is set. Backed by the curated set, so it opens
|
|
12
|
+
* instantly and never downloads a database.
|
|
13
|
+
*/
|
|
14
|
+
export function EmojiPicker({ value, onChange, children, align = "start" }) {
|
|
15
|
+
const [open, setOpen] = useState(false);
|
|
16
|
+
const [query, setQuery] = useState("");
|
|
17
|
+
const grouped = useMemo(() => {
|
|
18
|
+
const term = query.trim();
|
|
19
|
+
if (term)
|
|
20
|
+
return [{ group: null, items: searchEmoji(term, 120) }];
|
|
21
|
+
return GROUP_ORDER.map((group) => ({ group, items: EMOJI.filter((entry) => entry.group === group) }));
|
|
22
|
+
}, [query]);
|
|
23
|
+
const pick = (entry) => {
|
|
24
|
+
onChange(entry.char);
|
|
25
|
+
setOpen(false);
|
|
26
|
+
setQuery("");
|
|
27
|
+
};
|
|
28
|
+
return (_jsxs(PopoverRoot, { open: open, onOpenChange: (next) => {
|
|
29
|
+
setOpen(next);
|
|
30
|
+
if (!next)
|
|
31
|
+
setQuery("");
|
|
32
|
+
}, children: [_jsx(PopoverTrigger, { asChild: true, children: children }), _jsxs(PopoverContent, { align: align, className: "w-80 p-2", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground" }), _jsx(TextInput, { value: query, onChange: (event) => setQuery(event.target.value), placeholder: "Search emoji\u2026", "aria-label": "Search emoji", className: "pl-8", autoFocus: true })] }), _jsx("div", { className: "mt-2 max-h-64 overflow-y-auto pr-1", children: grouped.every((section) => section.items.length === 0) ? (_jsxs("p", { className: "px-1 py-6 text-center text-sm text-muted-foreground", children: ["Nothing matches \u201C", query, "\u201D."] })) : (grouped.map((section) => section.items.length === 0 ? null : (_jsxs("div", { className: "mb-2", children: [section.group ? (_jsx("p", { className: "px-1 pb-1 text-[11px] font-semibold tracking-wide text-muted-foreground uppercase", children: EMOJI_GROUP_LABELS[section.group] })) : null, _jsx("div", { className: "grid grid-cols-8 gap-0.5", children: section.items.map((entry) => (_jsx("button", { type: "button", title: entry.name.replace(/_/g, " "), "aria-label": entry.name.replace(/_/g, " "), "aria-pressed": value === entry.char, onClick: () => pick(entry), className: cx("flex size-8 items-center justify-center rounded-md text-xl leading-none hover:bg-accent", value === entry.char && "bg-accent ring-1 ring-ring"), children: entry.char }, entry.name))) })] }, section.group ?? "results")))) }), value ? (_jsx("div", { className: "mt-1 flex justify-end border-t border-border pt-2", children: _jsx("button", { type: "button", className: `${ghostButtonClass} h-8 px-3`, onClick: () => {
|
|
33
|
+
onChange(null);
|
|
34
|
+
setOpen(false);
|
|
35
|
+
}, children: "Remove icon" }) })) : null] })] }));
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=emoji-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emoji-picker.js","sourceRoot":"","sources":["../../src/editor/emoji-picker.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type EmojiGroup = "smileys" | "people" | "nature" | "food" | "activities" | "travel" | "objects" | "symbols";
|
|
2
|
+
export interface EmojiEntry {
|
|
3
|
+
/** The character itself. */
|
|
4
|
+
char: string;
|
|
5
|
+
/** The `:shortcode:` name, lowercase, underscores. */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Extra search words beyond the name. */
|
|
8
|
+
keywords: string[];
|
|
9
|
+
group: EmojiGroup;
|
|
10
|
+
}
|
|
11
|
+
export declare const EMOJI_GROUP_LABELS: Record<EmojiGroup, string>;
|
|
12
|
+
export declare const EMOJI: EmojiEntry[];
|
|
13
|
+
/**
|
|
14
|
+
* Case-insensitive search over names and keywords. An empty query returns the
|
|
15
|
+
* whole set in catalogue order, which is what a picker wants to open with.
|
|
16
|
+
*/
|
|
17
|
+
export declare function searchEmoji(query: string, limit?: number): EmojiEntry[];
|
|
18
|
+
//# sourceMappingURL=emoji.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emoji.d.ts","sourceRoot":"","sources":["../../src/editor/emoji.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAEpH,MAAM,WAAW,UAAU;IACzB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CASzD,CAAC;AASF,eAAO,MAAM,KAAK,EAAE,UAAU,EA0X7B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,UAAU,EAAE,CAWnE"}
|