mirror of
https://github.com/samber/lo.git
synced 2026-04-22 15:37:14 +08:00
adding posthog
This commit is contained in:
@@ -15,20 +15,22 @@ permissions:
|
|||||||
security-events: write
|
security-events: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
govulncheck:
|
# govulncheck:
|
||||||
name: govulncheck
|
# name: govulncheck
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
strategy:
|
# strategy:
|
||||||
fail-fast: false
|
# fail-fast: false
|
||||||
steps:
|
# steps:
|
||||||
- uses: actions/checkout@v6
|
# - uses: actions/checkout@v6
|
||||||
- uses: actions/setup-go@v6
|
# - uses: actions/setup-go@v6
|
||||||
|
# with:
|
||||||
|
# go-version-file: go.mod
|
||||||
|
|
||||||
- name: Install govulncheck
|
# - name: Install govulncheck
|
||||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
# run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
|
||||||
- name: govulncheck
|
# - name: govulncheck
|
||||||
run: govulncheck ./...
|
# run: govulncheck ./...
|
||||||
|
|
||||||
bearer:
|
bearer:
|
||||||
name: bearer
|
name: bearer
|
||||||
@@ -50,6 +52,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v4
|
uses: github/codeql-action/init@v4
|
||||||
@@ -69,6 +73,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scanner (source code)
|
- name: Run Trivy vulnerability scanner (source code)
|
||||||
uses: aquasecurity/trivy-action@0.35.0
|
uses: aquasecurity/trivy-action@0.35.0
|
||||||
@@ -80,6 +86,7 @@ jobs:
|
|||||||
output: "trivy-results.sarif"
|
output: "trivy-results.sarif"
|
||||||
severity: "CRITICAL,HIGH,MEDIUM"
|
severity: "CRITICAL,HIGH,MEDIUM"
|
||||||
ignore-unfixed: true
|
ignore-unfixed: true
|
||||||
|
trivyignores: ".trivyignore"
|
||||||
skip-dirs: "docs/"
|
skip-dirs: "docs/"
|
||||||
|
|
||||||
- name: Upload Trivy results to GitHub Security tab
|
- name: Upload Trivy results to GitHub Security tab
|
||||||
@@ -96,6 +103,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
- name: Run Trivy scanner (table output for logs)
|
- name: Run Trivy scanner (table output for logs)
|
||||||
uses: aquasecurity/trivy-action@0.35.0
|
uses: aquasecurity/trivy-action@0.35.0
|
||||||
@@ -107,6 +116,7 @@ jobs:
|
|||||||
format: "table"
|
format: "table"
|
||||||
severity: "CRITICAL,HIGH,MEDIUM"
|
severity: "CRITICAL,HIGH,MEDIUM"
|
||||||
ignore-unfixed: true
|
ignore-unfixed: true
|
||||||
|
trivyignores: ".trivyignore"
|
||||||
exit-code: "1"
|
exit-code: "1"
|
||||||
skip-dirs: "docs/"
|
skip-dirs: "docs/"
|
||||||
|
|
||||||
|
|||||||
+22
-12
@@ -59,10 +59,11 @@ const config: Config = {
|
|||||||
// Optional: Enable hash router for offline support (experimental)
|
// Optional: Enable hash router for offline support (experimental)
|
||||||
// Uncomment if you need offline browsing capability
|
// Uncomment if you need offline browsing capability
|
||||||
// router: 'hash',
|
// router: 'hash',
|
||||||
|
|
||||||
// Future-proofing configurations
|
// Future-proofing configurations
|
||||||
clientModules: [
|
clientModules: [
|
||||||
require.resolve('./src/theme/prism-include-languages.js'),
|
require.resolve('./src/theme/prism-include-languages.js'),
|
||||||
|
require.resolve('./src/clientModules/posthog-events.ts'),
|
||||||
],
|
],
|
||||||
|
|
||||||
// Even if you don't use internationalization, you can use this field to set
|
// Even if you don't use internationalization, you can use this field to set
|
||||||
@@ -170,8 +171,8 @@ const config: Config = {
|
|||||||
// Enhanced markdown features
|
// Enhanced markdown features
|
||||||
remarkPlugins: [],
|
remarkPlugins: [],
|
||||||
rehypePlugins: [],
|
rehypePlugins: [],
|
||||||
},
|
},
|
||||||
sitemap: {
|
sitemap: {
|
||||||
lastmod: 'date',
|
lastmod: 'date',
|
||||||
changefreq: 'weekly',
|
changefreq: 'weekly',
|
||||||
priority: 0.7,
|
priority: 0.7,
|
||||||
@@ -220,8 +221,8 @@ const config: Config = {
|
|||||||
maxTextSize: 50000,
|
maxTextSize: 50000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Enhanced metadata
|
// Enhanced metadata
|
||||||
metadata: [
|
metadata: [
|
||||||
{name: 'og:type', content: 'website'},
|
{name: 'og:type', content: 'website'},
|
||||||
],
|
],
|
||||||
@@ -238,8 +239,8 @@ const config: Config = {
|
|||||||
sidebarId: 'docSidebar',
|
sidebarId: 'docSidebar',
|
||||||
position: 'left',
|
position: 'left',
|
||||||
label: 'Doc',
|
label: 'Doc',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: 'https://pkg.go.dev/github.com/samber/lo',
|
to: 'https://pkg.go.dev/github.com/samber/lo',
|
||||||
label: 'GoDoc',
|
label: 'GoDoc',
|
||||||
position: 'left',
|
position: 'left',
|
||||||
@@ -363,10 +364,19 @@ const config: Config = {
|
|||||||
} satisfies Preset.ThemeConfig,
|
} satisfies Preset.ThemeConfig,
|
||||||
|
|
||||||
themes: ['@docusaurus/theme-mermaid'],
|
themes: ['@docusaurus/theme-mermaid'],
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
// Add ideal image plugin for better image optimization
|
[
|
||||||
[
|
"posthog-docusaurus",
|
||||||
|
{
|
||||||
|
apiKey: "phc_uA762TtYyJ6UrbF5nzWutAJojstpC2EDptFpd2bBvWFY",
|
||||||
|
appHost: "https://hogpost.samber.dev",
|
||||||
|
enableInDevelopment: false, // optional,
|
||||||
|
disableSessionRecording: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// Add ideal image plugin for better image optimization
|
||||||
|
[
|
||||||
'@docusaurus/plugin-ideal-image',
|
'@docusaurus/plugin-ideal-image',
|
||||||
{
|
{
|
||||||
quality: 70,
|
quality: 70,
|
||||||
|
|||||||
Generated
+10
@@ -21,6 +21,7 @@
|
|||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"marked": "^17.0.1",
|
"marked": "^17.0.1",
|
||||||
|
"posthog-docusaurus": "^2.0.5",
|
||||||
"prism-react-renderer": "^2.3.0",
|
"prism-react-renderer": "^2.3.0",
|
||||||
"prismjs": "^1.29.0",
|
"prismjs": "^1.29.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
@@ -17279,6 +17280,15 @@
|
|||||||
"postcss": "^8.4.31"
|
"postcss": "^8.4.31"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/posthog-docusaurus": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/posthog-docusaurus/-/posthog-docusaurus-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-Ray65LYEJrMMqDtsBUBXunEVP/g4wtATvq/xz9rchUoLy/9mSkkFgUko/8DVtGxgiP3vivpFMgfb9HpCuDrBHg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.15.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prebuild-install": {
|
"node_modules/prebuild-install": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"marked": "^17.0.1",
|
"marked": "^17.0.1",
|
||||||
|
"posthog-docusaurus": "^2.0.5",
|
||||||
"prism-react-renderer": "^2.3.0",
|
"prism-react-renderer": "^2.3.0",
|
||||||
"prismjs": "^1.29.0",
|
"prismjs": "^1.29.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ import Heading from '@theme/Heading';
|
|||||||
import CodeBlock from '@theme/CodeBlock';
|
import CodeBlock from '@theme/CodeBlock';
|
||||||
import '../../../src/prism-include-languages.js';
|
import '../../../src/prism-include-languages.js';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
posthog?: {
|
||||||
|
capture: (event: string, properties?: Record<string, unknown>) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface HelperCardProps {
|
interface HelperCardProps {
|
||||||
helper: HelperDefinition;
|
helper: HelperDefinition;
|
||||||
}
|
}
|
||||||
@@ -125,31 +133,34 @@ export default function HelperCard({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{sourceRef && (
|
{sourceRef && (
|
||||||
<a
|
<a
|
||||||
href={sourceRef}
|
href={sourceRef}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="helper-card__source"
|
className="helper-card__source"
|
||||||
|
onClick={() => window.posthog?.capture('helper_source_clicked', { helper: helper.slug, category: helper.category })}
|
||||||
>
|
>
|
||||||
🧩 Source
|
🧩 Source
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{godocUrl && (
|
{godocUrl && (
|
||||||
<a
|
<a
|
||||||
href={godocUrl}
|
href={godocUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="helper-card__godoc"
|
className="helper-card__godoc"
|
||||||
|
onClick={() => window.posthog?.capture('helper_godoc_clicked', { helper: helper.slug, category: helper.category })}
|
||||||
>
|
>
|
||||||
📚 GoDoc
|
📚 GoDoc
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{helper.playUrl && (
|
{helper.playUrl && (
|
||||||
<a
|
<a
|
||||||
href={helper.playUrl}
|
href={helper.playUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="helper-card__playground"
|
className="helper-card__playground"
|
||||||
|
onClick={() => window.posthog?.capture('helper_playground_clicked', { helper: helper.slug, category: helper.category })}
|
||||||
>
|
>
|
||||||
🎮 Try on Go Playground
|
🎮 Try on Go Playground
|
||||||
</a>
|
</a>
|
||||||
@@ -243,10 +254,11 @@ function SimilarHelpers({
|
|||||||
const displayName = nameRaw || name;
|
const displayName = nameRaw || name;
|
||||||
const isSameSection = type === currentTypeLower; // compare only type for label
|
const isSameSection = type === currentTypeLower; // compare only type for label
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
key={index}
|
key={index}
|
||||||
href={href}
|
href={href}
|
||||||
className="helper-card__similar-link"
|
className="helper-card__similar-link"
|
||||||
|
onClick={() => window.posthog?.capture('helper_similar_clicked', { from: currentName, to: name, title: title.toLowerCase() })}
|
||||||
>
|
>
|
||||||
{isSameSection ? (
|
{isSameSection ? (
|
||||||
displayName
|
displayName
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
posthog?: {
|
||||||
|
capture: (event: string, properties?: Record<string, unknown>) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sponsor click tracking (navbar + sidebar) ---
|
||||||
|
function trackSponsorClicks(): void {
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const anchor = (e.target as Element).closest('a[href*="sponsors/samber"]');
|
||||||
|
if (!anchor) return;
|
||||||
|
|
||||||
|
const isNavbar = anchor.closest('.navbar') !== null;
|
||||||
|
window.posthog?.capture('sponsor_clicked', {
|
||||||
|
location: isNavbar ? 'navbar' : 'sidebar',
|
||||||
|
href: (anchor as HTMLAnchorElement).href,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Search query tracking ---
|
||||||
|
function trackSearch(): void {
|
||||||
|
let inputEl: HTMLInputElement | null = null;
|
||||||
|
|
||||||
|
const attachInputListener = (input: HTMLInputElement) => {
|
||||||
|
if (input === inputEl) return;
|
||||||
|
inputEl = input;
|
||||||
|
input.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter' && input.value.trim()) {
|
||||||
|
window.posthog?.capture('search_submitted', {
|
||||||
|
query: input.value.trim(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
const input = document.querySelector<HTMLInputElement>('.DocSearch-Input');
|
||||||
|
if (input) attachInputListener(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {childList: true, subtree: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onRouteDidUpdate(): void {
|
||||||
|
// Re-run on each navigation in case DOM changed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs once on initial load
|
||||||
|
trackSponsorClicks();
|
||||||
|
trackSearch();
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import React, {useCallback, type ReactNode} from 'react';
|
||||||
|
import CopyButton from '@theme-original/CodeBlock/Buttons/CopyButton';
|
||||||
|
import {useCodeBlockContext} from '@docusaurus/theme-common/internal';
|
||||||
|
import type {Props} from '@theme/CodeBlock/Buttons/CopyButton';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
posthog?: {
|
||||||
|
capture: (event: string, properties?: Record<string, unknown>) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CopyButtonWrapper(props: Props): ReactNode {
|
||||||
|
const {metadata} = useCodeBlockContext();
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
window.posthog?.capture('code_copied', {
|
||||||
|
helper: window.location.hash.replace('#', '') || null,
|
||||||
|
page: window.location.pathname,
|
||||||
|
code_preview: metadata.code?.slice(0, 120),
|
||||||
|
});
|
||||||
|
}, [metadata.code]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span onClick={handleClick}>
|
||||||
|
<CopyButton {...props} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React, {type ReactNode} from 'react';
|
||||||
|
import OriginalColorModeToggle from '@theme-original/ColorModeToggle';
|
||||||
|
import type ColorModeToggleType from '@theme/ColorModeToggle';
|
||||||
|
import type {WrapperProps} from '@docusaurus/types';
|
||||||
|
|
||||||
|
type Props = WrapperProps<typeof ColorModeToggleType>;
|
||||||
|
|
||||||
|
export default function ColorModeToggleWrapper(props: Props): ReactNode {
|
||||||
|
const handleChange: Props['onChange'] = (newMode) => {
|
||||||
|
window.posthog?.capture('color_mode_toggled', {
|
||||||
|
from: props.value ?? 'system',
|
||||||
|
to: newMode ?? 'system',
|
||||||
|
});
|
||||||
|
props.onChange(newMode);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <OriginalColorModeToggle {...props} onChange={handleChange} />;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user