@stainless-api/docs 0.1.0-beta.11 → 0.1.0-beta.110
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/CHANGELOG.md +919 -0
- package/eslint-suppressions.json +27 -0
- package/locals.d.ts +17 -0
- package/package.json +50 -41
- package/playground-virtual-modules.d.ts +96 -0
- package/plugin/assets/languages/cli.svg +14 -0
- package/plugin/assets/languages/csharp.svg +1 -0
- package/plugin/assets/languages/php.svg +4 -0
- package/plugin/buildAlgoliaIndex.ts +40 -39
- package/plugin/components/MethodDescription.tsx +54 -0
- package/plugin/components/RequestBuilder/ParamEditor.tsx +55 -0
- package/plugin/components/RequestBuilder/SnippetStainlessIsland.tsx +107 -0
- package/plugin/components/RequestBuilder/index.tsx +37 -0
- package/plugin/components/RequestBuilder/props.ts +9 -0
- package/plugin/components/RequestBuilder/spec-helpers.ts +47 -0
- package/plugin/components/RequestBuilder/styles.css +67 -0
- package/plugin/components/SDKSelect.astro +18 -111
- package/plugin/components/SnippetCode.tsx +110 -68
- package/plugin/components/StainlessIslands.tsx +126 -0
- package/plugin/components/search/SearchAlgolia.astro +46 -29
- package/plugin/components/search/SearchIsland.tsx +47 -29
- package/plugin/generateAPIReferenceLink.ts +2 -2
- package/plugin/globalJs/ai-dropdown-options.ts +243 -0
- package/plugin/globalJs/code-snippets.ts +40 -11
- package/plugin/globalJs/copy.ts +95 -17
- package/plugin/globalJs/create-playground.shim.ts +3 -0
- package/plugin/globalJs/method-descriptions.ts +33 -0
- package/plugin/globalJs/navigation.ts +12 -32
- package/plugin/globalJs/playground-data.shim.ts +1 -0
- package/plugin/globalJs/playground-data.ts +14 -0
- package/plugin/helpers/generateDocsRoutes.ts +59 -0
- package/plugin/helpers/multiSpec.ts +8 -0
- package/plugin/index.ts +304 -138
- package/plugin/languages.ts +8 -2
- package/plugin/loadPluginConfig.ts +251 -107
- package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +3 -1
- package/plugin/react/Routing.tsx +212 -141
- package/plugin/referencePlaceholderUtils.ts +18 -15
- package/plugin/replaceSidebarPlaceholderMiddleware.ts +38 -34
- package/plugin/routes/Docs.astro +70 -111
- package/plugin/routes/DocsStatic.astro +6 -5
- package/plugin/routes/Overview.astro +45 -21
- package/plugin/routes/markdown.ts +13 -12
- package/plugin/{cms → sidebar-utils}/sidebar-builder.ts +49 -60
- package/plugin/specs/FileCache.ts +99 -0
- package/plugin/specs/fetchSpecSSR.ts +27 -0
- package/plugin/specs/generateSpec.ts +112 -0
- package/plugin/specs/index.ts +132 -0
- package/plugin/specs/inputResolver.ts +146 -0
- package/plugin/{cms → specs}/worker.ts +82 -5
- package/plugin/vendor/preview.worker.docs.js +27303 -18260
- package/plugin/vendor/templates/cli.md +1 -0
- package/plugin/vendor/templates/go.md +4 -2
- package/plugin/vendor/templates/java.md +5 -1
- package/plugin/vendor/templates/kotlin.md +5 -1
- package/plugin/vendor/templates/node.md +4 -2
- package/plugin/vendor/templates/python.md +4 -2
- package/plugin/vendor/templates/ruby.md +4 -2
- package/plugin/vendor/templates/terraform.md +1 -1
- package/plugin/vendor/templates/typescript.md +3 -1
- package/resolveSrcFile.ts +10 -0
- package/scripts/vendor_deps.ts +5 -5
- package/shared/conditionalIntegration.ts +28 -0
- package/shared/getProsePages.ts +41 -0
- package/shared/getSharedLogger.ts +15 -0
- package/shared/terminalUtils.ts +3 -0
- package/shared/virtualModule.ts +54 -1
- package/src/content.config.ts +9 -0
- package/stl-docs/components/AIDropdown.tsx +63 -0
- package/stl-docs/components/AiChatIsland.tsx +14 -0
- package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +2 -2
- package/stl-docs/components/Footer.astro +89 -0
- package/stl-docs/components/Head.astro +20 -0
- package/stl-docs/components/Header.astro +3 -10
- package/stl-docs/components/PageFrame.astro +34 -0
- package/stl-docs/components/PageSidebar.astro +11 -0
- package/stl-docs/components/PageTitle.astro +82 -0
- package/stl-docs/components/StainlessLogo.svg +4 -0
- package/stl-docs/components/TableOfContents.astro +34 -0
- package/stl-docs/components/ThemeProvider.astro +36 -0
- package/stl-docs/components/ThemeSelect.astro +84 -146
- package/stl-docs/components/TwoColumnContent.astro +2 -0
- package/stl-docs/components/content-panel/ContentPanel.astro +4 -64
- package/stl-docs/components/headers/DefaultHeader.astro +4 -6
- package/stl-docs/components/headers/StackedHeader.astro +8 -51
- package/stl-docs/components/icons/chat-gpt.tsx +2 -2
- package/stl-docs/components/icons/cursor.tsx +10 -0
- package/stl-docs/components/icons/gemini.tsx +19 -0
- package/stl-docs/components/icons/markdown.tsx +1 -1
- package/stl-docs/components/index.ts +1 -0
- package/stl-docs/components/mintlify-compat/Frame.astro +4 -4
- package/stl-docs/components/mintlify-compat/card.css +4 -4
- package/stl-docs/components/mintlify-compat/index.ts +2 -4
- package/stl-docs/components/nav-tabs/NavDropdown.astro +31 -75
- package/stl-docs/components/nav-tabs/NavTabs.astro +79 -81
- package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +15 -7
- package/stl-docs/components/nav-tabs/buildNavLinks.ts +3 -2
- package/stl-docs/components/pagination/HomeLink.astro +10 -0
- package/stl-docs/components/pagination/Pagination.astro +177 -0
- package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
- package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
- package/stl-docs/components/pagination/util.ts +71 -0
- package/stl-docs/components/scripts.ts +1 -0
- package/stl-docs/components/sidebars/BaseSidebar.astro +79 -2
- package/stl-docs/components/sidebars/SidebarWithComponents.tsx +10 -0
- package/stl-docs/components/sidebars/convertAstroSidebarToStl.tsx +62 -0
- package/stl-docs/disableCalloutSyntax.ts +36 -0
- package/stl-docs/fonts.ts +186 -0
- package/stl-docs/index.ts +171 -51
- package/stl-docs/loadStlDocsConfig.ts +64 -8
- package/stl-docs/proseDocSync.ts +314 -0
- package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +53 -0
- package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +41 -0
- package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
- package/stl-docs/proseSearchIndexing.ts +222 -0
- package/stl-docs/tabsMiddleware.ts +13 -4
- package/styles/code.css +53 -49
- package/styles/links.css +2 -37
- package/styles/method-descriptions.css +36 -0
- package/styles/overrides.css +28 -46
- package/styles/page.css +230 -52
- package/styles/sdk_select.css +9 -6
- package/styles/search.css +11 -21
- package/styles/sidebar.css +25 -211
- package/styles/{variables.css → sl-variables.css} +4 -8
- package/styles/stldocs-variables.css +6 -0
- package/styles/toc.css +19 -8
- package/theme.css +11 -9
- package/tsconfig.json +1 -4
- package/virtual-module.d.ts +65 -8
- package/components/variables.css +0 -112
- package/plugin/cms/client.ts +0 -62
- package/plugin/cms/server.ts +0 -268
- package/plugin/globalJs/ai-dropdown.ts +0 -57
- package/stl-docs/components/APIReferenceAIDropdown.tsx +0 -58
- package/stl-docs/components/content-panel/ProseAIDropdown.tsx +0 -55
- package/stl-docs/components/headers/SplashMobileMenuToggle.astro +0 -49
- package/stl-docs/components/mintlify-compat/Step.astro +0 -56
- package/stl-docs/components/mintlify-compat/Steps.astro +0 -15
- package/styles/fonts.css +0 -68
- /package/{plugin/assets → assets}/fonts/geist/OFL.txt +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-latin.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin.woff2 +0 -0
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { Icon } from '@astrojs/starlight/components';
|
|
3
2
|
import { DocsSearch } from './SearchIsland';
|
|
4
|
-
import {
|
|
3
|
+
import { Button } from '@stainless-api/ui-primitives';
|
|
4
|
+
import { SearchIcon, XIcon } from 'lucide-react';
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<site-search>
|
|
8
|
-
<
|
|
9
|
-
|
|
7
|
+
<site-search class={Astro.props.class}>
|
|
8
|
+
<Button
|
|
9
|
+
popoverTarget="stldocs-search"
|
|
10
10
|
data-open-modal
|
|
11
11
|
aria-label={Astro.locals.t('search.label')}
|
|
12
12
|
aria-keyshortcuts="Control+K"
|
|
13
|
-
|
|
13
|
+
variant="outline"
|
|
14
|
+
className="stl-algolia-search"
|
|
14
15
|
>
|
|
15
|
-
<
|
|
16
|
+
<SearchIcon className="icon-search" size={14} />
|
|
17
|
+
<XIcon className="icon-close" size={14} />
|
|
16
18
|
<span class="sl-hidden md:sl-block" aria-hidden="true">{Astro.locals.t('search.label')}</span>
|
|
17
19
|
<kbd class="sl-hidden md:sl-flex" style="display: none;">
|
|
18
20
|
<kbd>{Astro.locals.t('search.ctrlKey')}</kbd><kbd>K</kbd>
|
|
19
21
|
</kbd>
|
|
20
|
-
</
|
|
22
|
+
</Button>
|
|
21
23
|
|
|
22
24
|
<DocsSearch
|
|
23
25
|
client:only="react"
|
|
@@ -31,27 +33,6 @@ import { SEARCH } from 'virtual:stl-starlight-virtual-module';
|
|
|
31
33
|
/>
|
|
32
34
|
</site-search>
|
|
33
35
|
|
|
34
|
-
{
|
|
35
|
-
SEARCH?.enableAISearch === true && (
|
|
36
|
-
<button id="chat-button" popovertarget="stldocs-chat" data-open-modal>
|
|
37
|
-
<Icon name="comment" />
|
|
38
|
-
</button>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
<style>
|
|
43
|
-
#chat-button {
|
|
44
|
-
background: var(--sl-color-bg-ui);
|
|
45
|
-
border: 1px solid var(--sl-color-hairline);
|
|
46
|
-
height: 2.25rem;
|
|
47
|
-
width: 2.25rem;
|
|
48
|
-
|
|
49
|
-
&:hover {
|
|
50
|
-
border: 1px solid rgb(64, 64, 64);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
</style>
|
|
54
|
-
|
|
55
36
|
<script is:inline>
|
|
56
37
|
function setupShortcut() {
|
|
57
38
|
const openBtn = document.querySelector('button[data-open-modal]');
|
|
@@ -78,3 +59,39 @@ import { SEARCH } from 'virtual:stl-starlight-virtual-module';
|
|
|
78
59
|
}
|
|
79
60
|
});
|
|
80
61
|
</script>
|
|
62
|
+
|
|
63
|
+
<style is:inline>
|
|
64
|
+
.default-tabs-container .stl-algolia-search {
|
|
65
|
+
max-width: unset;
|
|
66
|
+
width: unset;
|
|
67
|
+
flex-grow: 1;
|
|
68
|
+
}
|
|
69
|
+
.stl-algolia-search {
|
|
70
|
+
padding: 0;
|
|
71
|
+
}
|
|
72
|
+
@media (min-width: 50rem) {
|
|
73
|
+
.stl-algolia-search {
|
|
74
|
+
padding: 8px 10px;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.stl-algolia-search {
|
|
79
|
+
.icon-search {
|
|
80
|
+
display: inline;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.icon-close {
|
|
84
|
+
display: none;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
site-search:has(#stldocs-search[data-stldocs-modal-open='true']) .stl-algolia-search {
|
|
89
|
+
.icon-search {
|
|
90
|
+
display: none;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.icon-close {
|
|
94
|
+
display: inline;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
</style>
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { parseRoute, generateRoute } from '@stainless-api/docs-ui/
|
|
4
|
-
import { SearchModal } from '@stainless-api/docs-
|
|
5
|
-
import { ChatModal } from '@stainless-api/docs-ui/src/components/chat';
|
|
2
|
+
import { RESOLVED_API_REFERENCE_PATH, HIGHLIGHT_THEMES } from 'virtual:stl-starlight-virtual-module';
|
|
3
|
+
import { parseRoute, generateRoute } from '@stainless-api/docs-ui/routing';
|
|
4
|
+
import { SearchModal } from '@stainless-api/docs-search';
|
|
6
5
|
import * as Markdoc from '@markdoc/markdoc';
|
|
7
6
|
import { createHighlighter } from 'shiki';
|
|
8
7
|
import type { BundledLanguage, BundledTheme, HighlighterGeneric } from 'shiki';
|
|
9
8
|
|
|
10
9
|
import {
|
|
11
10
|
DocsProvider,
|
|
11
|
+
type MarkdownContext,
|
|
12
12
|
MarkdownProvider,
|
|
13
13
|
NavigationProvider,
|
|
14
|
-
|
|
15
|
-
} from '@stainless-api/docs-ui/
|
|
16
|
-
import
|
|
14
|
+
} from '@stainless-api/docs-ui/contexts';
|
|
15
|
+
import { ComponentProvider } from '@stainless-api/docs-ui/contexts/component';
|
|
16
|
+
import { SearchProvider } from '@stainless-api/docs-search/context';
|
|
17
|
+
import type { SearchSettings } from '@stainless-api/docs-search/types';
|
|
17
18
|
|
|
18
19
|
let $$highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | null = null;
|
|
19
20
|
async function getHighlighter() {
|
|
@@ -27,7 +28,7 @@ async function getHighlighter() {
|
|
|
27
28
|
return $$highlighter;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
async function createMarkdownRenderer() {
|
|
31
|
+
async function createMarkdownRenderer(): Promise<MarkdownContext> {
|
|
31
32
|
const highlighter = await getHighlighter();
|
|
32
33
|
const markdocConfig: Markdoc.Config = {
|
|
33
34
|
nodes: {
|
|
@@ -37,7 +38,10 @@ async function createMarkdownRenderer() {
|
|
|
37
38
|
transform(node, config) {
|
|
38
39
|
const attributes = node.transformAttributes(config);
|
|
39
40
|
const lang = node.attributes.language === 'node' ? 'typescript' : node.attributes.language;
|
|
40
|
-
const code = highlighter.codeToTokens(node.attributes.content, {
|
|
41
|
+
const code = highlighter.codeToTokens(node.attributes.content, {
|
|
42
|
+
lang,
|
|
43
|
+
theme: 'github-dark',
|
|
44
|
+
});
|
|
41
45
|
|
|
42
46
|
return new Markdoc.Tag(
|
|
43
47
|
'pre',
|
|
@@ -59,42 +63,56 @@ async function createMarkdownRenderer() {
|
|
|
59
63
|
transform(node, config) {
|
|
60
64
|
const children = node.transformChildren(config);
|
|
61
65
|
const attrs = node.transformAttributes(config);
|
|
62
|
-
const href = attrs['href'].replace('docs://BASE_PATH',
|
|
66
|
+
const href = attrs['href'].replace('docs://BASE_PATH', RESOLVED_API_REFERENCE_PATH);
|
|
63
67
|
return new Markdoc.Tag(this.render, { ...attrs, href }, children);
|
|
64
68
|
},
|
|
65
69
|
},
|
|
66
70
|
},
|
|
67
71
|
};
|
|
68
72
|
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
return {
|
|
74
|
+
render: (content: string) => {
|
|
75
|
+
const ast = Markdoc.parse(content);
|
|
76
|
+
const transformed = Markdoc.transform(ast, markdocConfig);
|
|
77
|
+
return Markdoc.renderers.html(transformed);
|
|
78
|
+
},
|
|
79
|
+
highlight: (content: string, language: string) => {
|
|
80
|
+
return highlighter.codeToHtml(content, {
|
|
81
|
+
lang: language ?? 'javascript',
|
|
82
|
+
themes: HIGHLIGHT_THEMES || {},
|
|
83
|
+
});
|
|
84
|
+
},
|
|
73
85
|
};
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
export function DocsSearch({ settings, currentPath }: { settings: SearchSettings; currentPath: string }) {
|
|
77
|
-
const
|
|
78
|
-
const { stainlessPath, language } = parseRoute(
|
|
79
|
-
|
|
89
|
+
const markdownRenderer = React.use(createMarkdownRenderer());
|
|
90
|
+
const { stainlessPath, language } = parseRoute(RESOLVED_API_REFERENCE_PATH, currentPath);
|
|
91
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
92
|
+
const pageFind = import.meta.env.DEV
|
|
93
|
+
? undefined
|
|
94
|
+
: `${import.meta.env.BASE_URL}/pagefind/pagefind.js`.replace(/\/+/g, '/');
|
|
80
95
|
|
|
81
|
-
function handleSelect(
|
|
82
|
-
const url =
|
|
96
|
+
function handleSelect(selectedPath: string) {
|
|
97
|
+
const url = selectedPath.startsWith('/')
|
|
98
|
+
? selectedPath
|
|
99
|
+
: generateRoute(RESOLVED_API_REFERENCE_PATH, language, selectedPath);
|
|
83
100
|
if (url) window.location.href = url;
|
|
84
101
|
}
|
|
85
102
|
|
|
86
103
|
return (
|
|
87
104
|
<DocsProvider spec={null} language={language}>
|
|
88
|
-
<
|
|
89
|
-
<
|
|
90
|
-
<
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
<ComponentProvider>
|
|
106
|
+
<NavigationProvider basePath="/" selectedPath={stainlessPath}>
|
|
107
|
+
<MarkdownProvider {...markdownRenderer}>
|
|
108
|
+
<SearchProvider onSelect={handleSelect} pageFind={pageFind} settings={settings}>
|
|
109
|
+
<div className="stldocs-root">
|
|
110
|
+
<SearchModal id="stldocs-search" />
|
|
111
|
+
</div>
|
|
112
|
+
</SearchProvider>
|
|
113
|
+
</MarkdownProvider>
|
|
114
|
+
</NavigationProvider>
|
|
115
|
+
</ComponentProvider>
|
|
98
116
|
</DocsProvider>
|
|
99
117
|
);
|
|
100
118
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// This is probably temporary, but it fills in functionality needed for Mintlify imports
|
|
2
2
|
|
|
3
3
|
import type { StarlightRouteData } from '@astrojs/starlight/route-data';
|
|
4
|
-
import type * as SDKJSON from '
|
|
5
|
-
import { walkTree } from '@stainless-api/docs-ui/
|
|
4
|
+
import type * as SDKJSON from '@stainless/sdk-json';
|
|
5
|
+
import { walkTree } from '@stainless-api/docs-ui/routing';
|
|
6
6
|
|
|
7
7
|
const INTERNAL_REFERENCE_ENTRY_MARKER = 'STL_STARLIGHT_API_REFERENCE_METHOD_LINK_PLACEHOLDER';
|
|
8
8
|
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { initDropdownButton } from '@stainless-api/ui-primitives/scripts';
|
|
2
|
+
import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
|
|
3
|
+
|
|
4
|
+
export type DropdownIcon = 'markdown' | 'copy' | 'claude' | 'chatgpt' | 'gemini' | 'cursor';
|
|
5
|
+
|
|
6
|
+
interface DropdownOptionInputProps {
|
|
7
|
+
onClick: () => void;
|
|
8
|
+
icon: DropdownIcon;
|
|
9
|
+
primaryAction?: boolean;
|
|
10
|
+
clientHidden?: () => boolean;
|
|
11
|
+
external: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function option(label: string[] | string, props: DropdownOptionInputProps) {
|
|
15
|
+
const labelArr = typeof label === 'string' ? [label] : label;
|
|
16
|
+
return {
|
|
17
|
+
...props,
|
|
18
|
+
label: labelArr,
|
|
19
|
+
primaryAction: props.primaryAction ?? false,
|
|
20
|
+
clientHidden: props.clientHidden ?? (() => false),
|
|
21
|
+
id: labelArr.join('').toLowerCase().replace(/ /g, '-'),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type DropdownOption = ReturnType<typeof option>;
|
|
26
|
+
|
|
27
|
+
function getMarkdownUrl(type: 'relative' | 'absolute') {
|
|
28
|
+
const currentUrl = new URL(window.location.href);
|
|
29
|
+
const hasTrailingSlash = currentUrl.pathname.endsWith('/');
|
|
30
|
+
|
|
31
|
+
const markdownUrl = [
|
|
32
|
+
type === 'absolute' ? currentUrl.origin : '',
|
|
33
|
+
currentUrl.pathname,
|
|
34
|
+
hasTrailingSlash ? '' : '/',
|
|
35
|
+
'index.md',
|
|
36
|
+
].join('');
|
|
37
|
+
|
|
38
|
+
return markdownUrl;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getURLEncodedPrompt() {
|
|
42
|
+
const mdUrl = getMarkdownUrl('absolute');
|
|
43
|
+
const aiPrompt = encodeURIComponent(
|
|
44
|
+
`Load the contents of ${mdUrl} into this chat's context so we can discuss it.`,
|
|
45
|
+
);
|
|
46
|
+
return aiPrompt;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function openDeepLink({ deepLinkUrl, fallbackUrl }: { deepLinkUrl: string; fallbackUrl: string }) {
|
|
50
|
+
if (navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrom')) {
|
|
51
|
+
// safari doesn't let us detect if the deep link worked
|
|
52
|
+
window.open(fallbackUrl, '_blank');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
let frame: HTMLIFrameElement | null;
|
|
58
|
+
|
|
59
|
+
// We are using a native deep link with a fallback web url.
|
|
60
|
+
if (navigator.userAgent.includes('Chrom')) {
|
|
61
|
+
// In Chrome, load it in a hidden frame, this shows the "Do you want to open ...?" prompt, but unlike
|
|
62
|
+
// top level navigation this preserves our userActivation, so we can open the fallback if it fails.
|
|
63
|
+
frame = Object.assign(document.createElement('iframe'), { src: deepLinkUrl });
|
|
64
|
+
document.head.append(frame);
|
|
65
|
+
} else {
|
|
66
|
+
// In Firefox do the opposite.
|
|
67
|
+
location.href = deepLinkUrl;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The popup (in non-Safari browsers) fires a `blur` event.
|
|
71
|
+
window.addEventListener(
|
|
72
|
+
'blur',
|
|
73
|
+
() => {
|
|
74
|
+
controller.abort();
|
|
75
|
+
},
|
|
76
|
+
controller,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// If it's been 300ms with no popup, open the fallback web url.
|
|
80
|
+
const timeout = setTimeout(() => {
|
|
81
|
+
window.open(fallbackUrl, '_blank');
|
|
82
|
+
}, 300);
|
|
83
|
+
|
|
84
|
+
controller.signal.addEventListener('abort', () => {
|
|
85
|
+
clearTimeout(timeout);
|
|
86
|
+
frame?.remove();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Browser_detection_using_the_user_agent#mobile_tablet_or_desktop
|
|
91
|
+
const hasMobileUserAgent = () => navigator.userAgent.includes('Mobi');
|
|
92
|
+
|
|
93
|
+
// 2d array of dropdown options
|
|
94
|
+
// each sub-array is a group, separated by a horizontal rule in the UI
|
|
95
|
+
const aiDropdownOptions: DropdownOption[][] = [
|
|
96
|
+
[
|
|
97
|
+
option(['Open in ', 'Claude'], {
|
|
98
|
+
onClick: () => {
|
|
99
|
+
window.open(`https://claude.ai/new?q=${getURLEncodedPrompt()}`, '_blank');
|
|
100
|
+
},
|
|
101
|
+
icon: 'claude',
|
|
102
|
+
primaryAction: false,
|
|
103
|
+
external: true,
|
|
104
|
+
}),
|
|
105
|
+
option(['Open in ', 'ChatGPT'], {
|
|
106
|
+
onClick: () => {
|
|
107
|
+
window.open(`https://chatgpt.com/?hints=search&prompt=${getURLEncodedPrompt()}`, '_blank');
|
|
108
|
+
},
|
|
109
|
+
icon: 'chatgpt',
|
|
110
|
+
primaryAction: false,
|
|
111
|
+
external: true,
|
|
112
|
+
}),
|
|
113
|
+
option(['Open in ', 'Cursor'], {
|
|
114
|
+
onClick: () => {
|
|
115
|
+
const aiPrompt = getURLEncodedPrompt();
|
|
116
|
+
openDeepLink({
|
|
117
|
+
deepLinkUrl: `cursor://anysphere.cursor-deeplink/prompt?text=${aiPrompt}`,
|
|
118
|
+
fallbackUrl: `https://cursor.com/link/prompt?text=${aiPrompt}`,
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
clientHidden: hasMobileUserAgent,
|
|
122
|
+
icon: 'cursor',
|
|
123
|
+
primaryAction: false,
|
|
124
|
+
external: true,
|
|
125
|
+
}),
|
|
126
|
+
],
|
|
127
|
+
[
|
|
128
|
+
option('Copy Markdown', {
|
|
129
|
+
onClick: () => {
|
|
130
|
+
// Source: https://wolfgangrittner.dev/how-to-use-clipboard-api-in-firefox/
|
|
131
|
+
const markdownUrl = getMarkdownUrl('relative');
|
|
132
|
+
// ClipboardItem doesn't exist in every browser
|
|
133
|
+
// eslint-disable-next-line no-constant-binary-expression
|
|
134
|
+
if (typeof ClipboardItem && navigator.clipboard.write) {
|
|
135
|
+
// NOTE: Safari locks down the clipboard API to only work when triggered
|
|
136
|
+
// by a direct user interaction. You can't use it async in a promise.
|
|
137
|
+
// But! You can wrap the promise in a ClipboardItem, and give that to
|
|
138
|
+
// the clipboard API.
|
|
139
|
+
// Found this on https://developer.apple.com/forums/thread/691873
|
|
140
|
+
const text = new ClipboardItem({
|
|
141
|
+
'text/plain': fetch(markdownUrl)
|
|
142
|
+
.then((response) => response.text())
|
|
143
|
+
.then((text) => new Blob([text], { type: 'text/plain' })),
|
|
144
|
+
});
|
|
145
|
+
navigator.clipboard.write([text]);
|
|
146
|
+
} else {
|
|
147
|
+
// NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write,
|
|
148
|
+
// but those are behind `dom.events.asyncClipboard.clipboardItem` preference.
|
|
149
|
+
// Good news is that other than Safari, Firefox does not care about
|
|
150
|
+
// Clipboard API being used async in a Promise.
|
|
151
|
+
fetch(markdownUrl)
|
|
152
|
+
.then((response) => response.text())
|
|
153
|
+
.then((text) => navigator.clipboard.writeText(text));
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
icon: 'copy',
|
|
157
|
+
primaryAction: true,
|
|
158
|
+
external: false,
|
|
159
|
+
}),
|
|
160
|
+
option('View as Markdown', {
|
|
161
|
+
onClick: () => {
|
|
162
|
+
window.open(getMarkdownUrl('absolute'), '_blank');
|
|
163
|
+
},
|
|
164
|
+
icon: 'markdown',
|
|
165
|
+
primaryAction: false,
|
|
166
|
+
external: true,
|
|
167
|
+
}),
|
|
168
|
+
],
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
// TODO: Add support for more LLMs
|
|
172
|
+
// {
|
|
173
|
+
// label: ['Open in ', 'Gemini'],
|
|
174
|
+
// onClick: () => {
|
|
175
|
+
// openInLLM('https://gemini.google.com?prompt_action=prefill&prompt_text=');
|
|
176
|
+
// },
|
|
177
|
+
// icon: 'gemini',
|
|
178
|
+
// primaryAction: false,
|
|
179
|
+
// },
|
|
180
|
+
|
|
181
|
+
export function getAIDropdownOptions() {
|
|
182
|
+
const renderedOptions = aiDropdownOptions.map((group, index) => {
|
|
183
|
+
return {
|
|
184
|
+
options: group,
|
|
185
|
+
isLast: index === aiDropdownOptions.length - 1,
|
|
186
|
+
reactKey: index,
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const allOptions = renderedOptions.flatMap((group) => group.options);
|
|
191
|
+
const primaryAction = allOptions.find((o) => o.primaryAction) ?? allOptions[0]!;
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
primaryAction,
|
|
195
|
+
groups: renderedOptions,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function wireAIDropdown() {
|
|
200
|
+
const { primaryAction, groups } = getAIDropdownOptions();
|
|
201
|
+
const flatOptions = groups.flatMap((group) => group.options);
|
|
202
|
+
function triggerOption(id: string) {
|
|
203
|
+
const option = flatOptions.find((option) => option.id === id);
|
|
204
|
+
if (!option) return;
|
|
205
|
+
option.onClick();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
document.addEventListener(getPageLoadEvent(), () => {
|
|
209
|
+
// we hide the Cursor option on non-desktop devices
|
|
210
|
+
for (const option of flatOptions) {
|
|
211
|
+
if (option.clientHidden() === true) {
|
|
212
|
+
const el = document.querySelector(
|
|
213
|
+
`[data-dropdown-id="ai-dropdown-button"] [data-value="${option.id}"]`,
|
|
214
|
+
);
|
|
215
|
+
if (el) {
|
|
216
|
+
el.remove();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const dropdowns = document.querySelectorAll('[data-dropdown-id="ai-dropdown-button"]');
|
|
222
|
+
|
|
223
|
+
dropdowns.forEach((dropdown) => {
|
|
224
|
+
initDropdownButton({
|
|
225
|
+
dropdown: dropdown,
|
|
226
|
+
onSelect: (value) => {
|
|
227
|
+
triggerOption(value);
|
|
228
|
+
},
|
|
229
|
+
onPrimaryAction: (el) => {
|
|
230
|
+
triggerOption(primaryAction.id);
|
|
231
|
+
const span = el.querySelector('[data-part="primary-action-text"]');
|
|
232
|
+
if (span) {
|
|
233
|
+
const originalContent = span.textContent;
|
|
234
|
+
span.textContent = 'Copied!';
|
|
235
|
+
setTimeout(() => {
|
|
236
|
+
span.textContent = originalContent;
|
|
237
|
+
}, 2000);
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
@@ -19,9 +19,10 @@ document.addEventListener(getPageLoadEvent(), () => {
|
|
|
19
19
|
document
|
|
20
20
|
.querySelectorAll(`[data-snippet-response-pane-id="${panelId}"]`)
|
|
21
21
|
.forEach((p) => p.classList.add('stldocs-snippet-response-pane-active'));
|
|
22
|
-
document
|
|
23
|
-
.
|
|
24
|
-
.
|
|
22
|
+
document.querySelectorAll(`[data-snippet-response-tab-id="${panelId}"]`).forEach((p) => {
|
|
23
|
+
p.classList.add('stldocs-snippet-response-tab-item-active');
|
|
24
|
+
p.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
|
|
25
|
+
});
|
|
25
26
|
});
|
|
26
27
|
});
|
|
27
28
|
|
|
@@ -43,24 +44,31 @@ document.addEventListener(getPageLoadEvent(), () => {
|
|
|
43
44
|
// This is a bit funky, but it animates pretty smooth.
|
|
44
45
|
// 1. Toggle to new state so we can measure the target height
|
|
45
46
|
// 2. Reset to starting height to trigger transition
|
|
46
|
-
|
|
47
|
+
el.style.height = '';
|
|
48
|
+
el.style.overflowY = '';
|
|
49
|
+
const startHeight = el.getBoundingClientRect().height;
|
|
47
50
|
el.classList.toggle('stl-snippet-code-is-expanded', expand);
|
|
48
51
|
el.classList.toggle('stl-snippet-code-is-collapsed', !expand);
|
|
49
52
|
|
|
50
|
-
const endHeight = el.
|
|
51
|
-
|
|
52
|
-
el.style.height = `${startHeight}px`;
|
|
53
|
-
|
|
54
|
-
requestAnimationFrame(() => {
|
|
55
|
-
el.style.height = `${endHeight}px`;
|
|
56
|
-
});
|
|
53
|
+
const endHeight = el.getBoundingClientRect().height;
|
|
57
54
|
|
|
58
55
|
const onEnd = (e: TransitionEvent) => {
|
|
59
56
|
if (e.propertyName !== 'height') return;
|
|
60
57
|
el.style.height = '';
|
|
58
|
+
el.style.overflowY = '';
|
|
61
59
|
el.removeEventListener('transitionend', onEnd);
|
|
62
60
|
};
|
|
63
61
|
el.addEventListener('transitionend', onEnd);
|
|
62
|
+
|
|
63
|
+
// Set initial height
|
|
64
|
+
el.style.height = `${startHeight}px`;
|
|
65
|
+
el.style.overflowY = 'hidden';
|
|
66
|
+
|
|
67
|
+
// Force layout recalculation
|
|
68
|
+
el.getBoundingClientRect();
|
|
69
|
+
|
|
70
|
+
// Start transition to end state
|
|
71
|
+
el.style.height = `${endHeight}px`;
|
|
64
72
|
};
|
|
65
73
|
|
|
66
74
|
if (collapsedDiv) {
|
|
@@ -84,4 +92,25 @@ document.addEventListener(getPageLoadEvent(), () => {
|
|
|
84
92
|
btn.textContent = 'Show more';
|
|
85
93
|
}
|
|
86
94
|
});
|
|
95
|
+
|
|
96
|
+
document.querySelectorAll('[data-stldocs-multi-snippet-container]').forEach((container) => {
|
|
97
|
+
const radios = container.querySelectorAll<HTMLInputElement>('input[type="radio"]');
|
|
98
|
+
const panes = container.querySelectorAll<HTMLDivElement>('[data-stldocs-multi-snippet-id]');
|
|
99
|
+
|
|
100
|
+
radios.forEach((radio) => {
|
|
101
|
+
radio.addEventListener('change', (e) => {
|
|
102
|
+
if (!(e.target instanceof HTMLInputElement)) return;
|
|
103
|
+
if (e.target.checked) {
|
|
104
|
+
const selectedIndex = e.target.value;
|
|
105
|
+
panes.forEach((pane) => {
|
|
106
|
+
pane.classList.toggle(
|
|
107
|
+
'stldocs-snippet-multi-pane-active',
|
|
108
|
+
pane.dataset.stldocsMultiSnippetId === selectedIndex,
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
e.target.parentElement?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
87
116
|
});
|