@stll/chat 0.1.40 → 0.2.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/dist/link-detection.d.ts +14 -0
- package/dist/link-detection.js +68 -0
- package/dist/link-editor.d.ts +5 -0
- package/dist/link-editor.js +84 -0
- package/dist/link-markdown.d.ts +5 -0
- package/dist/link-markdown.js +58 -0
- package/dist/link.d.ts +20 -0
- package/dist/link.js +104 -0
- package/package.json +33 -4
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/link-detection.d.ts
|
|
2
|
+
export type ChatLinkSpan = {
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
text: string;
|
|
6
|
+
href: string;
|
|
7
|
+
hostname: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const normalizeChatLink: (input: string) => {
|
|
10
|
+
href: string;
|
|
11
|
+
hostname: string;
|
|
12
|
+
} | null;
|
|
13
|
+
export declare const detectChatLinks: (text: string) => ChatLinkSpan[];
|
|
14
|
+
//#endregion
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { LinkifyIt } from "linkify-it";
|
|
2
|
+
import { parse } from "tldts";
|
|
3
|
+
//#region src/link-detection.ts
|
|
4
|
+
const HTTP_SCHEME = /^https?:\/\//iu;
|
|
5
|
+
const BARE_HOST_LABEL_START = /^[\p{L}\p{N}]/u;
|
|
6
|
+
const UNSAFE_CHARACTERS = /[\s\p{Cc}\p{Cf}\\]/u;
|
|
7
|
+
const SPECIAL_USE_SUFFIX = /(?:^|\.)(?:localhost|local|internal|invalid|test|example|onion)$/iu;
|
|
8
|
+
const AMBIGUOUS_FILE_SUFFIXES = /* @__PURE__ */ new Set([
|
|
9
|
+
"zip",
|
|
10
|
+
"mov",
|
|
11
|
+
"pdf",
|
|
12
|
+
"doc",
|
|
13
|
+
"docx",
|
|
14
|
+
"txt",
|
|
15
|
+
"js",
|
|
16
|
+
"ts",
|
|
17
|
+
"json",
|
|
18
|
+
"xml",
|
|
19
|
+
"html"
|
|
20
|
+
]);
|
|
21
|
+
const normalizeChatLink = (input) => {
|
|
22
|
+
if (!input || UNSAFE_CHARACTERS.test(input)) return null;
|
|
23
|
+
const explicit = HTTP_SCHEME.test(input);
|
|
24
|
+
if (!explicit && /^[\w+.-]+:/u.test(input)) return null;
|
|
25
|
+
if (!explicit && !BARE_HOST_LABEL_START.test(input)) return null;
|
|
26
|
+
const candidate = explicit ? input : `https://${input}`;
|
|
27
|
+
if (!URL.canParse(candidate)) return null;
|
|
28
|
+
const url = new URL(candidate);
|
|
29
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
30
|
+
if (url.username || url.password || !url.hostname) return null;
|
|
31
|
+
const domain = parse(url.hostname, { allowPrivateDomains: false });
|
|
32
|
+
if (!explicit) {
|
|
33
|
+
if (SPECIAL_USE_SUFFIX.test(url.hostname)) return null;
|
|
34
|
+
if (!domain.isIcann || !domain.domain || domain.isIp) return null;
|
|
35
|
+
if (!/^www\./iu.test(input) && domain.publicSuffix && AMBIGUOUS_FILE_SUFFIXES.has(domain.publicSuffix)) return null;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
href: url.href,
|
|
39
|
+
hostname: url.hostname
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
const detectChatLinks = (text) => {
|
|
43
|
+
const suffixes = Array.from(text.matchAll(/\.([\p{L}\p{N}-]+)/gu), (match) => match[1] ?? "");
|
|
44
|
+
const linkify = new LinkifyIt({
|
|
45
|
+
fuzzyLink: true,
|
|
46
|
+
fuzzyEmail: false,
|
|
47
|
+
fuzzyIP: false,
|
|
48
|
+
urlAuth: true
|
|
49
|
+
}).tlds(suffixes).add("ftp:", null).add("mailto:", null).add("//", null);
|
|
50
|
+
const spans = [];
|
|
51
|
+
for (const match of linkify.match(text) ?? []) {
|
|
52
|
+
const explicit = HTTP_SCHEME.test(match.raw);
|
|
53
|
+
const preceding = text.slice(0, match.index).at(-1);
|
|
54
|
+
if (!explicit && preceding && /[\p{L}\p{N}_@/\\:.-]/u.test(preceding)) continue;
|
|
55
|
+
const normalized = normalizeChatLink(match.raw);
|
|
56
|
+
if (!normalized) continue;
|
|
57
|
+
spans.push({
|
|
58
|
+
start: match.index,
|
|
59
|
+
end: match.lastIndex,
|
|
60
|
+
text: match.raw,
|
|
61
|
+
href: normalized.href,
|
|
62
|
+
hostname: normalized.hostname
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return spans;
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
export { detectChatLinks, normalizeChatLink };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { detectChatLinks } from "./link-detection.js";
|
|
2
|
+
import { CHAT_LINK_ATTRIBUTES, CHAT_LINK_CLASS_NAME } from "./link.js";
|
|
3
|
+
import { panic } from "better-result";
|
|
4
|
+
import { Extension } from "@tiptap/core";
|
|
5
|
+
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
6
|
+
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
7
|
+
//#region src/link-editor.ts
|
|
8
|
+
const chatLinkPluginKey = new PluginKey("stllChatLinks");
|
|
9
|
+
const decorateChatLinks = (doc) => {
|
|
10
|
+
const decorations = [];
|
|
11
|
+
doc.descendants((node, position) => {
|
|
12
|
+
if (!node.isTextblock) return true;
|
|
13
|
+
const text = node.textBetween(0, node.content.size, "\n", "\n");
|
|
14
|
+
for (const link of detectChatLinks(text)) decorations.push(Decoration.inline(position + 1 + link.start, position + 1 + link.end, {
|
|
15
|
+
nodeName: "a",
|
|
16
|
+
href: link.href,
|
|
17
|
+
...CHAT_LINK_ATTRIBUTES,
|
|
18
|
+
class: CHAT_LINK_CLASS_NAME,
|
|
19
|
+
"data-chat-composer-link": "",
|
|
20
|
+
dir: "auto"
|
|
21
|
+
}));
|
|
22
|
+
return false;
|
|
23
|
+
});
|
|
24
|
+
return DecorationSet.create(doc, decorations);
|
|
25
|
+
};
|
|
26
|
+
const openComposerLink = (view, event) => {
|
|
27
|
+
if (event.type === "click" ? event.button !== 0 || !(event.metaKey || event.ctrlKey) : event.button !== 1) return false;
|
|
28
|
+
if (!(event.target instanceof Element)) return false;
|
|
29
|
+
const anchor = event.target.closest("a[data-chat-composer-link]");
|
|
30
|
+
if (!(anchor instanceof HTMLAnchorElement) || !view.dom.contains(anchor)) return false;
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
window.open(anchor.href, CHAT_LINK_ATTRIBUTES.target, "noopener,noreferrer");
|
|
33
|
+
return true;
|
|
34
|
+
};
|
|
35
|
+
/** View-only links: saved drafts and submitted HTML retain the original bytes. */
|
|
36
|
+
const ChatComposerLinks = Extension.create({
|
|
37
|
+
name: "stllChatComposerLinks",
|
|
38
|
+
addProseMirrorPlugins() {
|
|
39
|
+
return [new Plugin({
|
|
40
|
+
key: chatLinkPluginKey,
|
|
41
|
+
state: {
|
|
42
|
+
init: (_, state) => ({
|
|
43
|
+
status: "ready",
|
|
44
|
+
decorations: decorateChatLinks(state.doc)
|
|
45
|
+
}),
|
|
46
|
+
apply: (transaction, current) => {
|
|
47
|
+
const requestedStatus = transaction.getMeta(chatLinkPluginKey);
|
|
48
|
+
const status = requestedStatus === "ready" || requestedStatus === "composing" ? requestedStatus : current.status;
|
|
49
|
+
if (!transaction.docChanged && requestedStatus !== "ready" && requestedStatus !== "composing") return current;
|
|
50
|
+
switch (status) {
|
|
51
|
+
case "composing": return {
|
|
52
|
+
status: "composing",
|
|
53
|
+
decorations: current.decorations.map(transaction.mapping, transaction.doc)
|
|
54
|
+
};
|
|
55
|
+
case "ready": return {
|
|
56
|
+
status: "ready",
|
|
57
|
+
decorations: decorateChatLinks(transaction.doc)
|
|
58
|
+
};
|
|
59
|
+
default: return panic("Unhandled composer link status");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
props: {
|
|
64
|
+
decorations: (state) => chatLinkPluginKey.getState(state)?.decorations,
|
|
65
|
+
handleDOMEvents: {
|
|
66
|
+
compositionstart: (view) => {
|
|
67
|
+
view.dispatch(view.state.tr.setMeta(chatLinkPluginKey, "composing"));
|
|
68
|
+
return false;
|
|
69
|
+
},
|
|
70
|
+
compositionend: (view) => {
|
|
71
|
+
queueMicrotask(() => {
|
|
72
|
+
if (!view.isDestroyed) view.dispatch(view.state.tr.setMeta(chatLinkPluginKey, "ready"));
|
|
73
|
+
});
|
|
74
|
+
return false;
|
|
75
|
+
},
|
|
76
|
+
click: openComposerLink,
|
|
77
|
+
auxclick: openComposerLink
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
})];
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
//#endregion
|
|
84
|
+
export { ChatComposerLinks };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { detectChatLinks } from "./link-detection.js";
|
|
2
|
+
import { CHAT_LINK_ATTRIBUTES } from "./link.js";
|
|
3
|
+
//#region src/link-markdown.ts
|
|
4
|
+
const SKIPPED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
5
|
+
"a",
|
|
6
|
+
"code",
|
|
7
|
+
"pre",
|
|
8
|
+
"stll-anon"
|
|
9
|
+
]);
|
|
10
|
+
/** Run after sanitization and source-offset anchoring; existing links keep their renderer contract. */
|
|
11
|
+
const rehypeChatLinks = () => (tree) => {
|
|
12
|
+
const visit = (parent) => {
|
|
13
|
+
if (parent.type === "element" && SKIPPED_ELEMENTS.has(parent.tagName)) return;
|
|
14
|
+
const children = [];
|
|
15
|
+
for (const child of parent.children) {
|
|
16
|
+
if (child.type === "element") visit(child);
|
|
17
|
+
if (child.type !== "text") {
|
|
18
|
+
children.push(child);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const links = detectChatLinks(child.value);
|
|
22
|
+
if (!links.length) {
|
|
23
|
+
children.push(child);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
let offset = 0;
|
|
27
|
+
for (const link of links) {
|
|
28
|
+
if (link.start > offset) children.push({
|
|
29
|
+
type: "text",
|
|
30
|
+
value: child.value.slice(offset, link.start)
|
|
31
|
+
});
|
|
32
|
+
children.push({
|
|
33
|
+
type: "element",
|
|
34
|
+
tagName: "a",
|
|
35
|
+
properties: {
|
|
36
|
+
href: link.href,
|
|
37
|
+
target: CHAT_LINK_ATTRIBUTES.target,
|
|
38
|
+
rel: CHAT_LINK_ATTRIBUTES.rel.split(" ")
|
|
39
|
+
},
|
|
40
|
+
children: [{
|
|
41
|
+
type: "text",
|
|
42
|
+
value: link.text
|
|
43
|
+
}]
|
|
44
|
+
});
|
|
45
|
+
offset = link.end;
|
|
46
|
+
}
|
|
47
|
+
if (offset < child.value.length) children.push({
|
|
48
|
+
type: "text",
|
|
49
|
+
value: child.value.slice(offset)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (parent.type === "root") parent.children = children;
|
|
53
|
+
else parent.children = children.filter((child) => child.type !== "doctype");
|
|
54
|
+
};
|
|
55
|
+
visit(tree);
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
export { rehypeChatLinks };
|
package/dist/link.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AnchorHTMLAttributes } from "react";
|
|
2
|
+
//#region src/link.d.ts
|
|
3
|
+
export declare const CHAT_LINK_ATTRIBUTES: {
|
|
4
|
+
readonly target: "_blank";
|
|
5
|
+
readonly rel: "noopener noreferrer";
|
|
6
|
+
};
|
|
7
|
+
export declare const CHAT_LINK_CLASS_NAME = "text-foreground decoration-border hover:decoration-foreground rounded-xs underline underline-offset-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring";
|
|
8
|
+
export declare const hasBundledChatLinkIcon: (hostname: string) => boolean;
|
|
9
|
+
export type ChatLinkIconSource = {
|
|
10
|
+
type: "proxy";
|
|
11
|
+
src: string;
|
|
12
|
+
};
|
|
13
|
+
type ChatLinkProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href" | "target" | "rel" | "className"> & {
|
|
14
|
+
href: string;
|
|
15
|
+
appearance?: "inline" | "tray";
|
|
16
|
+
iconSource?: ChatLinkIconSource | undefined;
|
|
17
|
+
newTabLabel?: string;
|
|
18
|
+
};
|
|
19
|
+
export declare const ChatLink: ({ appearance, children, href, iconSource, newTabLabel, title, ...anchorProps }: ChatLinkProps) => import("react").JSX.Element;
|
|
20
|
+
//#endregion
|
package/dist/link.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { normalizeChatLink } from "./link-detection.js";
|
|
2
|
+
import { GlobeIcon } from "@stll/ui/icons";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { isValidElement, useState } from "react";
|
|
5
|
+
import { GitHubLogoIcon, XLogoIcon } from "@stll/ui/brand-icons";
|
|
6
|
+
//#region src/link.tsx
|
|
7
|
+
const CHAT_LINK_ATTRIBUTES = {
|
|
8
|
+
target: "_blank",
|
|
9
|
+
rel: "noopener noreferrer"
|
|
10
|
+
};
|
|
11
|
+
const CHAT_LINK_CLASS_NAME = "text-foreground decoration-border hover:decoration-foreground rounded-xs underline underline-offset-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring";
|
|
12
|
+
const linkText = (node) => {
|
|
13
|
+
if (typeof node === "string" || typeof node === "number") return String(node);
|
|
14
|
+
if (Array.isArray(node)) return node.map(linkText).join("");
|
|
15
|
+
if (isValidElement(node)) return linkText(node.props.children);
|
|
16
|
+
return "";
|
|
17
|
+
};
|
|
18
|
+
const ProxyIcon = ({ src }) => {
|
|
19
|
+
const [failed, setFailed] = useState(false);
|
|
20
|
+
if (failed || !src.startsWith("blob:")) return /* @__PURE__ */ jsx(GlobeIcon, {
|
|
21
|
+
"aria-hidden": "true",
|
|
22
|
+
className: "size-[1em]"
|
|
23
|
+
});
|
|
24
|
+
return /* @__PURE__ */ jsx("img", {
|
|
25
|
+
alt: "",
|
|
26
|
+
"aria-hidden": "true",
|
|
27
|
+
className: "size-[1em] object-contain",
|
|
28
|
+
onError: () => setFailed(true),
|
|
29
|
+
src
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const GITHUB_LINK_ICON = /* @__PURE__ */ jsx(GitHubLogoIcon, {
|
|
33
|
+
"aria-hidden": "true",
|
|
34
|
+
className: "size-[1em]"
|
|
35
|
+
});
|
|
36
|
+
const X_LINK_ICON = /* @__PURE__ */ jsx(XLogoIcon, {
|
|
37
|
+
"aria-hidden": "true",
|
|
38
|
+
className: "size-[1em]"
|
|
39
|
+
});
|
|
40
|
+
const BUNDLED_ICONS = /* @__PURE__ */ new Map([
|
|
41
|
+
["github.com", GITHUB_LINK_ICON],
|
|
42
|
+
["www.github.com", GITHUB_LINK_ICON],
|
|
43
|
+
["x.com", X_LINK_ICON],
|
|
44
|
+
["www.x.com", X_LINK_ICON],
|
|
45
|
+
["twitter.com", X_LINK_ICON],
|
|
46
|
+
["www.twitter.com", X_LINK_ICON]
|
|
47
|
+
]);
|
|
48
|
+
const hasBundledChatLinkIcon = (hostname) => BUNDLED_ICONS.has(hostname);
|
|
49
|
+
const ChatLink = ({ appearance = "inline", children, href, iconSource, newTabLabel, title, ...anchorProps }) => {
|
|
50
|
+
const className = appearance === "tray" ? "bg-muted/50 hover:bg-muted inline-flex shrink-0 items-center gap-1 rounded-md border px-1.5 py-0.5 text-xs focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring" : CHAT_LINK_CLASS_NAME;
|
|
51
|
+
const labelClassName = appearance === "tray" ? "max-w-[20ch] truncate" : void 0;
|
|
52
|
+
const target = normalizeChatLink(href);
|
|
53
|
+
if (!target) {
|
|
54
|
+
const parsed = URL.canParse(href) ? new URL(href) : null;
|
|
55
|
+
const safeLocal = href.startsWith("#") || /^(?:\/(?![\\/])|\.{1,2}\/)/u.test(href);
|
|
56
|
+
const safeMail = parsed?.protocol === "mailto:";
|
|
57
|
+
if (!safeLocal && !safeMail || /[\p{Cc}\p{Cf}]/u.test(href)) return appearance === "tray" ? /* @__PURE__ */ jsx("span", {
|
|
58
|
+
className,
|
|
59
|
+
children: /* @__PURE__ */ jsx("bdi", {
|
|
60
|
+
className: labelClassName,
|
|
61
|
+
children: children ?? href
|
|
62
|
+
})
|
|
63
|
+
}) : /* @__PURE__ */ jsx("span", { children: children ?? href });
|
|
64
|
+
return /* @__PURE__ */ jsx("a", {
|
|
65
|
+
...anchorProps,
|
|
66
|
+
title: newTabLabel ?? title,
|
|
67
|
+
className,
|
|
68
|
+
href,
|
|
69
|
+
target: CHAT_LINK_ATTRIBUTES.target,
|
|
70
|
+
rel: CHAT_LINK_ATTRIBUTES.rel,
|
|
71
|
+
children: /* @__PURE__ */ jsx("bdi", {
|
|
72
|
+
className: labelClassName,
|
|
73
|
+
children: children ?? href
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const text = linkText(children).trim();
|
|
78
|
+
const label = text && normalizeChatLink(text) === null ? children : target.hostname.replace(/^www\./u, "");
|
|
79
|
+
let icon = /* @__PURE__ */ jsx(GlobeIcon, {
|
|
80
|
+
"aria-hidden": "true",
|
|
81
|
+
className: "size-[1em]"
|
|
82
|
+
});
|
|
83
|
+
const bundledIcon = BUNDLED_ICONS.get(target.hostname);
|
|
84
|
+
if (iconSource) icon = /* @__PURE__ */ jsx(ProxyIcon, { src: iconSource.src }, iconSource.src);
|
|
85
|
+
else if (bundledIcon) icon = bundledIcon;
|
|
86
|
+
return /* @__PURE__ */ jsxs("a", {
|
|
87
|
+
...anchorProps,
|
|
88
|
+
title: newTabLabel ?? title,
|
|
89
|
+
className,
|
|
90
|
+
href: target.href,
|
|
91
|
+
target: CHAT_LINK_ATTRIBUTES.target,
|
|
92
|
+
rel: CHAT_LINK_ATTRIBUTES.rel,
|
|
93
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
94
|
+
"aria-hidden": "true",
|
|
95
|
+
className: appearance === "tray" ? "inline-block shrink-0" : "me-1 inline-block align-[-0.125em]",
|
|
96
|
+
children: icon
|
|
97
|
+
}), /* @__PURE__ */ jsx("bdi", {
|
|
98
|
+
className: labelClassName,
|
|
99
|
+
children: label
|
|
100
|
+
})]
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
export { CHAT_LINK_ATTRIBUTES, CHAT_LINK_CLASS_NAME, ChatLink, hasBundledChatLinkIcon };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Typed React chat composer, message stream, model selector, and transport runtime primitives.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -52,6 +52,22 @@
|
|
|
52
52
|
"./resume-contract": {
|
|
53
53
|
"types": "./dist/resume-contract.d.ts",
|
|
54
54
|
"import": "./dist/resume-contract.js"
|
|
55
|
+
},
|
|
56
|
+
"./link": {
|
|
57
|
+
"types": "./dist/link.d.ts",
|
|
58
|
+
"import": "./dist/link.js"
|
|
59
|
+
},
|
|
60
|
+
"./link-detection": {
|
|
61
|
+
"types": "./dist/link-detection.d.ts",
|
|
62
|
+
"import": "./dist/link-detection.js"
|
|
63
|
+
},
|
|
64
|
+
"./link-editor": {
|
|
65
|
+
"types": "./dist/link-editor.d.ts",
|
|
66
|
+
"import": "./dist/link-editor.js"
|
|
67
|
+
},
|
|
68
|
+
"./link-markdown": {
|
|
69
|
+
"types": "./dist/link-markdown.d.ts",
|
|
70
|
+
"import": "./dist/link-markdown.js"
|
|
55
71
|
}
|
|
56
72
|
},
|
|
57
73
|
"publishConfig": {
|
|
@@ -71,17 +87,22 @@
|
|
|
71
87
|
},
|
|
72
88
|
"dependencies": {
|
|
73
89
|
"@ag-ui/core": "1.0.2",
|
|
74
|
-
"@stll/ai-catalog": "^0.7.
|
|
75
|
-
"@stll/ui": "^0.
|
|
90
|
+
"@stll/ai-catalog": "^0.7.1",
|
|
91
|
+
"@stll/ui": "^0.46.0",
|
|
76
92
|
"@tanstack/ai-client": "0.38.0",
|
|
93
|
+
"@types/hast": "^3.0.5",
|
|
77
94
|
"better-result": "3.0.1",
|
|
95
|
+
"linkify-it": "6.1.0",
|
|
96
|
+
"tldts": "7.4.18",
|
|
78
97
|
"valibot": "1.5.0",
|
|
79
98
|
"zod": "^4.5.4"
|
|
80
99
|
},
|
|
81
100
|
"devDependencies": {
|
|
82
101
|
"@stll/property-testing": "0.0.0",
|
|
83
102
|
"@stll/typescript-config": "0.0.0",
|
|
84
|
-
"@
|
|
103
|
+
"@tiptap/core": "^3.31.4",
|
|
104
|
+
"@tiptap/pm": "^3.31.4",
|
|
105
|
+
"@types/react": "^19.3.0",
|
|
85
106
|
"bun-types": "1.4.3",
|
|
86
107
|
"fast-check": "^4.10.2",
|
|
87
108
|
"react": "^19.3.0",
|
|
@@ -89,10 +110,18 @@
|
|
|
89
110
|
},
|
|
90
111
|
"peerDependencies": {
|
|
91
112
|
"@base-ui/react": "^1.7.0",
|
|
113
|
+
"@tiptap/core": "^3.31.4",
|
|
114
|
+
"@tiptap/pm": "^3.31.4",
|
|
92
115
|
"@types/react": ">=19",
|
|
93
116
|
"react": ">=19"
|
|
94
117
|
},
|
|
95
118
|
"peerDependenciesMeta": {
|
|
119
|
+
"@tiptap/core": {
|
|
120
|
+
"optional": true
|
|
121
|
+
},
|
|
122
|
+
"@tiptap/pm": {
|
|
123
|
+
"optional": true
|
|
124
|
+
},
|
|
96
125
|
"@types/react": {
|
|
97
126
|
"optional": true
|
|
98
127
|
}
|