@umami/shiso 0.54.0 → 0.61.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/CHANGELOG.md +12 -0
- package/README.md +9 -49
- package/bin/shiso.mjs +132 -0
- package/docs.schema.json +835 -0
- package/mdx.config.ts +143 -0
- package/package.json +73 -83
- package/scripts/check-package.mjs +18 -0
- package/scripts/generate-icon-registry.mjs +196 -0
- package/scripts/generate-last-modified.mjs +128 -0
- package/scripts/generate-search-index.mjs +252 -0
- package/scripts/load-docs-config.mjs +244 -0
- package/scripts/prerender.mjs +187 -0
- package/scripts/validate-config.mjs +104 -0
- package/scripts/vite-docs-config.mjs +60 -0
- package/src/App.tsx +39 -0
- package/src/components/Banner.tsx +69 -0
- package/src/components/CodeBlock.tsx +46 -0
- package/src/components/ConfiguredIcon.tsx +15 -0
- package/src/components/ContextualMenu.tsx +93 -0
- package/src/components/DocContent.tsx +100 -0
- package/src/components/Docs.tsx +126 -0
- package/src/components/Footer.tsx +84 -0
- package/src/components/Header.tsx +72 -0
- package/src/components/Layout.tsx +16 -0
- package/src/components/PageLinks.tsx +71 -0
- package/src/components/Search.tsx +208 -0
- package/src/components/SideNav.tsx +347 -0
- package/src/components/SocialIcon.tsx +88 -0
- package/src/components/ThemeToggle.tsx +34 -0
- package/src/components/TopNav.tsx +127 -0
- package/src/components/docs/Accordion.tsx +68 -0
- package/src/components/docs/Badge.tsx +171 -0
- package/src/components/docs/Callout.tsx +73 -0
- package/src/components/docs/Card.tsx +158 -0
- package/src/components/docs/CodeGroup.tsx +73 -0
- package/src/components/docs/Columns.tsx +20 -0
- package/src/components/docs/Expandable.tsx +28 -0
- package/src/components/docs/Frame.tsx +56 -0
- package/src/components/docs/Icon.tsx +30 -0
- package/src/components/docs/ParamField.tsx +45 -0
- package/src/components/docs/PropertiesTable.tsx +84 -0
- package/src/components/docs/ResponseField.tsx +36 -0
- package/src/components/docs/Steps.tsx +47 -0
- package/src/components/docs/Tabs.tsx +116 -0
- package/src/components/docs/Tooltip.tsx +21 -0
- package/src/components/docs/index.ts +15 -0
- package/src/components/docs/styles.ts +82 -0
- package/src/components/docs/utils.ts +118 -0
- package/src/components/icons/index.ts +17 -0
- package/src/components/ui/accordion.tsx +69 -0
- package/src/components/ui/alert.tsx +69 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/button.tsx +58 -0
- package/src/components/ui/card.tsx +88 -0
- package/src/components/ui/collapsible.tsx +15 -0
- package/src/components/ui/command.tsx +173 -0
- package/src/components/ui/dialog.tsx +137 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/scroll-area.tsx +71 -0
- package/src/components/ui/sheet.tsx +124 -0
- package/src/components/ui/tabs.tsx +73 -0
- package/src/components/ui/tooltip.tsx +52 -0
- package/src/declarations.d.ts +9 -0
- package/src/entry-client.tsx +17 -0
- package/src/entry-server.tsx +77 -0
- package/src/generated/last-modified.ts +2 -0
- package/src/lib/content.ts +44 -0
- package/src/lib/docs-config.ts +682 -0
- package/src/lib/head.ts +220 -0
- package/src/lib/icon-registry.generated.ts +4 -0
- package/src/lib/icons.ts +29 -0
- package/src/lib/inline-markdown.tsx +86 -0
- package/src/lib/mdast.ts +56 -0
- package/src/lib/paths.ts +86 -0
- package/src/lib/remark-toc.ts +71 -0
- package/src/lib/search/config.ts +43 -0
- package/src/lib/search/provider.ts +80 -0
- package/src/lib/search/providers/local.ts +15 -0
- package/src/lib/search-index.generated.ts +4 -0
- package/src/lib/search.ts +100 -0
- package/src/lib/site-config.ts +104 -0
- package/src/lib/site-model.ts +221 -0
- package/src/lib/slug.ts +38 -0
- package/src/lib/types.ts +478 -0
- package/src/lib/utils.ts +6 -0
- package/src/pages/DocPage.tsx +33 -0
- package/src/styles/global.css +268 -0
- package/src/styles/tokens.css +114 -0
- package/types/client.d.ts +3 -0
- package/types/search.d.ts +17 -0
- package/vite.config.ts +342 -0
- package/LICENSE +0 -21
- package/dist/index.css +0 -189
- package/dist/index.d.ts +0 -57
- package/dist/index.js +0 -464
- package/dist/index.mjs +0 -437
- package/server/index.d.ts +0 -30
- package/server/index.js +0 -189
- package/styles.css +0 -4766
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side search over the build-time index
|
|
3
|
+
* (see scripts/generate-search-index.mjs).
|
|
4
|
+
*
|
|
5
|
+
* Plain substring scoring, no dependencies: docs sites are small enough that
|
|
6
|
+
* scanning a few hundred sections per keystroke is cheap, and substring
|
|
7
|
+
* matches behave predictably for exact terms like config keys.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface SearchRecord {
|
|
11
|
+
/** Base-relative route of the page. */
|
|
12
|
+
url: string;
|
|
13
|
+
/** Page title. */
|
|
14
|
+
page: string;
|
|
15
|
+
/** Section heading, absent for the page intro. */
|
|
16
|
+
heading?: string;
|
|
17
|
+
/** Heading anchor id, matching the rendered heading. */
|
|
18
|
+
id?: string;
|
|
19
|
+
/** Plain text of the section. */
|
|
20
|
+
text: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SearchResult {
|
|
24
|
+
/** Route including the section anchor. */
|
|
25
|
+
url: string;
|
|
26
|
+
/** Page title. */
|
|
27
|
+
page: string;
|
|
28
|
+
/** Section heading, absent for the page intro. */
|
|
29
|
+
heading?: string;
|
|
30
|
+
/** Snippet of section text around the first match, when the text matched. */
|
|
31
|
+
snippet?: string;
|
|
32
|
+
/** Provider-specific relevance score. Use 0 when a provider does not expose one. */
|
|
33
|
+
score: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SNIPPET_RADIUS = 60;
|
|
37
|
+
|
|
38
|
+
function makeSnippet(text: string, index: number, length: number): string {
|
|
39
|
+
const start = Math.max(0, index - SNIPPET_RADIUS);
|
|
40
|
+
const end = Math.min(text.length, index + length + SNIPPET_RADIUS);
|
|
41
|
+
|
|
42
|
+
return `${start > 0 ? '…' : ''}${text.slice(start, end).trim()}${end < text.length ? '…' : ''}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function searchIndex(records: SearchRecord[], query: string, limit = 10): SearchResult[] {
|
|
46
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
47
|
+
|
|
48
|
+
if (!terms.length) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const results: SearchResult[] = [];
|
|
53
|
+
|
|
54
|
+
for (const record of records) {
|
|
55
|
+
const page = record.page.toLowerCase();
|
|
56
|
+
const heading = (record.heading || '').toLowerCase();
|
|
57
|
+
const text = record.text.toLowerCase();
|
|
58
|
+
|
|
59
|
+
let score = 0;
|
|
60
|
+
let snippetAt = -1;
|
|
61
|
+
let snippetLength = 0;
|
|
62
|
+
let matched = true;
|
|
63
|
+
|
|
64
|
+
for (const term of terms) {
|
|
65
|
+
if (page.includes(term)) {
|
|
66
|
+
score += page === term ? 40 : 20;
|
|
67
|
+
} else if (heading.includes(term)) {
|
|
68
|
+
score += heading === term ? 30 : 15;
|
|
69
|
+
} else {
|
|
70
|
+
const index = text.indexOf(term);
|
|
71
|
+
|
|
72
|
+
if (index === -1) {
|
|
73
|
+
matched = false;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
score += 5;
|
|
78
|
+
|
|
79
|
+
if (snippetAt === -1) {
|
|
80
|
+
snippetAt = index;
|
|
81
|
+
snippetLength = term.length;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!matched || !score) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
results.push({
|
|
91
|
+
url: record.id ? `${record.url}#${record.id}` : record.url,
|
|
92
|
+
page: record.page,
|
|
93
|
+
heading: record.heading,
|
|
94
|
+
snippet: snippetAt >= 0 ? makeSnippet(record.text, snippetAt, snippetLength) : undefined,
|
|
95
|
+
score,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
100
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import rawConfig from 'virtual:shiso-docs-config';
|
|
2
|
+
import { resolveDocFile } from '@/lib/content';
|
|
3
|
+
import { assertDocsConfig, normalizeDocsConfig } from '@/lib/docs-config';
|
|
4
|
+
import { stripBase, stripDocsPrefix } from '@/lib/paths';
|
|
5
|
+
import { resolveSiteModel } from '@/lib/site-model';
|
|
6
|
+
import type {
|
|
7
|
+
DocsConfig,
|
|
8
|
+
NormalizedDocsConfig,
|
|
9
|
+
NormalizedDocsPage,
|
|
10
|
+
RedirectRule,
|
|
11
|
+
SeoConfig,
|
|
12
|
+
} from '@/lib/types';
|
|
13
|
+
|
|
14
|
+
assertDocsConfig(rawConfig, 'docs.json');
|
|
15
|
+
|
|
16
|
+
export const siteConfig: DocsConfig = rawConfig;
|
|
17
|
+
|
|
18
|
+
export const docsConfig: NormalizedDocsConfig = normalizeDocsConfig(siteConfig, resolveDocFile);
|
|
19
|
+
|
|
20
|
+
export const siteModel = resolveSiteModel(siteConfig, docsConfig);
|
|
21
|
+
|
|
22
|
+
export const siteName = siteModel.name;
|
|
23
|
+
|
|
24
|
+
/** Trailing-slash-insensitive route key for redirect matching. */
|
|
25
|
+
function toRouteKey(routePath: string): string {
|
|
26
|
+
const trimmed = routePath.replace(/\/+$/, '');
|
|
27
|
+
return trimmed || '/';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Redirect rules with exact-match sources. Wildcard patterns are part of the
|
|
32
|
+
* standard but not implemented; they are skipped with a warning.
|
|
33
|
+
*/
|
|
34
|
+
export function getRedirects(): RedirectRule[] {
|
|
35
|
+
return (siteConfig.redirects || []).filter(rule => {
|
|
36
|
+
if (!rule?.source || !rule.destination) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (/[:*]/.test(rule.source)) {
|
|
41
|
+
console.warn(
|
|
42
|
+
`[shiso] Redirect source "${rule.source}" uses a wildcard pattern, which is not ` +
|
|
43
|
+
'implemented yet — it will be skipped.',
|
|
44
|
+
);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const redirectBySource = new Map(
|
|
53
|
+
getRedirects().map(rule => [toRouteKey(rule.source), rule.destination]),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
/** Destination for a base-relative route covered by a redirect rule, if any. */
|
|
57
|
+
export function matchRedirect(routePath: string): string | null {
|
|
58
|
+
return redirectBySource.get(toRouteKey(routePath)) || null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getSeo(): SeoConfig {
|
|
62
|
+
const seo = siteConfig.seo || {};
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
metatags: seo.metatags || {},
|
|
66
|
+
indexing: seo.indexing === 'all' ? 'all' : 'navigable',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** True when the last-modified timestamp should show for a page. */
|
|
71
|
+
export function showTimestamp(frontmatterValue: unknown): boolean {
|
|
72
|
+
if (typeof frontmatterValue === 'boolean') {
|
|
73
|
+
return frontmatterValue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return siteModel.showTimestamp;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function normalizeParamSlug(slug: string): string {
|
|
80
|
+
const cleaned = slug.replace(/^\/+|\/+$/g, '');
|
|
81
|
+
|
|
82
|
+
if (!cleaned) {
|
|
83
|
+
return 'index';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (cleaned === 'index') {
|
|
87
|
+
return cleaned;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return cleaned.replace(/\/index$/, '') || 'index';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getPageByPathname(pathname: string): NormalizedDocsPage | null {
|
|
94
|
+
const slug = normalizeParamSlug(stripDocsPrefix(stripBase(pathname)));
|
|
95
|
+
return docsConfig.pageByLookupSlug[slug] || null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function getPageTitle(pageTitle?: string): string {
|
|
99
|
+
if (pageTitle && siteName) {
|
|
100
|
+
return `${pageTitle} – ${siteName}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return pageTitle || siteName || '';
|
|
104
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { toAbsoluteUrl, toHref } from '@/lib/paths';
|
|
2
|
+
import { resolveSearchConfig } from '@/lib/search/config';
|
|
3
|
+
import type {
|
|
4
|
+
ConfigLink,
|
|
5
|
+
ContextualOption,
|
|
6
|
+
ContextualOptionObject,
|
|
7
|
+
DocsConfig,
|
|
8
|
+
LinkTarget,
|
|
9
|
+
NormalizedDocsConfig,
|
|
10
|
+
NormalizedDocsPage,
|
|
11
|
+
NormalizedFooter,
|
|
12
|
+
NormalizedLink,
|
|
13
|
+
NormalizedNavbar,
|
|
14
|
+
ResolvedContextualOption,
|
|
15
|
+
SiteModel,
|
|
16
|
+
ThemeLabels,
|
|
17
|
+
} from '@/lib/types';
|
|
18
|
+
|
|
19
|
+
const SHISO_THEME_LABELS: ThemeLabels = {
|
|
20
|
+
menu: 'Menu',
|
|
21
|
+
documentationNavigation: 'Documentation navigation',
|
|
22
|
+
sections: 'Sections',
|
|
23
|
+
tableOfContents: 'On this page',
|
|
24
|
+
tableOfContentsNavigation: 'Table of contents',
|
|
25
|
+
searchTitle: 'Search',
|
|
26
|
+
searching: 'Searching...',
|
|
27
|
+
searchUnavailable: 'Search unavailable',
|
|
28
|
+
noResults: 'No results',
|
|
29
|
+
lastUpdated: 'Last updated on',
|
|
30
|
+
notFound: 'Page not found',
|
|
31
|
+
dismissBanner: 'Dismiss banner',
|
|
32
|
+
toggleTheme: 'Toggle theme',
|
|
33
|
+
moreOptions: 'More options',
|
|
34
|
+
copied: 'Copied',
|
|
35
|
+
expand: 'Expand',
|
|
36
|
+
collapse: 'Collapse',
|
|
37
|
+
copyPage: 'Copy page',
|
|
38
|
+
copyPageDescription: 'Copy this page as Markdown',
|
|
39
|
+
viewMarkdown: 'View as Markdown',
|
|
40
|
+
viewMarkdownDescription: 'Open this page as plain Markdown',
|
|
41
|
+
openInChatGPT: 'Open in ChatGPT',
|
|
42
|
+
openInClaude: 'Open in Claude',
|
|
43
|
+
openInPerplexity: 'Open in Perplexity',
|
|
44
|
+
askQuestionsAboutPage: 'Ask questions about this page',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function isInternalHref(href: string): boolean {
|
|
48
|
+
return /^(?:#|\/|\.\.?\/)/.test(href);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function resolveLinkTarget(href: string, target?: LinkTarget): LinkTarget {
|
|
52
|
+
return target || (isInternalHref(href) ? '_self' : '_blank');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeLink(link: ConfigLink): NormalizedLink {
|
|
56
|
+
return {
|
|
57
|
+
href: link.href,
|
|
58
|
+
label: link.label?.trim() || undefined,
|
|
59
|
+
ariaLabel: link.ariaLabel?.trim() || undefined,
|
|
60
|
+
icon: link.icon?.trim() || undefined,
|
|
61
|
+
target: resolveLinkTarget(link.href, link.target),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeNavbar(config: DocsConfig['navbar']): NormalizedNavbar | null {
|
|
66
|
+
if (!config) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const links = (config.links || []).filter(link => !!link?.href).map(normalizeLink);
|
|
71
|
+
const primary = config.primary?.href ? normalizeLink(config.primary) : undefined;
|
|
72
|
+
|
|
73
|
+
return links.length || primary ? { links, primary } : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeFooter(config: DocsConfig['footer']): NormalizedFooter | null {
|
|
77
|
+
const socials = (config?.socials || []).filter(link => !!link?.href).map(normalizeLink);
|
|
78
|
+
const links = (config?.links || [])
|
|
79
|
+
.filter(column => column?.items?.length)
|
|
80
|
+
.map(column => ({
|
|
81
|
+
...column,
|
|
82
|
+
items: column.items.map(item => ({
|
|
83
|
+
...item,
|
|
84
|
+
target: resolveLinkTarget(item.href, item.target),
|
|
85
|
+
})),
|
|
86
|
+
}));
|
|
87
|
+
const attribution = config?.attribution !== false;
|
|
88
|
+
|
|
89
|
+
return socials.length || links.length || attribution ? { socials, links, attribution } : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function resolveSiteModel(config: DocsConfig, docs: NormalizedDocsConfig): SiteModel {
|
|
93
|
+
const appearance = config.appearance || {};
|
|
94
|
+
const logo = config.logo
|
|
95
|
+
? typeof config.logo === 'string'
|
|
96
|
+
? { light: config.logo, dark: config.logo }
|
|
97
|
+
: {
|
|
98
|
+
light: config.logo.light || config.logo.dark,
|
|
99
|
+
dark: config.logo.dark || config.logo.light,
|
|
100
|
+
href: config.logo.href,
|
|
101
|
+
target: config.logo.href
|
|
102
|
+
? resolveLinkTarget(config.logo.href, config.logo.target)
|
|
103
|
+
: undefined,
|
|
104
|
+
}
|
|
105
|
+
: null;
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
name: config.name?.trim() || undefined,
|
|
109
|
+
logo,
|
|
110
|
+
navbar: normalizeNavbar(config.navbar),
|
|
111
|
+
footer: normalizeFooter(config.footer),
|
|
112
|
+
banner: config.banner?.content?.trim()
|
|
113
|
+
? { content: config.banner.content.trim(), dismissible: config.banner.dismissible === true }
|
|
114
|
+
: null,
|
|
115
|
+
appearance: {
|
|
116
|
+
default:
|
|
117
|
+
appearance.default === 'light' || appearance.default === 'dark'
|
|
118
|
+
? appearance.default
|
|
119
|
+
: 'system',
|
|
120
|
+
strict: appearance.strict === true,
|
|
121
|
+
},
|
|
122
|
+
styling: {
|
|
123
|
+
eyebrows: config.styling?.eyebrows === 'breadcrumbs' ? 'breadcrumbs' : 'section',
|
|
124
|
+
},
|
|
125
|
+
search: resolveSearchConfig(config.search),
|
|
126
|
+
contextualOptions: config.contextual?.options || [],
|
|
127
|
+
error404: { ...config.errors?.['404'], redirect: config.errors?.['404']?.redirect !== false },
|
|
128
|
+
showTimestamp: config.metadata?.timestamp === true,
|
|
129
|
+
drilldown: config.interaction?.drilldown,
|
|
130
|
+
locale: config.$shiso?.locale?.trim() || 'en-US',
|
|
131
|
+
labels: SHISO_THEME_LABELS,
|
|
132
|
+
docs,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function aiPrompt(mdUrl: string): string {
|
|
137
|
+
return `Read ${mdUrl} so I can ask questions about it.`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function resolveContextualOptions(
|
|
141
|
+
options: ContextualOption[],
|
|
142
|
+
page: NormalizedDocsPage,
|
|
143
|
+
labels: ThemeLabels = SHISO_THEME_LABELS,
|
|
144
|
+
): ResolvedContextualOption[] {
|
|
145
|
+
const mdHref = `${toHref(page.url)}.md`;
|
|
146
|
+
const absolutePageUrl = toAbsoluteUrl(page.url);
|
|
147
|
+
const mdUrl = absolutePageUrl ? `${absolutePageUrl}.md` : undefined;
|
|
148
|
+
const resolved: ResolvedContextualOption[] = [];
|
|
149
|
+
|
|
150
|
+
for (const option of options) {
|
|
151
|
+
if (typeof option !== 'string') {
|
|
152
|
+
const custom = option as ContextualOptionObject;
|
|
153
|
+
const href = custom.href.replaceAll('$path', page.url).replaceAll('$page', mdUrl || mdHref);
|
|
154
|
+
|
|
155
|
+
resolved.push({
|
|
156
|
+
key: custom.title,
|
|
157
|
+
title: custom.title,
|
|
158
|
+
description: custom.description,
|
|
159
|
+
icon: custom.icon,
|
|
160
|
+
action: 'link',
|
|
161
|
+
href,
|
|
162
|
+
target: resolveLinkTarget(href, custom.target),
|
|
163
|
+
});
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (option === 'copy') {
|
|
168
|
+
resolved.push({
|
|
169
|
+
key: option,
|
|
170
|
+
title: labels.copyPage,
|
|
171
|
+
description: labels.copyPageDescription,
|
|
172
|
+
icon: 'copy',
|
|
173
|
+
action: 'copy',
|
|
174
|
+
href: mdHref,
|
|
175
|
+
target: '_self',
|
|
176
|
+
});
|
|
177
|
+
} else if (option === 'view') {
|
|
178
|
+
resolved.push({
|
|
179
|
+
key: option,
|
|
180
|
+
title: labels.viewMarkdown,
|
|
181
|
+
description: labels.viewMarkdownDescription,
|
|
182
|
+
icon: 'external-link',
|
|
183
|
+
action: 'link',
|
|
184
|
+
href: mdHref,
|
|
185
|
+
target: '_blank',
|
|
186
|
+
});
|
|
187
|
+
} else if (mdUrl && option === 'chatgpt') {
|
|
188
|
+
resolved.push({
|
|
189
|
+
key: option,
|
|
190
|
+
title: labels.openInChatGPT,
|
|
191
|
+
description: labels.askQuestionsAboutPage,
|
|
192
|
+
icon: 'external-link',
|
|
193
|
+
action: 'link',
|
|
194
|
+
href: `https://chatgpt.com/?q=${encodeURIComponent(aiPrompt(mdUrl))}`,
|
|
195
|
+
target: '_blank',
|
|
196
|
+
});
|
|
197
|
+
} else if (mdUrl && option === 'claude') {
|
|
198
|
+
resolved.push({
|
|
199
|
+
key: option,
|
|
200
|
+
title: labels.openInClaude,
|
|
201
|
+
description: labels.askQuestionsAboutPage,
|
|
202
|
+
icon: 'external-link',
|
|
203
|
+
action: 'link',
|
|
204
|
+
href: `https://claude.ai/new?q=${encodeURIComponent(aiPrompt(mdUrl))}`,
|
|
205
|
+
target: '_blank',
|
|
206
|
+
});
|
|
207
|
+
} else if (mdUrl && option === 'perplexity') {
|
|
208
|
+
resolved.push({
|
|
209
|
+
key: option,
|
|
210
|
+
title: labels.openInPerplexity,
|
|
211
|
+
description: labels.askQuestionsAboutPage,
|
|
212
|
+
icon: 'external-link',
|
|
213
|
+
action: 'link',
|
|
214
|
+
href: `https://www.perplexity.ai/search?q=${encodeURIComponent(aiPrompt(mdUrl))}`,
|
|
215
|
+
target: '_blank',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return resolved;
|
|
221
|
+
}
|
package/src/lib/slug.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import GithubSlugger, { slug as slugifyOnce } from 'github-slugger';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The single slug algorithm in the repo.
|
|
5
|
+
*
|
|
6
|
+
* Heading anchors are produced in two independent places — `remarkToc` (which
|
|
7
|
+
* builds the on-page table of contents) and `rehype-slug` (which sets the `id`
|
|
8
|
+
* on the rendered heading). Both must agree or every TOC link breaks, so both
|
|
9
|
+
* go through github-slugger here rather than reimplementing it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a stateful slugger. Call `.slug(text)` once per heading in document
|
|
14
|
+
* order; repeated headings get `-1`, `-2`, ... suffixes exactly as rehype-slug
|
|
15
|
+
* does, because it is the same implementation.
|
|
16
|
+
*/
|
|
17
|
+
export function createSlugger(): GithubSlugger {
|
|
18
|
+
return new GithubSlugger();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Stateless slugify for one-off ids that do not need de-duplication.
|
|
23
|
+
*
|
|
24
|
+
* github-slugger does not trim, so padded input would otherwise produce leading
|
|
25
|
+
* and trailing dashes. Heading text is trimmed upstream by `headingText`; this
|
|
26
|
+
* makes the guarantee hold for every other caller too.
|
|
27
|
+
*/
|
|
28
|
+
export function slugify(value: string): string {
|
|
29
|
+
return slugifyOnce(value.trim());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Slugifies a label into a config-level identifier (tab ids, section ids).
|
|
34
|
+
* Falls back when the value contains no slug-able characters.
|
|
35
|
+
*/
|
|
36
|
+
export function slugifyId(value: string, fallback: string): string {
|
|
37
|
+
return slugify(value) || fallback;
|
|
38
|
+
}
|