@gravity-ui/aikit 2.23.0 → 2.23.1-beta.d014c71fccccb7347cf97c255b2ae8d7b9880b79.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/build/cjs/components/atoms/MarkdownRenderer/MarkdownRenderer.css +21 -0
- package/build/cjs/components/atoms/MarkdownRenderer/MarkdownRenderer.js +8 -22
- package/build/cjs/components/atoms/MarkdownRenderer/MarkdownRenderer.js.map +1 -1
- package/build/cjs/hooks/useMermaid.d.ts +2 -0
- package/build/cjs/hooks/useMermaid.js +96 -0
- package/build/cjs/hooks/useMermaid.js.map +1 -0
- package/build/cjs/hooks/useSmartScroll.js +4 -8
- package/build/cjs/hooks/useSmartScroll.js.map +1 -1
- package/build/cjs/hooks/useVirtualStickToBottom.js +4 -8
- package/build/cjs/hooks/useVirtualStickToBottom.js.map +1 -1
- package/build/cjs/package.json +1 -1
- package/build/cjs/utils/markdownLinks.d.ts +6 -0
- package/build/cjs/utils/markdownLinks.js +43 -0
- package/build/cjs/utils/markdownLinks.js.map +1 -0
- package/build/cjs/utils/markdownMermaidPlugin.d.ts +4 -0
- package/build/cjs/utils/markdownMermaidPlugin.js +19 -0
- package/build/cjs/utils/markdownMermaidPlugin.js.map +1 -0
- package/build/cjs/utils/markdownUtils.d.ts +1 -1
- package/build/cjs/utils/markdownUtils.js +8 -2
- package/build/cjs/utils/markdownUtils.js.map +1 -1
- package/build/cjs/utils/observeScrollContent.d.ts +2 -0
- package/build/cjs/utils/observeScrollContent.js +53 -0
- package/build/cjs/utils/observeScrollContent.js.map +1 -0
- package/build/cjs/utils/renderMermaid.d.ts +1 -0
- package/build/cjs/utils/renderMermaid.js +54 -0
- package/build/cjs/utils/renderMermaid.js.map +1 -0
- package/build/docs/components/atoms/MarkdownRenderer.md +42 -5
- package/build/esm/components/atoms/MarkdownRenderer/MarkdownRenderer.css +21 -0
- package/build/esm/components/atoms/MarkdownRenderer/MarkdownRenderer.js +8 -22
- package/build/esm/components/atoms/MarkdownRenderer/MarkdownRenderer.js.map +1 -1
- package/build/esm/hooks/useMermaid.d.ts +2 -0
- package/build/esm/hooks/useMermaid.js +93 -0
- package/build/esm/hooks/useMermaid.js.map +1 -0
- package/build/esm/hooks/useSmartScroll.js +4 -8
- package/build/esm/hooks/useSmartScroll.js.map +1 -1
- package/build/esm/hooks/useVirtualStickToBottom.js +4 -8
- package/build/esm/hooks/useVirtualStickToBottom.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/utils/markdownLinks.d.ts +6 -0
- package/build/esm/utils/markdownLinks.js +39 -0
- package/build/esm/utils/markdownLinks.js.map +1 -0
- package/build/esm/utils/markdownMermaidPlugin.d.ts +4 -0
- package/build/esm/utils/markdownMermaidPlugin.js +16 -0
- package/build/esm/utils/markdownMermaidPlugin.js.map +1 -0
- package/build/esm/utils/markdownUtils.d.ts +1 -1
- package/build/esm/utils/markdownUtils.js +8 -2
- package/build/esm/utils/markdownUtils.js.map +1 -1
- package/build/esm/utils/observeScrollContent.d.ts +2 -0
- package/build/esm/utils/observeScrollContent.js +50 -0
- package/build/esm/utils/observeScrollContent.js.map +1 -0
- package/build/esm/utils/renderMermaid.d.ts +1 -0
- package/build/esm/utils/renderMermaid.js +51 -0
- package/build/esm/utils/renderMermaid.js.map +1 -0
- package/package.json +2 -1
|
@@ -90,6 +90,27 @@
|
|
|
90
90
|
width: 100%;
|
|
91
91
|
max-width: 100%;
|
|
92
92
|
min-width: 0;
|
|
93
|
+
}
|
|
94
|
+
.g-aikit-markdown-renderer__mermaid {
|
|
95
|
+
max-width: 100%;
|
|
96
|
+
min-width: 0;
|
|
97
|
+
margin-block: 16px;
|
|
98
|
+
overflow-x: auto;
|
|
99
|
+
overscroll-behavior-x: var(--g-aikit-scroll-overscroll-behavior, auto);
|
|
100
|
+
}
|
|
101
|
+
.g-aikit-markdown-renderer__mermaid pre[hidden] {
|
|
102
|
+
display: none;
|
|
103
|
+
}
|
|
104
|
+
.g-aikit-markdown-renderer__mermaid-diagram {
|
|
105
|
+
width: max-content;
|
|
106
|
+
min-width: 100%;
|
|
107
|
+
}
|
|
108
|
+
.g-aikit-markdown-renderer__mermaid-diagram > svg {
|
|
109
|
+
display: block;
|
|
110
|
+
height: auto;
|
|
111
|
+
max-height: none;
|
|
112
|
+
}
|
|
113
|
+
.g-aikit-markdown-renderer {
|
|
93
114
|
/* Scroll container injected by markdownTableWrapPlugin; text outside stays fixed */
|
|
94
115
|
}
|
|
95
116
|
.g-aikit-markdown-renderer__table-wrap {
|
|
@@ -5,31 +5,14 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const mdx_extension_1 = require("@diplodoc/mdx-extension");
|
|
7
7
|
const useMarkdownTransform_1 = require("../../../hooks/useMarkdownTransform.js");
|
|
8
|
+
const useMermaid_1 = require("../../../hooks/useMermaid.js");
|
|
8
9
|
const useRemend_1 = require("../../../hooks/useRemend.js");
|
|
9
10
|
const cn_1 = require("../../../utils/cn.js");
|
|
11
|
+
const markdownLinks_1 = require("../../../utils/markdownLinks.js");
|
|
10
12
|
const markdownUtils_1 = require("../../../utils/markdownUtils.js");
|
|
11
13
|
const MdxPortals_1 = require("./MdxPortals.js");
|
|
12
14
|
require("./MarkdownRenderer.css");
|
|
13
15
|
const b = (0, cn_1.block)('markdown-renderer');
|
|
14
|
-
const ABSOLUTE_HTTP_URL_RE = /^(https?:)?\/\//i;
|
|
15
|
-
const isSameDocumentAnchor = (href) => {
|
|
16
|
-
if (href.startsWith('#')) {
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
if (ABSOLUTE_HTTP_URL_RE.test(href) || typeof window === 'undefined') {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
const url = new URL(href, window.location.href);
|
|
24
|
-
return (Boolean(url.hash) &&
|
|
25
|
-
url.origin === window.location.origin &&
|
|
26
|
-
url.pathname === window.location.pathname &&
|
|
27
|
-
url.search === window.location.search);
|
|
28
|
-
}
|
|
29
|
-
catch (_a) {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
16
|
const openLinksInNewTabPlugin = ((md) => {
|
|
34
17
|
const rendererRules = md.renderer.rules;
|
|
35
18
|
const defaultRender = rendererRules.link_open ||
|
|
@@ -38,9 +21,11 @@ const openLinksInNewTabPlugin = ((md) => {
|
|
|
38
21
|
};
|
|
39
22
|
rendererRules.link_open = function (tokens, idx, options, env, self) {
|
|
40
23
|
const href = tokens[idx].attrGet('href');
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
24
|
+
const attributes = (0, markdownLinks_1.getNewTabLinkAttributes)(href);
|
|
25
|
+
if (attributes) {
|
|
26
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
27
|
+
tokens[idx].attrSet(name, value);
|
|
28
|
+
}
|
|
44
29
|
}
|
|
45
30
|
return defaultRender(tokens, idx, options, env, self);
|
|
46
31
|
};
|
|
@@ -67,6 +52,7 @@ function MarkdownRendererComponent({ content, className, qa, transformOptions, s
|
|
|
67
52
|
}, [openLinksInNewTab, transformOptions, enableMdx, mdxTagNames]);
|
|
68
53
|
const containerRef = (0, react_1.useRef)(null);
|
|
69
54
|
const { html, mdxArtifacts } = (0, useMarkdownTransform_1.useMarkdownTransform)(closedContent, finalTransformOptions, enableMdx);
|
|
55
|
+
(0, useMermaid_1.useMermaid)(containerRef, html, openLinksInNewTab);
|
|
70
56
|
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ ref: containerRef, className: b(null, [className, 'yfm']), "data-qa": qa, dangerouslySetInnerHTML: { __html: html } }, extraProps)), enableMdx ? ((0, jsx_runtime_1.jsx)(MdxPortals_1.MdxPortals, { refCtr: containerRef, html: html, components: mdxComponents, mdxArtifacts: mdxArtifacts, nonce: mdxNonce, mdxContext: mdxContext })) : null] }));
|
|
71
57
|
}
|
|
72
58
|
exports.MarkdownRenderer = (0, react_1.memo)(MarkdownRendererComponent, (prevProps, nextProps) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkdownRenderer.js","sourceRoot":"../../../../../src","sources":["components/atoms/MarkdownRenderer/MarkdownRenderer.tsx"],"names":[],"mappings":";;;;AAAA,iCAA2E;AAE3E,2DAAkD;AAQlD,iFAAyE;AACzE,2DAAmD;AACnD,6CAAwC;AACxC,mEAA6D;AAE7D,gDAAwC;AAExC,kCAAiC;AAEjC,MAAM,CAAC,GAAG,IAAA,UAAK,EAAC,mBAAmB,CAAC,CAAC;AA4BrC,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;AAEhD,MAAM,oBAAoB,GAAG,CAAC,IAAY,EAAE,EAAE;IAC1C,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QACnE,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAEhD,OAAO,CACH,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YACjB,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM;YACrC,GAAG,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC,QAAQ;YACzC,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM,CACxC,CAAC;IACN,CAAC;IAAC,WAAM,CAAC;QACL,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,uBAAuB,GAA8B,CAAC,CAAC,EAAc,EAAE,EAAE;IAC3E,MAAM,aAAa,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;IACxC,MAAM,aAAa,GACf,aAAa,CAAC,SAAS;QACvB,UAAU,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI;YACtC,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;QAClD,CAAC,CAAC;IAEN,aAAa,CAAC,SAAS,GAAG,UAAU,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI;QAC/D,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACxC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAC;QACtD,CAAC;QAED,OAAO,aAAa,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1D,CAAC,CAAC;AACN,CAAC,CAA8B,CAAC;AAEhC,SAAS,yBAAyB,CAAC,EAC/B,OAAO,EACP,SAAS,EACT,EAAE,EACF,gBAAgB,EAChB,6BAA6B,GAAG,KAAK,EACrC,iBAAiB,GAAG,KAAK,EACzB,UAAU,EACV,UAAU,EACV,UAAU,GACU;IACpB,MAAM,aAAa,GAAG,IAAA,qBAAS,EAAC,OAAO,EAAE,6BAA6B,CAAC,CAAC;IACxE,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtC,MAAM,aAAa,GAAG,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,UAAU,CAAC;IAC7C,MAAM,WAAW,GAAG,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,QAAQ,CAAC;IACzC,MAAM,QAAQ,GAAG,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,KAAK,CAAC;IACnC,MAAM,qBAAqB,GAAG,IAAA,eAAO,EAA0B,GAAG,EAAE;;QAChE,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,MAAA,gBAAgB,aAAhB,gBAAgB,uBAAhB,gBAAgB,CAAE,OAAO,mCAAI,EAAE,CAAC,CAAC,CAAC;QAEvD,IAAI,iBAAiB,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAC1C,CAAC;QAED,IAAI,SAAS,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CACR,IAAA,yBAAS,EACL,WAAW,CAAC,CAAC,CAAC,EAAC,QAAQ,EAAE,WAAW,EAAC,CAAC,CAAC,CAAC,SAAS,CACZ,CAC5C,CAAC;QACN,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,gBAAgB,CAAC;QAC5B,CAAC;QAED,uCACO,gBAAgB,KACnB,OAAO,IACT;IACN,CAAC,EAAE,CAAC,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;IAElE,MAAM,YAAY,GAAG,IAAA,cAAM,EAAwB,IAAI,CAAC,CAAC;IACzD,MAAM,EAAC,IAAI,EAAE,YAAY,EAAC,GAAG,IAAA,2CAAoB,EAC7C,aAAa,EACb,qBAAqB,EACrB,SAAS,CACZ,CAAC;IAEF,OAAO,CACH,wBAAC,gBAAQ,eACL,8CACI,GAAG,EAAE,YAAY,EACjB,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,aAC7B,EAAE,EACX,uBAAuB,EAAE,EAAC,MAAM,EAAE,IAAI,EAAC,IACnC,UAAU,EAChB,EACD,SAAS,CAAC,CAAC,CAAC,CACT,uBAAC,uBAAU,IACP,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,aAAa,EACzB,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAE,QAAQ,EACf,UAAU,EAAE,UAAU,GACxB,CACL,CAAC,CAAC,CAAC,IAAI,IACD,CACd,CAAC;AACN,CAAC;AAEY,QAAA,gBAAgB,GAAG,IAAA,YAAI,EAAC,yBAAyB,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,EAAE;;IACrF,IAAI,SAAS,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,SAAS,CAAC,6BAA6B,KAAK,SAAS,CAAC,6BAA6B,EAAE,CAAC;QACtF,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,SAAS,CAAC,iBAAiB,KAAK,SAAS,CAAC,iBAAiB,EAAE,CAAC;QAC9D,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,SAAS,CAAC,SAAS,KAAK,SAAS,CAAC,SAAS,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,SAAS,CAAC,EAAE,KAAK,SAAS,CAAC,EAAE,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,CAAA,MAAA,SAAS,CAAC,UAAU,0CAAE,UAAU,OAAK,MAAA,SAAS,CAAC,UAAU,0CAAE,UAAU,CAAA,EAAE,CAAC;QACxE,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,CAAA,MAAA,SAAS,CAAC,UAAU,0CAAE,QAAQ,OAAK,MAAA,SAAS,CAAC,UAAU,0CAAE,QAAQ,CAAA,EAAE,CAAC;QACpE,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,CAAA,MAAA,SAAS,CAAC,UAAU,0CAAE,KAAK,OAAK,MAAA,SAAS,CAAC,UAAU,0CAAE,KAAK,CAAA,EAAE,CAAC;QAC9D,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS,CAAC,UAAU,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS,CAAC,UAAU,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,OAAO,IAAA,+BAAe,EAAC,SAAS,CAAC,gBAAgB,EAAE,SAAS,CAAC,gBAAgB,CAAC,CAAC;AACnF,CAAC,CAAC,CAAC;AAEH,wBAAgB,CAAC,WAAW,GAAG,kBAAkB,CAAC","sourcesContent":["import {Fragment, type HTMLAttributes, memo, useMemo, useRef} from 'react';\n\nimport {mdxPlugin} from '@diplodoc/mdx-extension';\nimport type {\n ExtendedPluginWithCollect,\n MarkdownIt,\n OptionsType,\n} from '@diplodoc/transform/lib/typings';\nimport type {MDXComponents} from 'mdx/types';\n\nimport {useMarkdownTransform} from '../../../hooks/useMarkdownTransform';\nimport {useRemend} from '../../../hooks/useRemend';\nimport {block} from '../../../utils/cn';\nimport {areOptionsEqual} from '../../../utils/markdownUtils';\n\nimport {MdxPortals} from './MdxPortals';\n\nimport './MarkdownRenderer.scss';\n\nconst b = block('markdown-renderer');\n\n/**\n * Options for rendering embedded MDX/JSX in the markdown content via\n * `@diplodoc/mdx-extension`. Passing `mdxOptions` enables MDX processing.\n */\nexport interface MarkdownRendererMdxOptions {\n /** Map of components rendered from embedded MDX/JSX in the markdown content. */\n components: MDXComponents;\n /** Optional list of tag names to limit which components are processed as MDX. */\n tagNames?: string[];\n /** CSP nonce applied to compiled MDX scripts. Defaults to a nonce found in the document. */\n nonce?: string;\n}\n\nexport interface MarkdownRendererProps {\n content: string;\n className?: string;\n qa?: string;\n transformOptions?: OptionsType;\n shouldParseIncompleteMarkdown?: boolean;\n openLinksInNewTab?: boolean;\n mdxOptions?: MarkdownRendererMdxOptions;\n mdxContext?: Record<string, unknown>;\n /** Extra props forwarded to the root container `div` element. */\n extraProps?: HTMLAttributes<HTMLDivElement>;\n}\n\nconst ABSOLUTE_HTTP_URL_RE = /^(https?:)?\\/\\//i;\n\nconst isSameDocumentAnchor = (href: string) => {\n if (href.startsWith('#')) {\n return true;\n }\n\n if (ABSOLUTE_HTTP_URL_RE.test(href) || typeof window === 'undefined') {\n return false;\n }\n\n try {\n const url = new URL(href, window.location.href);\n\n return (\n Boolean(url.hash) &&\n url.origin === window.location.origin &&\n url.pathname === window.location.pathname &&\n url.search === window.location.search\n );\n } catch {\n return false;\n }\n};\n\nconst openLinksInNewTabPlugin: ExtendedPluginWithCollect = ((md: MarkdownIt) => {\n const rendererRules = md.renderer.rules;\n const defaultRender =\n rendererRules.link_open ||\n function (tokens, idx, options, _env, self) {\n return self.renderToken(tokens, idx, options);\n };\n\n rendererRules.link_open = function (tokens, idx, options, env, self) {\n const href = tokens[idx].attrGet('href');\n if (!href || !isSameDocumentAnchor(href)) {\n tokens[idx].attrSet('target', '_blank');\n tokens[idx].attrSet('rel', 'noopener noreferrer');\n }\n\n return defaultRender(tokens, idx, options, env, self);\n };\n}) as ExtendedPluginWithCollect;\n\nfunction MarkdownRendererComponent({\n content,\n className,\n qa,\n transformOptions,\n shouldParseIncompleteMarkdown = false,\n openLinksInNewTab = false,\n mdxOptions,\n mdxContext,\n extraProps,\n}: MarkdownRendererProps) {\n const closedContent = useRemend(content, shouldParseIncompleteMarkdown);\n const enableMdx = Boolean(mdxOptions);\n const mdxComponents = mdxOptions?.components;\n const mdxTagNames = mdxOptions?.tagNames;\n const mdxNonce = mdxOptions?.nonce;\n const finalTransformOptions = useMemo<OptionsType | undefined>(() => {\n const plugins = [...(transformOptions?.plugins ?? [])];\n\n if (openLinksInNewTab) {\n plugins.push(openLinksInNewTabPlugin);\n }\n\n if (enableMdx) {\n plugins.push(\n mdxPlugin(\n mdxTagNames ? {tagNames: mdxTagNames} : undefined,\n ) as unknown as ExtendedPluginWithCollect,\n );\n }\n\n if (plugins.length === 0) {\n return transformOptions;\n }\n\n return {\n ...transformOptions,\n plugins,\n };\n }, [openLinksInNewTab, transformOptions, enableMdx, mdxTagNames]);\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const {html, mdxArtifacts} = useMarkdownTransform(\n closedContent,\n finalTransformOptions,\n enableMdx,\n );\n\n return (\n <Fragment>\n <div\n ref={containerRef}\n className={b(null, [className, 'yfm'])}\n data-qa={qa}\n dangerouslySetInnerHTML={{__html: html}}\n {...extraProps}\n />\n {enableMdx ? (\n <MdxPortals\n refCtr={containerRef}\n html={html}\n components={mdxComponents}\n mdxArtifacts={mdxArtifacts}\n nonce={mdxNonce}\n mdxContext={mdxContext}\n />\n ) : null}\n </Fragment>\n );\n}\n\nexport const MarkdownRenderer = memo(MarkdownRendererComponent, (prevProps, nextProps) => {\n if (prevProps.content !== nextProps.content) {\n return false;\n }\n\n if (prevProps.shouldParseIncompleteMarkdown !== nextProps.shouldParseIncompleteMarkdown) {\n return false;\n }\n\n if (prevProps.openLinksInNewTab !== nextProps.openLinksInNewTab) {\n return false;\n }\n\n if (prevProps.className !== nextProps.className) {\n return false;\n }\n\n if (prevProps.qa !== nextProps.qa) {\n return false;\n }\n\n if (prevProps.mdxOptions?.components !== nextProps.mdxOptions?.components) {\n return false;\n }\n\n if (prevProps.mdxOptions?.tagNames !== nextProps.mdxOptions?.tagNames) {\n return false;\n }\n\n if (prevProps.mdxOptions?.nonce !== nextProps.mdxOptions?.nonce) {\n return false;\n }\n\n if (prevProps.mdxContext !== nextProps.mdxContext) {\n return false;\n }\n\n if (prevProps.extraProps !== nextProps.extraProps) {\n return false;\n }\n\n return areOptionsEqual(prevProps.transformOptions, nextProps.transformOptions);\n});\n\nMarkdownRenderer.displayName = 'MarkdownRenderer';\n"]}
|
|
1
|
+
{"version":3,"file":"MarkdownRenderer.js","sourceRoot":"../../../../../src","sources":["components/atoms/MarkdownRenderer/MarkdownRenderer.tsx"],"names":[],"mappings":"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","sourcesContent":["import {Fragment, type HTMLAttributes, memo, useMemo, useRef} from 'react';\n\nimport {mdxPlugin} from '@diplodoc/mdx-extension';\nimport type {\n ExtendedPluginWithCollect,\n MarkdownIt,\n OptionsType,\n} from '@diplodoc/transform/lib/typings';\nimport type {MDXComponents} from 'mdx/types';\n\nimport {useMarkdownTransform} from '../../../hooks/useMarkdownTransform';\nimport {useMermaid} from '../../../hooks/useMermaid';\nimport {useRemend} from '../../../hooks/useRemend';\nimport {block} from '../../../utils/cn';\nimport {getNewTabLinkAttributes} from '../../../utils/markdownLinks';\nimport {areOptionsEqual} from '../../../utils/markdownUtils';\n\nimport {MdxPortals} from './MdxPortals';\n\nimport './MarkdownRenderer.scss';\n\nconst b = block('markdown-renderer');\n\n/**\n * Options for rendering embedded MDX/JSX in the markdown content via\n * `@diplodoc/mdx-extension`. Passing `mdxOptions` enables MDX processing.\n */\nexport interface MarkdownRendererMdxOptions {\n /** Map of components rendered from embedded MDX/JSX in the markdown content. */\n components: MDXComponents;\n /** Optional list of tag names to limit which components are processed as MDX. */\n tagNames?: string[];\n /** CSP nonce applied to compiled MDX scripts. Defaults to a nonce found in the document. */\n nonce?: string;\n}\n\nexport interface MarkdownRendererProps {\n content: string;\n className?: string;\n qa?: string;\n transformOptions?: OptionsType;\n shouldParseIncompleteMarkdown?: boolean;\n openLinksInNewTab?: boolean;\n mdxOptions?: MarkdownRendererMdxOptions;\n mdxContext?: Record<string, unknown>;\n /** Extra props forwarded to the root container `div` element. */\n extraProps?: HTMLAttributes<HTMLDivElement>;\n}\n\nconst openLinksInNewTabPlugin: ExtendedPluginWithCollect = ((md: MarkdownIt) => {\n const rendererRules = md.renderer.rules;\n const defaultRender =\n rendererRules.link_open ||\n function (tokens, idx, options, _env, self) {\n return self.renderToken(tokens, idx, options);\n };\n\n rendererRules.link_open = function (tokens, idx, options, env, self) {\n const href = tokens[idx].attrGet('href');\n const attributes = getNewTabLinkAttributes(href);\n if (attributes) {\n for (const [name, value] of Object.entries(attributes)) {\n tokens[idx].attrSet(name, value);\n }\n }\n\n return defaultRender(tokens, idx, options, env, self);\n };\n}) as ExtendedPluginWithCollect;\n\nfunction MarkdownRendererComponent({\n content,\n className,\n qa,\n transformOptions,\n shouldParseIncompleteMarkdown = false,\n openLinksInNewTab = false,\n mdxOptions,\n mdxContext,\n extraProps,\n}: MarkdownRendererProps) {\n const closedContent = useRemend(content, shouldParseIncompleteMarkdown);\n const enableMdx = Boolean(mdxOptions);\n const mdxComponents = mdxOptions?.components;\n const mdxTagNames = mdxOptions?.tagNames;\n const mdxNonce = mdxOptions?.nonce;\n const finalTransformOptions = useMemo<OptionsType | undefined>(() => {\n const plugins = [...(transformOptions?.plugins ?? [])];\n\n if (openLinksInNewTab) {\n plugins.push(openLinksInNewTabPlugin);\n }\n\n if (enableMdx) {\n plugins.push(\n mdxPlugin(\n mdxTagNames ? {tagNames: mdxTagNames} : undefined,\n ) as unknown as ExtendedPluginWithCollect,\n );\n }\n\n if (plugins.length === 0) {\n return transformOptions;\n }\n\n return {\n ...transformOptions,\n plugins,\n };\n }, [openLinksInNewTab, transformOptions, enableMdx, mdxTagNames]);\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const {html, mdxArtifacts} = useMarkdownTransform(\n closedContent,\n finalTransformOptions,\n enableMdx,\n );\n\n useMermaid(containerRef, html, openLinksInNewTab);\n\n return (\n <Fragment>\n <div\n ref={containerRef}\n className={b(null, [className, 'yfm'])}\n data-qa={qa}\n dangerouslySetInnerHTML={{__html: html}}\n {...extraProps}\n />\n {enableMdx ? (\n <MdxPortals\n refCtr={containerRef}\n html={html}\n components={mdxComponents}\n mdxArtifacts={mdxArtifacts}\n nonce={mdxNonce}\n mdxContext={mdxContext}\n />\n ) : null}\n </Fragment>\n );\n}\n\nexport const MarkdownRenderer = memo(MarkdownRendererComponent, (prevProps, nextProps) => {\n if (prevProps.content !== nextProps.content) {\n return false;\n }\n\n if (prevProps.shouldParseIncompleteMarkdown !== nextProps.shouldParseIncompleteMarkdown) {\n return false;\n }\n\n if (prevProps.openLinksInNewTab !== nextProps.openLinksInNewTab) {\n return false;\n }\n\n if (prevProps.className !== nextProps.className) {\n return false;\n }\n\n if (prevProps.qa !== nextProps.qa) {\n return false;\n }\n\n if (prevProps.mdxOptions?.components !== nextProps.mdxOptions?.components) {\n return false;\n }\n\n if (prevProps.mdxOptions?.tagNames !== nextProps.mdxOptions?.tagNames) {\n return false;\n }\n\n if (prevProps.mdxOptions?.nonce !== nextProps.mdxOptions?.nonce) {\n return false;\n }\n\n if (prevProps.mdxContext !== nextProps.mdxContext) {\n return false;\n }\n\n if (prevProps.extraProps !== nextProps.extraProps) {\n return false;\n }\n\n return areOptionsEqual(prevProps.transformOptions, nextProps.transformOptions);\n});\n\nMarkdownRenderer.displayName = 'MarkdownRenderer';\n"]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useMermaid = useMermaid;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const uikit_1 = require("@gravity-ui/uikit");
|
|
6
|
+
const markdownLinks_1 = require("../utils/markdownLinks.js");
|
|
7
|
+
const markdownMermaidPlugin_1 = require("../utils/markdownMermaidPlugin.js");
|
|
8
|
+
const renderMermaid_1 = require("../utils/renderMermaid.js");
|
|
9
|
+
function useMermaid(containerRef, html, openLinksInNewTab = false) {
|
|
10
|
+
var _a;
|
|
11
|
+
const themeContext = (0, react_1.useContext)(uikit_1.ThemeContext);
|
|
12
|
+
const theme = (0, uikit_1.getThemeType)((_a = themeContext === null || themeContext === void 0 ? void 0 : themeContext.themeValue) !== null && _a !== void 0 ? _a : 'light');
|
|
13
|
+
const cacheRef = (0, react_1.useRef)(new Map());
|
|
14
|
+
(0, react_1.useEffect)(() => {
|
|
15
|
+
var _a, _b;
|
|
16
|
+
const blocks = Array.from((_b = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll(`.${markdownMermaidPlugin_1.MARKDOWN_MERMAID_CLASS}`)) !== null && _b !== void 0 ? _b : []);
|
|
17
|
+
const entries = blocks.map((block, index) => {
|
|
18
|
+
var _a, _b, _c, _d;
|
|
19
|
+
return ({
|
|
20
|
+
block,
|
|
21
|
+
source: (_b = (_a = block.querySelector('code')) === null || _a === void 0 ? void 0 : _a.textContent) !== null && _b !== void 0 ? _b : '',
|
|
22
|
+
// Identical diagrams in one message need distinct SVG identifiers.
|
|
23
|
+
key: `${theme}:${index}:${(_d = (_c = block.querySelector('code')) === null || _c === void 0 ? void 0 : _c.textContent) !== null && _d !== void 0 ? _d : ''}`,
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
const keys = new Set(entries.map(({ key }) => key));
|
|
27
|
+
const cache = cacheRef.current;
|
|
28
|
+
for (const key of cache.keys()) {
|
|
29
|
+
if (!keys.has(key)) {
|
|
30
|
+
cache.delete(key);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
let active = true;
|
|
34
|
+
const isActive = () => active;
|
|
35
|
+
const cleanups = [];
|
|
36
|
+
const display = (block, svg) => {
|
|
37
|
+
const source = block.querySelector('pre');
|
|
38
|
+
const diagram = document.createElement('div');
|
|
39
|
+
diagram.className = `${markdownMermaidPlugin_1.MARKDOWN_MERMAID_CLASS}-diagram`;
|
|
40
|
+
diagram.innerHTML = svg;
|
|
41
|
+
if (openLinksInNewTab) {
|
|
42
|
+
(0, markdownLinks_1.applyNewTabLinkAttributes)(diagram);
|
|
43
|
+
}
|
|
44
|
+
const svgElement = diagram.querySelector('svg');
|
|
45
|
+
const width = svgElement === null || svgElement === void 0 ? void 0 : svgElement.viewBox.baseVal.width;
|
|
46
|
+
if (svgElement && width) {
|
|
47
|
+
svgElement.setAttribute('width', String(width));
|
|
48
|
+
svgElement.style.maxWidth = 'none';
|
|
49
|
+
}
|
|
50
|
+
block.appendChild(diagram);
|
|
51
|
+
block.setAttribute('tabindex', '0');
|
|
52
|
+
if (source) {
|
|
53
|
+
source.hidden = true;
|
|
54
|
+
}
|
|
55
|
+
cleanups.push(() => {
|
|
56
|
+
diagram.remove();
|
|
57
|
+
block.removeAttribute('tabindex');
|
|
58
|
+
if (source) {
|
|
59
|
+
source.hidden = false;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
const pending = entries.filter(({ block, key }) => {
|
|
64
|
+
const cached = cache.get(key);
|
|
65
|
+
if (cached) {
|
|
66
|
+
display(block, cached);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
});
|
|
71
|
+
// Coalesce rapid streaming updates; incomplete syntax stays as source.
|
|
72
|
+
const timeout = window.setTimeout(async () => {
|
|
73
|
+
for (const { block, source, key } of pending) {
|
|
74
|
+
if (!active) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const svg = await (0, renderMermaid_1.renderMermaid)(source, theme, isActive);
|
|
79
|
+
if (active && svg) {
|
|
80
|
+
cache.set(key, svg);
|
|
81
|
+
display(block, svg);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (_a) {
|
|
85
|
+
// Keep the escaped source block and continue with other diagrams.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}, 150);
|
|
89
|
+
return () => {
|
|
90
|
+
active = false;
|
|
91
|
+
window.clearTimeout(timeout);
|
|
92
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
93
|
+
};
|
|
94
|
+
}, [containerRef, html, theme, openLinksInNewTab]);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=useMermaid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMermaid.js","sourceRoot":"../../../src","sources":["hooks/useMermaid.ts"],"names":[],"mappings":"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","sourcesContent":["import {type RefObject, useContext, useEffect, useRef} from 'react';\n\nimport {ThemeContext, getThemeType} from '@gravity-ui/uikit';\n\nimport {applyNewTabLinkAttributes} from '../utils/markdownLinks';\nimport {MARKDOWN_MERMAID_CLASS} from '../utils/markdownMermaidPlugin';\nimport {renderMermaid} from '../utils/renderMermaid';\n\nexport function useMermaid(\n containerRef: RefObject<HTMLDivElement | null>,\n html: string,\n openLinksInNewTab = false,\n) {\n const themeContext = useContext(ThemeContext);\n const theme = getThemeType(themeContext?.themeValue ?? 'light');\n const cacheRef = useRef(new Map<string, string>());\n\n useEffect(() => {\n const blocks = Array.from(\n containerRef.current?.querySelectorAll<HTMLElement>(`.${MARKDOWN_MERMAID_CLASS}`) ?? [],\n );\n const entries = blocks.map((block, index) => ({\n block,\n source: block.querySelector('code')?.textContent ?? '',\n // Identical diagrams in one message need distinct SVG identifiers.\n key: `${theme}:${index}:${block.querySelector('code')?.textContent ?? ''}`,\n }));\n const keys = new Set(entries.map(({key}) => key));\n const cache = cacheRef.current;\n for (const key of cache.keys()) {\n if (!keys.has(key)) {\n cache.delete(key);\n }\n }\n\n let active = true;\n const isActive = () => active;\n const cleanups: (() => void)[] = [];\n const display = (block: HTMLElement, svg: string) => {\n const source = block.querySelector('pre');\n const diagram = document.createElement('div');\n diagram.className = `${MARKDOWN_MERMAID_CLASS}-diagram`;\n diagram.innerHTML = svg;\n if (openLinksInNewTab) {\n applyNewTabLinkAttributes(diagram);\n }\n const svgElement = diagram.querySelector('svg');\n const width = svgElement?.viewBox.baseVal.width;\n if (svgElement && width) {\n svgElement.setAttribute('width', String(width));\n svgElement.style.maxWidth = 'none';\n }\n block.appendChild(diagram);\n block.setAttribute('tabindex', '0');\n if (source) {\n source.hidden = true;\n }\n cleanups.push(() => {\n diagram.remove();\n block.removeAttribute('tabindex');\n if (source) {\n source.hidden = false;\n }\n });\n };\n\n const pending = entries.filter(({block, key}) => {\n const cached = cache.get(key);\n if (cached) {\n display(block, cached);\n return false;\n }\n return true;\n });\n\n // Coalesce rapid streaming updates; incomplete syntax stays as source.\n const timeout = window.setTimeout(async () => {\n for (const {block, source, key} of pending) {\n if (!active) {\n return;\n }\n try {\n const svg = await renderMermaid(source, theme, isActive);\n if (active && svg) {\n cache.set(key, svg);\n display(block, svg);\n }\n } catch {\n // Keep the escaped source block and continue with other diagrams.\n }\n }\n }, 150);\n\n return () => {\n active = false;\n window.clearTimeout(timeout);\n cleanups.forEach((cleanup) => cleanup());\n };\n }, [containerRef, html, theme, openLinksInNewTab]);\n}\n"]}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.useSmartScroll = useSmartScroll;
|
|
4
4
|
const react_1 = require("react");
|
|
5
|
+
const observeScrollContent_1 = require("../utils/observeScrollContent.js");
|
|
5
6
|
const SCROLL_THRESHOLD = 10;
|
|
6
7
|
function useSmartScroll({ isStreaming = false, messagesCount, status, }) {
|
|
7
8
|
const containerRef = (0, react_1.useRef)(null);
|
|
@@ -41,19 +42,14 @@ function useSmartScroll({ isStreaming = false, messagesCount, status, }) {
|
|
|
41
42
|
container.removeEventListener('scroll', handleScroll);
|
|
42
43
|
};
|
|
43
44
|
}, []);
|
|
44
|
-
// Keep
|
|
45
|
-
//
|
|
46
|
-
// bottom of the list would otherwise slide out of view.
|
|
45
|
+
// Keep pinned after delayed content renders as well as viewport changes,
|
|
46
|
+
// even when the response has already finished streaming.
|
|
47
47
|
(0, react_1.useEffect)(() => {
|
|
48
48
|
const container = containerRef.current;
|
|
49
49
|
if (!container || typeof ResizeObserver === 'undefined') {
|
|
50
50
|
return undefined;
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
observer.observe(container);
|
|
54
|
-
return () => {
|
|
55
|
-
observer.disconnect();
|
|
56
|
-
};
|
|
52
|
+
return (0, observeScrollContent_1.observeScrollContent)(container, () => scrollToBottom('instant'));
|
|
57
53
|
}, [scrollToBottom]);
|
|
58
54
|
// Handle DOM mutations during streaming
|
|
59
55
|
(0, react_1.useEffect)(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSmartScroll.js","sourceRoot":"../../../src","sources":["hooks/useSmartScroll.tsx"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"useSmartScroll.js","sourceRoot":"../../../src","sources":["hooks/useSmartScroll.tsx"],"names":[],"mappings":"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","sourcesContent":["import {type RefObject, useCallback, useEffect, useRef} from 'react';\n\nimport type {ChatStatus} from '../types';\nimport {observeScrollContent} from '../utils/observeScrollContent';\n\nexport interface UseSmartScrollReturn<T extends HTMLElement> {\n containerRef: RefObject<T>;\n scrollToBottom: (behavior?: ScrollBehavior) => void;\n}\n\nconst SCROLL_THRESHOLD = 10;\n\nexport function useSmartScroll<T extends HTMLElement>({\n isStreaming = false,\n messagesCount,\n status,\n}: {\n isStreaming?: boolean;\n messagesCount: number;\n status?: ChatStatus;\n}): UseSmartScrollReturn<T> {\n const containerRef = useRef<T>(null);\n const userScrolledUpRef = useRef(false);\n\n const scrollToBottom = useCallback((behavior: ScrollBehavior = 'instant') => {\n if (!userScrolledUpRef.current) {\n const container = containerRef.current;\n if (container) {\n container.scrollTo({\n top: container.scrollHeight,\n behavior,\n });\n }\n }\n }, []);\n\n // Initial scroll to bottom\n useEffect(() => {\n scrollToBottom();\n }, []);\n\n // Handle user scroll events\n useEffect(() => {\n const container = containerRef.current;\n if (!container) {\n return undefined;\n }\n\n const handleScroll = () => {\n if (!container) {\n return;\n }\n\n const {scrollTop, scrollHeight, clientHeight} = container;\n const distanceFromBottom = scrollHeight - scrollTop - clientHeight;\n\n const scrolledUp = distanceFromBottom > SCROLL_THRESHOLD;\n userScrolledUpRef.current = scrolledUp;\n };\n\n container.addEventListener('scroll', handleScroll, {passive: true});\n return () => {\n container.removeEventListener('scroll', handleScroll);\n };\n }, []);\n\n // Keep pinned after delayed content renders as well as viewport changes,\n // even when the response has already finished streaming.\n useEffect(() => {\n const container = containerRef.current;\n if (!container || typeof ResizeObserver === 'undefined') {\n return undefined;\n }\n\n return observeScrollContent(container, () => scrollToBottom('instant'));\n }, [scrollToBottom]);\n\n // Handle DOM mutations during streaming\n useEffect(() => {\n const container = containerRef.current;\n if (!container || !isStreaming) {\n return undefined;\n }\n\n const observer = new MutationObserver(() => {\n scrollToBottom('instant');\n });\n\n observer.observe(container, {\n childList: true,\n subtree: true,\n attributes: true,\n characterData: true,\n });\n\n return () => {\n observer.disconnect();\n };\n }, [isStreaming]);\n\n // Handle status changes\n useEffect(() => {\n scrollToBottom('smooth');\n }, [status]);\n\n useEffect(() => {\n if (messagesCount) {\n scrollToBottom('smooth');\n }\n }, [messagesCount]);\n\n return {\n containerRef,\n scrollToBottom,\n };\n}\n"]}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.useVirtualStickToBottom = useVirtualStickToBottom;
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const react_window_1 = require("react-window");
|
|
6
|
+
const observeScrollContent_1 = require("../utils/observeScrollContent.js");
|
|
6
7
|
const SCROLL_THRESHOLD = 10;
|
|
7
8
|
// Number of animation frames over which a scroll correction is re-applied. react-window measures
|
|
8
9
|
// rows after paint (ResizeObserver -> state update -> re-render) and keys heights by row index, so
|
|
@@ -153,19 +154,14 @@ function useVirtualStickToBottom({ rowCount, isStreaming = false, status, messag
|
|
|
153
154
|
}
|
|
154
155
|
return cancelPendingScroll;
|
|
155
156
|
}, [listApi, pinToBottom, cancelPendingScroll]);
|
|
156
|
-
// Re-pin
|
|
157
|
-
//
|
|
158
|
-
// otherwise end up below the fold.
|
|
157
|
+
// Re-pin after delayed row measurements (including diagrams) and viewport changes.
|
|
158
|
+
// pinToBottom preserves user scroll intent and the existing prepend guard.
|
|
159
159
|
(0, react_1.useEffect)(() => {
|
|
160
160
|
const element = listApi === null || listApi === void 0 ? void 0 : listApi.element;
|
|
161
161
|
if (!element || typeof ResizeObserver === 'undefined') {
|
|
162
162
|
return undefined;
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
observer.observe(element);
|
|
166
|
-
return () => {
|
|
167
|
-
observer.disconnect();
|
|
168
|
-
};
|
|
164
|
+
return (0, observeScrollContent_1.observeScrollContent)(element, pinToBottom);
|
|
169
165
|
}, [listApi, pinToBottom]);
|
|
170
166
|
// Preserve the viewport when older messages are prepended (load previous) so the list doesn't
|
|
171
167
|
// jump. A prepend is detected by a change of the first message id together with a growing count
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useVirtualStickToBottom.js","sourceRoot":"../../../src","sources":["hooks/useVirtualStickToBottom.ts"],"names":[],"mappings":"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","sourcesContent":["import {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react';\n\nimport {useListCallbackRef} from 'react-window';\n\nimport type {ChatStatus} from '../types';\n\nconst SCROLL_THRESHOLD = 10;\n// Number of animation frames over which a scroll correction is re-applied. react-window measures\n// rows after paint (ResizeObserver -> state update -> re-render) and keys heights by row index, so\n// both pinning to the bottom and restoring an anchor must be re-applied until the heights settle.\nconst REANCHOR_FRAMES = 8;\n// Minimum time the prepend shimmer overlay stays up. The scroll restore itself settles in a few\n// frames, but react-window paints the wrong window for a frame in between; a shimmer covers that\n// transition. Keeping it up a touch longer makes it read as an intentional loading animation\n// rather than a blink.\nconst PREPEND_SHIMMER_MS = 320;\n\nexport interface UseVirtualStickToBottomParams {\n /** Total number of rows rendered by the list, including non-message header rows. */\n rowCount: number;\n /** Whether a response is currently streaming (keep pinned to the growing last row). */\n isStreaming?: boolean;\n status?: ChatStatus;\n /** Current number of messages (drives auto-scroll on new messages). */\n messagesCount: number;\n /** Stable id of the first message - used to distinguish prepends (load older) from appends. */\n firstMessageId?: string;\n /** Number of non-message rows rendered before the messages (e.g. the load-more trigger). */\n headerOffset?: number;\n /**\n * Value that changes whenever the streaming (last) row's content updates - typically the\n * last message reference. react-window only fires `onRowsRendered` when the set of visible\n * rows changes, not when an already-mounted row is re-measured taller, so an explicit signal\n * is needed to keep pinning to the bottom while a single row grows in place.\n */\n streamingSignal?: unknown;\n /** Value that changes when a trailing non-message row appears or disappears. */\n trailingContentSignal?: unknown;\n}\n\n/**\n * Stick-to-bottom / reverse-scroll behavior for the virtualized message list.\n *\n * Replaces the MutationObserver-based {@link useSmartScroll} (which cannot work under\n * virtualization, since off-screen rows are not in the DOM and the scroll container is owned\n * by react-window).\n *\n * Off-screen row heights are only estimates in react-window, so neither `scrollHeight` nor a single\n * `scrollToRow` call is reliable on its own. Both corrections here are therefore re-applied across a\n * few frames (until measured heights settle), and prepend preservation anchors to a real rendered\n * row (`data-message-id`) near the viewport rather than to the distant, still-estimated bottom:\n * - keeps the last row pinned to the bottom while streaming, unless the user scrolled up;\n * - preserves the on-screen position of the top visible message when older messages are prepended.\n */\nexport function useVirtualStickToBottom({\n rowCount,\n isStreaming = false,\n status,\n messagesCount,\n firstMessageId,\n headerOffset = 0,\n streamingSignal,\n trailingContentSignal,\n}: UseVirtualStickToBottomParams) {\n const [listApi, listRef] = useListCallbackRef();\n // Drives a shimmer overlay while older messages are being prepended and the scroll is restored.\n const [isPrepending, setIsPrepending] = useState(false);\n const userScrolledUpRef = useRef(false);\n const prependingRef = useRef(false);\n const shimmerTimerRef = useRef<ReturnType<typeof setTimeout>>();\n const prevFirstIdRef = useRef(firstMessageId);\n const prevMessagesCountRef = useRef(messagesCount);\n // Top offset (relative to the scroll container) of the first message row, captured on scroll.\n // A prepend restores this offset for the same message so the viewport doesn't jump. Null until\n // the first message has been seen in the DOM.\n const firstMsgOffsetRef = useRef<number | null>(null);\n // Last observed scrollTop - lets the scroll listener tell a user scroll-up (scrollTop decreases)\n // from our own pin-to-bottom (scrollTop only ever increases toward the bottom). This direction\n // check replaces a \"programmatic scroll\" flag, which during streaming was set almost every frame\n // by the multi-frame pin and so swallowed the user's scroll-up, breaking auto-stick interruption.\n const lastScrollTopRef = useRef(0);\n // Handle of the in-flight multi-frame correction so a new one cancels the previous.\n const rafRef = useRef(0);\n // Handle of the debounced first-message-offset capture. Measured only after scrolling settles,\n // so we read the first message at its resting position (and skip the transient mid-jump frames\n // of a programmatic pin, where react-window has not yet unmounted the old rows).\n const captureTimerRef = useRef<ReturnType<typeof setTimeout>>();\n // Latest values read through refs so the scroll callbacks stay referentially stable. Otherwise a\n // prepend changes rowCount -> a new pinToBottom -> the initial effect re-runs and its cleanup\n // cancels the in-flight prepend restore (which then leaves the viewport jumped).\n const listApiRef = useRef(listApi);\n listApiRef.current = listApi;\n const rowCountRef = useRef(rowCount);\n rowCountRef.current = rowCount;\n const firstMessageIdRef = useRef(firstMessageId);\n firstMessageIdRef.current = firstMessageId;\n\n const cancelPendingScroll = useCallback(() => {\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = 0;\n }\n }, []);\n\n // Pin to the true bottom, re-applying across frames: react-window's `scrollToRow` targets a\n // position computed from (partly estimated) row heights, so a single call lands short; once the\n // bottom rows render and are measured, the next frame re-targets the now-lower bottom.\n const pinToBottom = useCallback(() => {\n if (userScrolledUpRef.current || prependingRef.current || rowCountRef.current <= 0) {\n return;\n }\n cancelPendingScroll();\n let frame = 0;\n const step = () => {\n const api = listApiRef.current;\n if (userScrolledUpRef.current || prependingRef.current || !api) {\n return;\n }\n api.scrollToRow({index: rowCountRef.current - 1, align: 'end', behavior: 'instant'});\n frame += 1;\n if (frame < REANCHOR_FRAMES) {\n rafRef.current = requestAnimationFrame(step);\n }\n };\n step();\n }, [cancelPendingScroll]);\n\n // Record the first message row's top offset (relative to the scroll container) so a prepend can\n // restore it. Only meaningful while the first message is rendered, i.e. when scrolled near the\n // top - which is exactly when a load-previous can be triggered.\n const captureFirstOffset = useCallback((element: HTMLElement) => {\n const id = firstMessageIdRef.current;\n if (id === undefined) {\n return;\n }\n const row = element.querySelector<HTMLElement>(`[data-message-id=\"${CSS.escape(id)}\"]`);\n if (row) {\n firstMsgOffsetRef.current =\n row.getBoundingClientRect().top - element.getBoundingClientRect().top;\n }\n }, []);\n\n // Track scroll direction to decide whether auto-stick should pause. A user scroll-up moves the\n // content down (scrollTop decreases); our pin-to-bottom only ever moves toward the bottom\n // (scrollTop increases), so it never trips the \"scrolled up\" branch even when it momentarily\n // undershoots the still-estimated bottom.\n useEffect(() => {\n const element = listApi?.element;\n if (!element) {\n return undefined;\n }\n\n lastScrollTopRef.current = element.scrollTop;\n\n const handleScroll = () => {\n const {scrollTop, scrollHeight, clientHeight} = element;\n const prevTop = lastScrollTopRef.current;\n lastScrollTopRef.current = scrollTop;\n\n // Ignore scrolls produced by an in-flight prepend restore - it is not user intent.\n if (prependingRef.current) {\n return;\n }\n\n const distanceFromBottom = scrollHeight - scrollTop - clientHeight;\n if (scrollTop < prevTop - 1) {\n // User scrolled up: pause auto-stick unless they are still effectively at the bottom.\n userScrolledUpRef.current = distanceFromBottom > SCROLL_THRESHOLD;\n } else if (distanceFromBottom <= SCROLL_THRESHOLD) {\n // At (or back to) the bottom: resume auto-stick.\n userScrolledUpRef.current = false;\n }\n // Measure the first message's offset only after scrolling settles: react-window renders\n // the rows for the new position asynchronously, and a programmatic pin jumps through\n // intermediate positions, so a synchronous (or single-frame) read can capture a stale\n // offset for a row about to be unmounted.\n clearTimeout(captureTimerRef.current);\n captureTimerRef.current = setTimeout(() => {\n if (!prependingRef.current) {\n captureFirstOffset(element);\n }\n }, 120);\n };\n\n element.addEventListener('scroll', handleScroll, {passive: true});\n return () => {\n element.removeEventListener('scroll', handleScroll);\n clearTimeout(captureTimerRef.current);\n };\n }, [listApi, captureFirstOffset]);\n\n // Initial scroll to bottom once the list element is mounted.\n useEffect(() => {\n if (listApi) {\n pinToBottom();\n }\n return cancelPendingScroll;\n }, [listApi, pinToBottom, cancelPendingScroll]);\n\n // Re-pin when the scroll viewport itself is resized - typically the on-screen keyboard opening\n // on mobile and shrinking the chat. The browser keeps `scrollTop`, so the last row would\n // otherwise end up below the fold.\n useEffect(() => {\n const element = listApi?.element;\n if (!element || typeof ResizeObserver === 'undefined') {\n return undefined;\n }\n\n const observer = new ResizeObserver(() => pinToBottom());\n observer.observe(element);\n\n return () => {\n observer.disconnect();\n };\n }, [listApi, pinToBottom]);\n\n // Preserve the viewport when older messages are prepended (load previous) so the list doesn't\n // jump. A prepend is detected by a change of the first message id together with a growing count\n // while the user is scrolled up (a bottom append - e.g. a new streamed message - leaves the\n // first id unchanged, so it is ignored here and handled by the stick-to-bottom effect).\n //\n // Two stages, re-applied across frames (react-window measures rows after paint), stopping early\n // once the anchor is stable to avoid extra re-renders:\n // 1. coarse - `scrollToRow` to the previous top message's new index, which forces react-window\n // to render that region (it is off-screen right after a prepend, so a pure DOM lookup would\n // find nothing - the cause of the \"jumps to the newly loaded top\" bug);\n // 2. fine - once that row is in the DOM, nudge `scrollTop` so it sits at the exact offset it\n // had before the prepend (preserves the load-trigger gap / intra-message offset, instead of\n // snapping it to the very top).\n useLayoutEffect(() => {\n const prevFirstId = prevFirstIdRef.current;\n const prevCount = prevMessagesCountRef.current;\n prevFirstIdRef.current = firstMessageId;\n prevMessagesCountRef.current = messagesCount;\n\n const element = listApi?.element;\n if (!element) {\n return undefined;\n }\n\n const isPrepend =\n userScrolledUpRef.current &&\n messagesCount > prevCount &&\n prevCount > 0 &&\n firstMessageId !== undefined &&\n prevFirstId !== undefined &&\n firstMessageId !== prevFirstId;\n\n if (!isPrepend || prevFirstId === undefined) {\n return undefined;\n }\n\n // Suppress the post-paint auto-scroll-to-bottom while restoring this commit's position.\n prependingRef.current = true;\n cancelPendingScroll();\n\n // Cover the list with a shimmer while we re-anchor. react-window applies scroll via a native\n // scroll and only re-renders the new window on the (async) scroll event, so right after a\n // prepend it paints the offset-0 window (the freshly loaded top) for a frame before our\n // correction takes hold. The overlay hides that transition behind an intentional loading\n // animation instead of a flash of the wrong messages.\n clearTimeout(shimmerTimerRef.current);\n setIsPrepending(true);\n element.style.visibility = 'hidden';\n shimmerTimerRef.current = setTimeout(() => {\n element.style.visibility = '';\n setIsPrepending(false);\n }, PREPEND_SHIMMER_MS);\n\n // Releases only the scroll-gating flag; the overlay/visibility are cleared by the timer above\n // so the shimmer stays up for its minimum duration even though the scroll settles sooner.\n const finish = () => {\n prependingRef.current = false;\n };\n\n const anchorId = prevFirstId;\n const savedOffset = firstMsgOffsetRef.current ?? 0;\n // The previous first message shifted down by the number of prepended messages.\n const anchorIndex = messagesCount - prevCount + headerOffset;\n\n let frame = 0;\n let stableFrames = 0;\n const step = () => {\n const api = listApiRef.current;\n if (!api) {\n finish();\n return;\n }\n\n const row = element.querySelector<HTMLElement>(\n `[data-message-id=\"${CSS.escape(anchorId)}\"]`,\n );\n let positioned = false;\n if (row) {\n // Fine: move the anchor row to the offset it had before the prepend.\n const currentTop =\n row.getBoundingClientRect().top - element.getBoundingClientRect().top;\n const delta = currentTop - savedOffset;\n if (Math.abs(delta) >= 1) {\n element.scrollTop += delta;\n } else {\n positioned = true;\n }\n } else {\n // Coarse: the anchor is not rendered yet - scroll to its index to render it.\n api.scrollToRow({index: anchorIndex, align: 'start', behavior: 'instant'});\n }\n\n frame += 1;\n stableFrames = positioned ? stableFrames + 1 : 0;\n // Stop once the anchor has held its position for two frames (heights settled) or the cap\n // is hit, so we don't keep forcing react-window to re-render every frame.\n if (frame < REANCHOR_FRAMES && stableFrames < 2) {\n rafRef.current = requestAnimationFrame(step);\n } else {\n finish();\n }\n };\n step();\n\n return () => {\n cancelPendingScroll();\n clearTimeout(shimmerTimerRef.current);\n prependingRef.current = false;\n element.style.visibility = '';\n setIsPrepending(false);\n };\n }, [messagesCount, firstMessageId, headerOffset, listApi, cancelPendingScroll]);\n\n // Scroll to bottom on status changes (mirrors useSmartScroll).\n useEffect(() => {\n pinToBottom();\n }, [status, pinToBottom]);\n\n // Scroll to bottom on new messages.\n useEffect(() => {\n if (messagesCount) {\n pinToBottom();\n }\n }, [messagesCount, pinToBottom]);\n\n useEffect(() => {\n pinToBottom();\n }, [trailingContentSignal, pinToBottom]);\n\n // During streaming the last row grows in place without changing the rendered row set, so\n // `onRowsRendered` does not fire. Pin to the bottom whenever the streaming content signal\n // changes instead.\n useEffect(() => {\n if (!isStreaming) {\n return undefined;\n }\n pinToBottom();\n return undefined;\n }, [streamingSignal, isStreaming, pinToBottom]);\n\n return {listRef, isPrepending};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useVirtualStickToBottom.js","sourceRoot":"../../../src","sources":["hooks/useVirtualStickToBottom.ts"],"names":[],"mappings":"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","sourcesContent":["import {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react';\n\nimport {useListCallbackRef} from 'react-window';\n\nimport type {ChatStatus} from '../types';\nimport {observeScrollContent} from '../utils/observeScrollContent';\n\nconst SCROLL_THRESHOLD = 10;\n// Number of animation frames over which a scroll correction is re-applied. react-window measures\n// rows after paint (ResizeObserver -> state update -> re-render) and keys heights by row index, so\n// both pinning to the bottom and restoring an anchor must be re-applied until the heights settle.\nconst REANCHOR_FRAMES = 8;\n// Minimum time the prepend shimmer overlay stays up. The scroll restore itself settles in a few\n// frames, but react-window paints the wrong window for a frame in between; a shimmer covers that\n// transition. Keeping it up a touch longer makes it read as an intentional loading animation\n// rather than a blink.\nconst PREPEND_SHIMMER_MS = 320;\n\nexport interface UseVirtualStickToBottomParams {\n /** Total number of rows rendered by the list, including non-message header rows. */\n rowCount: number;\n /** Whether a response is currently streaming (keep pinned to the growing last row). */\n isStreaming?: boolean;\n status?: ChatStatus;\n /** Current number of messages (drives auto-scroll on new messages). */\n messagesCount: number;\n /** Stable id of the first message - used to distinguish prepends (load older) from appends. */\n firstMessageId?: string;\n /** Number of non-message rows rendered before the messages (e.g. the load-more trigger). */\n headerOffset?: number;\n /**\n * Value that changes whenever the streaming (last) row's content updates - typically the\n * last message reference. react-window only fires `onRowsRendered` when the set of visible\n * rows changes, not when an already-mounted row is re-measured taller, so an explicit signal\n * is needed to keep pinning to the bottom while a single row grows in place.\n */\n streamingSignal?: unknown;\n /** Value that changes when a trailing non-message row appears or disappears. */\n trailingContentSignal?: unknown;\n}\n\n/**\n * Stick-to-bottom / reverse-scroll behavior for the virtualized message list.\n *\n * Replaces the MutationObserver-based {@link useSmartScroll} (which cannot work under\n * virtualization, since off-screen rows are not in the DOM and the scroll container is owned\n * by react-window).\n *\n * Off-screen row heights are only estimates in react-window, so neither `scrollHeight` nor a single\n * `scrollToRow` call is reliable on its own. Both corrections here are therefore re-applied across a\n * few frames (until measured heights settle), and prepend preservation anchors to a real rendered\n * row (`data-message-id`) near the viewport rather than to the distant, still-estimated bottom:\n * - keeps the last row pinned to the bottom while streaming, unless the user scrolled up;\n * - preserves the on-screen position of the top visible message when older messages are prepended.\n */\nexport function useVirtualStickToBottom({\n rowCount,\n isStreaming = false,\n status,\n messagesCount,\n firstMessageId,\n headerOffset = 0,\n streamingSignal,\n trailingContentSignal,\n}: UseVirtualStickToBottomParams) {\n const [listApi, listRef] = useListCallbackRef();\n // Drives a shimmer overlay while older messages are being prepended and the scroll is restored.\n const [isPrepending, setIsPrepending] = useState(false);\n const userScrolledUpRef = useRef(false);\n const prependingRef = useRef(false);\n const shimmerTimerRef = useRef<ReturnType<typeof setTimeout>>();\n const prevFirstIdRef = useRef(firstMessageId);\n const prevMessagesCountRef = useRef(messagesCount);\n // Top offset (relative to the scroll container) of the first message row, captured on scroll.\n // A prepend restores this offset for the same message so the viewport doesn't jump. Null until\n // the first message has been seen in the DOM.\n const firstMsgOffsetRef = useRef<number | null>(null);\n // Last observed scrollTop - lets the scroll listener tell a user scroll-up (scrollTop decreases)\n // from our own pin-to-bottom (scrollTop only ever increases toward the bottom). This direction\n // check replaces a \"programmatic scroll\" flag, which during streaming was set almost every frame\n // by the multi-frame pin and so swallowed the user's scroll-up, breaking auto-stick interruption.\n const lastScrollTopRef = useRef(0);\n // Handle of the in-flight multi-frame correction so a new one cancels the previous.\n const rafRef = useRef(0);\n // Handle of the debounced first-message-offset capture. Measured only after scrolling settles,\n // so we read the first message at its resting position (and skip the transient mid-jump frames\n // of a programmatic pin, where react-window has not yet unmounted the old rows).\n const captureTimerRef = useRef<ReturnType<typeof setTimeout>>();\n // Latest values read through refs so the scroll callbacks stay referentially stable. Otherwise a\n // prepend changes rowCount -> a new pinToBottom -> the initial effect re-runs and its cleanup\n // cancels the in-flight prepend restore (which then leaves the viewport jumped).\n const listApiRef = useRef(listApi);\n listApiRef.current = listApi;\n const rowCountRef = useRef(rowCount);\n rowCountRef.current = rowCount;\n const firstMessageIdRef = useRef(firstMessageId);\n firstMessageIdRef.current = firstMessageId;\n\n const cancelPendingScroll = useCallback(() => {\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = 0;\n }\n }, []);\n\n // Pin to the true bottom, re-applying across frames: react-window's `scrollToRow` targets a\n // position computed from (partly estimated) row heights, so a single call lands short; once the\n // bottom rows render and are measured, the next frame re-targets the now-lower bottom.\n const pinToBottom = useCallback(() => {\n if (userScrolledUpRef.current || prependingRef.current || rowCountRef.current <= 0) {\n return;\n }\n cancelPendingScroll();\n let frame = 0;\n const step = () => {\n const api = listApiRef.current;\n if (userScrolledUpRef.current || prependingRef.current || !api) {\n return;\n }\n api.scrollToRow({index: rowCountRef.current - 1, align: 'end', behavior: 'instant'});\n frame += 1;\n if (frame < REANCHOR_FRAMES) {\n rafRef.current = requestAnimationFrame(step);\n }\n };\n step();\n }, [cancelPendingScroll]);\n\n // Record the first message row's top offset (relative to the scroll container) so a prepend can\n // restore it. Only meaningful while the first message is rendered, i.e. when scrolled near the\n // top - which is exactly when a load-previous can be triggered.\n const captureFirstOffset = useCallback((element: HTMLElement) => {\n const id = firstMessageIdRef.current;\n if (id === undefined) {\n return;\n }\n const row = element.querySelector<HTMLElement>(`[data-message-id=\"${CSS.escape(id)}\"]`);\n if (row) {\n firstMsgOffsetRef.current =\n row.getBoundingClientRect().top - element.getBoundingClientRect().top;\n }\n }, []);\n\n // Track scroll direction to decide whether auto-stick should pause. A user scroll-up moves the\n // content down (scrollTop decreases); our pin-to-bottom only ever moves toward the bottom\n // (scrollTop increases), so it never trips the \"scrolled up\" branch even when it momentarily\n // undershoots the still-estimated bottom.\n useEffect(() => {\n const element = listApi?.element;\n if (!element) {\n return undefined;\n }\n\n lastScrollTopRef.current = element.scrollTop;\n\n const handleScroll = () => {\n const {scrollTop, scrollHeight, clientHeight} = element;\n const prevTop = lastScrollTopRef.current;\n lastScrollTopRef.current = scrollTop;\n\n // Ignore scrolls produced by an in-flight prepend restore - it is not user intent.\n if (prependingRef.current) {\n return;\n }\n\n const distanceFromBottom = scrollHeight - scrollTop - clientHeight;\n if (scrollTop < prevTop - 1) {\n // User scrolled up: pause auto-stick unless they are still effectively at the bottom.\n userScrolledUpRef.current = distanceFromBottom > SCROLL_THRESHOLD;\n } else if (distanceFromBottom <= SCROLL_THRESHOLD) {\n // At (or back to) the bottom: resume auto-stick.\n userScrolledUpRef.current = false;\n }\n // Measure the first message's offset only after scrolling settles: react-window renders\n // the rows for the new position asynchronously, and a programmatic pin jumps through\n // intermediate positions, so a synchronous (or single-frame) read can capture a stale\n // offset for a row about to be unmounted.\n clearTimeout(captureTimerRef.current);\n captureTimerRef.current = setTimeout(() => {\n if (!prependingRef.current) {\n captureFirstOffset(element);\n }\n }, 120);\n };\n\n element.addEventListener('scroll', handleScroll, {passive: true});\n return () => {\n element.removeEventListener('scroll', handleScroll);\n clearTimeout(captureTimerRef.current);\n };\n }, [listApi, captureFirstOffset]);\n\n // Initial scroll to bottom once the list element is mounted.\n useEffect(() => {\n if (listApi) {\n pinToBottom();\n }\n return cancelPendingScroll;\n }, [listApi, pinToBottom, cancelPendingScroll]);\n\n // Re-pin after delayed row measurements (including diagrams) and viewport changes.\n // pinToBottom preserves user scroll intent and the existing prepend guard.\n useEffect(() => {\n const element = listApi?.element;\n if (!element || typeof ResizeObserver === 'undefined') {\n return undefined;\n }\n\n return observeScrollContent(element, pinToBottom);\n }, [listApi, pinToBottom]);\n\n // Preserve the viewport when older messages are prepended (load previous) so the list doesn't\n // jump. A prepend is detected by a change of the first message id together with a growing count\n // while the user is scrolled up (a bottom append - e.g. a new streamed message - leaves the\n // first id unchanged, so it is ignored here and handled by the stick-to-bottom effect).\n //\n // Two stages, re-applied across frames (react-window measures rows after paint), stopping early\n // once the anchor is stable to avoid extra re-renders:\n // 1. coarse - `scrollToRow` to the previous top message's new index, which forces react-window\n // to render that region (it is off-screen right after a prepend, so a pure DOM lookup would\n // find nothing - the cause of the \"jumps to the newly loaded top\" bug);\n // 2. fine - once that row is in the DOM, nudge `scrollTop` so it sits at the exact offset it\n // had before the prepend (preserves the load-trigger gap / intra-message offset, instead of\n // snapping it to the very top).\n useLayoutEffect(() => {\n const prevFirstId = prevFirstIdRef.current;\n const prevCount = prevMessagesCountRef.current;\n prevFirstIdRef.current = firstMessageId;\n prevMessagesCountRef.current = messagesCount;\n\n const element = listApi?.element;\n if (!element) {\n return undefined;\n }\n\n const isPrepend =\n userScrolledUpRef.current &&\n messagesCount > prevCount &&\n prevCount > 0 &&\n firstMessageId !== undefined &&\n prevFirstId !== undefined &&\n firstMessageId !== prevFirstId;\n\n if (!isPrepend || prevFirstId === undefined) {\n return undefined;\n }\n\n // Suppress the post-paint auto-scroll-to-bottom while restoring this commit's position.\n prependingRef.current = true;\n cancelPendingScroll();\n\n // Cover the list with a shimmer while we re-anchor. react-window applies scroll via a native\n // scroll and only re-renders the new window on the (async) scroll event, so right after a\n // prepend it paints the offset-0 window (the freshly loaded top) for a frame before our\n // correction takes hold. The overlay hides that transition behind an intentional loading\n // animation instead of a flash of the wrong messages.\n clearTimeout(shimmerTimerRef.current);\n setIsPrepending(true);\n element.style.visibility = 'hidden';\n shimmerTimerRef.current = setTimeout(() => {\n element.style.visibility = '';\n setIsPrepending(false);\n }, PREPEND_SHIMMER_MS);\n\n // Releases only the scroll-gating flag; the overlay/visibility are cleared by the timer above\n // so the shimmer stays up for its minimum duration even though the scroll settles sooner.\n const finish = () => {\n prependingRef.current = false;\n };\n\n const anchorId = prevFirstId;\n const savedOffset = firstMsgOffsetRef.current ?? 0;\n // The previous first message shifted down by the number of prepended messages.\n const anchorIndex = messagesCount - prevCount + headerOffset;\n\n let frame = 0;\n let stableFrames = 0;\n const step = () => {\n const api = listApiRef.current;\n if (!api) {\n finish();\n return;\n }\n\n const row = element.querySelector<HTMLElement>(\n `[data-message-id=\"${CSS.escape(anchorId)}\"]`,\n );\n let positioned = false;\n if (row) {\n // Fine: move the anchor row to the offset it had before the prepend.\n const currentTop =\n row.getBoundingClientRect().top - element.getBoundingClientRect().top;\n const delta = currentTop - savedOffset;\n if (Math.abs(delta) >= 1) {\n element.scrollTop += delta;\n } else {\n positioned = true;\n }\n } else {\n // Coarse: the anchor is not rendered yet - scroll to its index to render it.\n api.scrollToRow({index: anchorIndex, align: 'start', behavior: 'instant'});\n }\n\n frame += 1;\n stableFrames = positioned ? stableFrames + 1 : 0;\n // Stop once the anchor has held its position for two frames (heights settled) or the cap\n // is hit, so we don't keep forcing react-window to re-render every frame.\n if (frame < REANCHOR_FRAMES && stableFrames < 2) {\n rafRef.current = requestAnimationFrame(step);\n } else {\n finish();\n }\n };\n step();\n\n return () => {\n cancelPendingScroll();\n clearTimeout(shimmerTimerRef.current);\n prependingRef.current = false;\n element.style.visibility = '';\n setIsPrepending(false);\n };\n }, [messagesCount, firstMessageId, headerOffset, listApi, cancelPendingScroll]);\n\n // Scroll to bottom on status changes (mirrors useSmartScroll).\n useEffect(() => {\n pinToBottom();\n }, [status, pinToBottom]);\n\n // Scroll to bottom on new messages.\n useEffect(() => {\n if (messagesCount) {\n pinToBottom();\n }\n }, [messagesCount, pinToBottom]);\n\n useEffect(() => {\n pinToBottom();\n }, [trailingContentSignal, pinToBottom]);\n\n // During streaming the last row grows in place without changing the rendered row set, so\n // `onRowsRendered` does not fire. Pin to the bottom whenever the streaming content signal\n // changes instead.\n useEffect(() => {\n if (!isStreaming) {\n return undefined;\n }\n pinToBottom();\n return undefined;\n }, [streamingSignal, isStreaming, pinToBottom]);\n\n return {listRef, isPrepending};\n}\n"]}
|
package/build/cjs/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"2.23.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
|
|
1
|
+
{"version":"2.23.1-beta.d014c71fccccb7347cf97c255b2ae8d7b9880b79.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Shared policy for Markdown links and links emitted by diagram renderers. */
|
|
2
|
+
export declare function getNewTabLinkAttributes(href: string | null): {
|
|
3
|
+
target: string;
|
|
4
|
+
rel: string;
|
|
5
|
+
} | undefined;
|
|
6
|
+
export declare function applyNewTabLinkAttributes(container: ParentNode): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getNewTabLinkAttributes = getNewTabLinkAttributes;
|
|
4
|
+
exports.applyNewTabLinkAttributes = applyNewTabLinkAttributes;
|
|
5
|
+
const ABSOLUTE_HTTP_URL_RE = /^(https?:)?\/\//i;
|
|
6
|
+
const isSameDocumentAnchor = (href) => {
|
|
7
|
+
if (href.startsWith('#')) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
if (ABSOLUTE_HTTP_URL_RE.test(href) || typeof window === 'undefined') {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(href, window.location.href);
|
|
15
|
+
return (Boolean(url.hash) &&
|
|
16
|
+
url.origin === window.location.origin &&
|
|
17
|
+
url.pathname === window.location.pathname &&
|
|
18
|
+
url.search === window.location.search);
|
|
19
|
+
}
|
|
20
|
+
catch (_a) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/** Shared policy for Markdown links and links emitted by diagram renderers. */
|
|
25
|
+
function getNewTabLinkAttributes(href) {
|
|
26
|
+
if (href && isSameDocumentAnchor(href)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return { target: '_blank', rel: 'noopener noreferrer' };
|
|
30
|
+
}
|
|
31
|
+
function applyNewTabLinkAttributes(container) {
|
|
32
|
+
var _a;
|
|
33
|
+
for (const link of container.querySelectorAll('a')) {
|
|
34
|
+
const href = (_a = link.getAttribute('href')) !== null && _a !== void 0 ? _a : link.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
|
|
35
|
+
const attributes = getNewTabLinkAttributes(href);
|
|
36
|
+
if (attributes) {
|
|
37
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
38
|
+
link.setAttribute(name, value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=markdownLinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdownLinks.js","sourceRoot":"../../../src","sources":["utils/markdownLinks.ts"],"names":[],"mappings":";;AAyBA,0DAKC;AAED,8DAYC;AA5CD,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;AAEhD,MAAM,oBAAoB,GAAG,CAAC,IAAY,EAAE,EAAE;IAC1C,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QACnE,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAChD,OAAO,CACH,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YACjB,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM;YACrC,GAAG,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC,QAAQ;YACzC,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM,CACxC,CAAC;IACN,CAAC;IAAC,WAAM,CAAC;QACL,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC,CAAC;AAEF,+EAA+E;AAC/E,SAAgB,uBAAuB,CAAC,IAAmB;IACvD,IAAI,IAAI,IAAI,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,OAAO,EAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,qBAAqB,EAAC,CAAC;AAC1D,CAAC;AAED,SAAgB,yBAAyB,CAAC,SAAqB;;IAC3D,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,GACN,MAAA,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,mCACzB,IAAI,CAAC,cAAc,CAAC,8BAA8B,EAAE,MAAM,CAAC,CAAC;QAChE,MAAM,UAAU,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,UAAU,EAAE,CAAC;YACb,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBACrD,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACnC,CAAC;QACL,CAAC;IACL,CAAC;AACL,CAAC","sourcesContent":["const ABSOLUTE_HTTP_URL_RE = /^(https?:)?\\/\\//i;\n\nconst isSameDocumentAnchor = (href: string) => {\n if (href.startsWith('#')) {\n return true;\n }\n\n if (ABSOLUTE_HTTP_URL_RE.test(href) || typeof window === 'undefined') {\n return false;\n }\n\n try {\n const url = new URL(href, window.location.href);\n return (\n Boolean(url.hash) &&\n url.origin === window.location.origin &&\n url.pathname === window.location.pathname &&\n url.search === window.location.search\n );\n } catch {\n return false;\n }\n};\n\n/** Shared policy for Markdown links and links emitted by diagram renderers. */\nexport function getNewTabLinkAttributes(href: string | null) {\n if (href && isSameDocumentAnchor(href)) {\n return undefined;\n }\n return {target: '_blank', rel: 'noopener noreferrer'};\n}\n\nexport function applyNewTabLinkAttributes(container: ParentNode) {\n for (const link of container.querySelectorAll('a')) {\n const href =\n link.getAttribute('href') ??\n link.getAttributeNS('http://www.w3.org/1999/xlink', 'href');\n const attributes = getNewTabLinkAttributes(href);\n if (attributes) {\n for (const [name, value] of Object.entries(attributes)) {\n link.setAttribute(name, value);\n }\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ExtendedPluginWithCollect } from '@diplodoc/transform/lib/typings';
|
|
2
|
+
export declare const MARKDOWN_MERMAID_CLASS = "g-aikit-markdown-renderer__mermaid";
|
|
3
|
+
/** Keep the source readable until the browser successfully renders the diagram. */
|
|
4
|
+
export declare const markdownMermaidPlugin: ExtendedPluginWithCollect;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.markdownMermaidPlugin = exports.MARKDOWN_MERMAID_CLASS = void 0;
|
|
4
|
+
const cn_1 = require("./cn.js");
|
|
5
|
+
exports.MARKDOWN_MERMAID_CLASS = `${cn_1.NAMESPACE}markdown-renderer__mermaid`;
|
|
6
|
+
/** Keep the source readable until the browser successfully renders the diagram. */
|
|
7
|
+
exports.markdownMermaidPlugin = ((md) => {
|
|
8
|
+
const defaultFence = md.renderer.rules.fence ||
|
|
9
|
+
((tokens, index, options, _env, self) => self.renderToken(tokens, index, options));
|
|
10
|
+
// eslint-disable-next-line no-param-reassign
|
|
11
|
+
md.renderer.rules.fence = (tokens, index, options, env, self) => {
|
|
12
|
+
const token = tokens[index];
|
|
13
|
+
if (token.info.trim().split(/\s+/)[0] !== 'mermaid') {
|
|
14
|
+
return defaultFence(tokens, index, options, env, self);
|
|
15
|
+
}
|
|
16
|
+
return `<div class="${exports.MARKDOWN_MERMAID_CLASS}"><pre><code class="language-mermaid">${md.utils.escapeHtml(token.content)}</code></pre></div>`;
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
//# sourceMappingURL=markdownMermaidPlugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdownMermaidPlugin.js","sourceRoot":"../../../src","sources":["utils/markdownMermaidPlugin.ts"],"names":[],"mappings":";;;AAEA,gCAA+B;AAElB,QAAA,sBAAsB,GAAG,GAAG,cAAS,4BAA4B,CAAC;AAE/E,mFAAmF;AACtE,QAAA,qBAAqB,GAA8B,CAAC,CAAC,EAAc,EAAE,EAAE;IAChF,MAAM,YAAY,GACd,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK;QACvB,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;IAEvF,6CAA6C;IAC7C,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC5D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;YAClD,OAAO,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QAC3D,CAAC;QAED,OAAO,eAAe,8BAAsB,yCAAyC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,qBAAqB,CAAC;IACjJ,CAAC,CAAC;AACN,CAAC,CAA8B,CAAC","sourcesContent":["import type {ExtendedPluginWithCollect, MarkdownIt} from '@diplodoc/transform/lib/typings';\n\nimport {NAMESPACE} from './cn';\n\nexport const MARKDOWN_MERMAID_CLASS = `${NAMESPACE}markdown-renderer__mermaid`;\n\n/** Keep the source readable until the browser successfully renders the diagram. */\nexport const markdownMermaidPlugin: ExtendedPluginWithCollect = ((md: MarkdownIt) => {\n const defaultFence =\n md.renderer.rules.fence ||\n ((tokens, index, options, _env, self) => self.renderToken(tokens, index, options));\n\n // eslint-disable-next-line no-param-reassign\n md.renderer.rules.fence = (tokens, index, options, env, self) => {\n const token = tokens[index];\n if (token.info.trim().split(/\\s+/)[0] !== 'mermaid') {\n return defaultFence(tokens, index, options, env, self);\n }\n\n return `<div class=\"${MARKDOWN_MERMAID_CLASS}\"><pre><code class=\"language-mermaid\">${md.utils.escapeHtml(token.content)}</code></pre></div>`;\n };\n}) as ExtendedPluginWithCollect;\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OptionsType } from '@diplodoc/transform/lib/typings';
|
|
2
2
|
export declare function areOptionsEqual(prev?: OptionsType, next?: OptionsType): boolean;
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Adds AIKit table and Mermaid plugins and restores @diplodoc/transform defaults.
|
|
5
5
|
* Explicit `plugins` in transform options replace defaults — user plugins are appended after.
|
|
6
6
|
*/
|
|
7
7
|
export declare function mergeMarkdownTransformOptions(options?: OptionsType): OptionsType;
|