doccupine 0.0.47 → 0.0.49

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.
@@ -1 +1 @@
1
- export declare const docsSideBarTemplate = "\"use client\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Space } from \"cherry-styled-components/src/lib\";\nimport {\n StyledIndexSidebar,\n StyledIndexSidebarLink,\n StyledIndexSidebarLabel,\n} from \"@/components/layout/DocsComponents\";\n\nexport interface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nexport function DocsSideBar({ headings }: { headings: Heading[] }) {\n const [activeId, setActiveId] = useState<string>(\"\");\n\n const handleScroll = useCallback(() => {\n if (headings.length === 0) return;\n\n const headingElements = headings\n .map((heading) => document.getElementById(heading.id))\n .filter(Boolean) as HTMLElement[];\n\n if (headingElements.length === 0) return;\n\n const windowHeight = window.innerHeight;\n\n const visibleHeadings = headingElements.filter((element) => {\n const rect = element.getBoundingClientRect();\n const elementTop = rect.top;\n const elementBottom = rect.bottom;\n return elementTop < windowHeight && elementBottom > -50;\n });\n\n if (visibleHeadings.length > 0) {\n let closestHeading = visibleHeadings[0];\n let closestDistance = Math.abs(\n closestHeading.getBoundingClientRect().top,\n );\n for (const heading of visibleHeadings) {\n const distance = Math.abs(heading.getBoundingClientRect().top);\n if (\n distance < closestDistance &&\n heading.getBoundingClientRect().top <= windowHeight * 0.3\n ) {\n closestDistance = distance;\n closestHeading = heading;\n }\n }\n setActiveId(closestHeading.id);\n return;\n }\n\n let currentActiveId = headings[0].id;\n for (const element of headingElements) {\n const rect = element.getBoundingClientRect();\n if (rect.top <= 0) {\n currentActiveId = element.id;\n } else {\n break;\n }\n }\n setActiveId(currentActiveId);\n }, [headings]);\n\n useEffect(() => {\n if (headings.length === 0) return;\n // Run initial scroll check on next frame to avoid synchronous setState in effect\n const rafId = requestAnimationFrame(handleScroll);\n let timeoutId: NodeJS.Timeout;\n const throttledHandleScroll = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(handleScroll, 50);\n };\n window.addEventListener(\"scroll\", throttledHandleScroll);\n window.addEventListener(\"resize\", handleScroll);\n return () => {\n window.removeEventListener(\"scroll\", throttledHandleScroll);\n window.removeEventListener(\"resize\", handleScroll);\n cancelAnimationFrame(rafId);\n clearTimeout(timeoutId);\n };\n }, [handleScroll, headings]);\n\n const handleHeadingClick = (headingId: string) => {\n const element = document.getElementById(headingId);\n if (element) {\n element.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\n }\n };\n\n return (\n <StyledIndexSidebar>\n {headings?.length > 0 && (\n <>\n <StyledIndexSidebarLabel>On this page</StyledIndexSidebarLabel>\n <Space $size={20} />\n </>\n )}\n {headings.map((heading, index) => (\n <li\n key={index}\n style={{ paddingLeft: `${(heading.level - 1) * 16}px` }}\n >\n <StyledIndexSidebarLink\n href={`#${heading.id}`}\n onClick={(e) => {\n e.preventDefault();\n handleHeadingClick(heading.id);\n }}\n $isActive={activeId === heading.id}\n >\n {heading.text}\n </StyledIndexSidebarLink>\n </li>\n ))}\n </StyledIndexSidebar>\n );\n}\n";
1
+ export declare const docsSideBarTemplate = "\"use client\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Space } from \"cherry-styled-components/src/lib\";\nimport {\n StyledIndexSidebar,\n StyledIndexSidebarLink,\n StyledIndexSidebarLabel,\n} from \"@/components/layout/DocsComponents\";\n\nexport interface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nexport function DocsSideBar({ headings }: { headings: Heading[] }) {\n const [activeId, setActiveId] = useState<string>(\"\");\n\n const getScrollOffset = useCallback(() => {\n return document.getElementById(\"static-links\") ? 90 : 18;\n }, []);\n\n const handleScroll = useCallback(() => {\n if (headings.length === 0) return;\n\n const offset = getScrollOffset();\n\n const headingElements = headings\n .map((heading) => document.getElementById(heading.id))\n .filter(Boolean) as HTMLElement[];\n\n if (headingElements.length === 0) return;\n\n const windowHeight = window.innerHeight;\n\n const visibleHeadings = headingElements.filter((element) => {\n const rect = element.getBoundingClientRect();\n const elementTop = rect.top;\n const elementBottom = rect.bottom;\n return elementTop < windowHeight && elementBottom > -50;\n });\n\n if (visibleHeadings.length > 0) {\n let closestHeading = visibleHeadings[0];\n let closestDistance = Math.abs(\n closestHeading.getBoundingClientRect().top - offset,\n );\n for (const heading of visibleHeadings) {\n const distance = Math.abs(heading.getBoundingClientRect().top - offset);\n if (\n distance < closestDistance &&\n heading.getBoundingClientRect().top <= windowHeight * 0.3\n ) {\n closestDistance = distance;\n closestHeading = heading;\n }\n }\n setActiveId(closestHeading.id);\n return;\n }\n\n let currentActiveId = headings[0].id;\n for (const element of headingElements) {\n const rect = element.getBoundingClientRect();\n if (rect.top <= offset) {\n currentActiveId = element.id;\n } else {\n break;\n }\n }\n setActiveId(currentActiveId);\n }, [headings, getScrollOffset]);\n\n useEffect(() => {\n if (headings.length === 0) return;\n // Run initial scroll check on next frame to avoid synchronous setState in effect\n const rafId = requestAnimationFrame(handleScroll);\n let timeoutId: NodeJS.Timeout;\n const throttledHandleScroll = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(handleScroll, 50);\n };\n window.addEventListener(\"scroll\", throttledHandleScroll);\n window.addEventListener(\"resize\", handleScroll);\n return () => {\n window.removeEventListener(\"scroll\", throttledHandleScroll);\n window.removeEventListener(\"resize\", handleScroll);\n cancelAnimationFrame(rafId);\n clearTimeout(timeoutId);\n };\n }, [handleScroll, headings]);\n\n const handleHeadingClick = (headingId: string) => {\n const element = document.getElementById(headingId);\n if (element) {\n const offset = getScrollOffset();\n const elementPosition =\n element.getBoundingClientRect().top + window.scrollY;\n window.scrollTo({ top: elementPosition - offset, behavior: \"smooth\" });\n }\n };\n\n return (\n <StyledIndexSidebar>\n {headings?.length > 0 && (\n <>\n <StyledIndexSidebarLabel>On this page</StyledIndexSidebarLabel>\n <Space $size={20} />\n </>\n )}\n {headings.map((heading, index) => (\n <li\n key={index}\n style={{ paddingLeft: `${(heading.level - 1) * 16}px` }}\n >\n <StyledIndexSidebarLink\n href={`#${heading.id}`}\n onClick={(e) => {\n e.preventDefault();\n handleHeadingClick(heading.id);\n }}\n $isActive={activeId === heading.id}\n >\n {heading.text}\n </StyledIndexSidebarLink>\n </li>\n ))}\n </StyledIndexSidebar>\n );\n}\n";
@@ -16,9 +16,15 @@ export interface Heading {
16
16
  export function DocsSideBar({ headings }: { headings: Heading[] }) {
17
17
  const [activeId, setActiveId] = useState<string>("");
18
18
 
19
+ const getScrollOffset = useCallback(() => {
20
+ return document.getElementById("static-links") ? 90 : 18;
21
+ }, []);
22
+
19
23
  const handleScroll = useCallback(() => {
20
24
  if (headings.length === 0) return;
21
25
 
26
+ const offset = getScrollOffset();
27
+
22
28
  const headingElements = headings
23
29
  .map((heading) => document.getElementById(heading.id))
24
30
  .filter(Boolean) as HTMLElement[];
@@ -37,10 +43,10 @@ export function DocsSideBar({ headings }: { headings: Heading[] }) {
37
43
  if (visibleHeadings.length > 0) {
38
44
  let closestHeading = visibleHeadings[0];
39
45
  let closestDistance = Math.abs(
40
- closestHeading.getBoundingClientRect().top,
46
+ closestHeading.getBoundingClientRect().top - offset,
41
47
  );
42
48
  for (const heading of visibleHeadings) {
43
- const distance = Math.abs(heading.getBoundingClientRect().top);
49
+ const distance = Math.abs(heading.getBoundingClientRect().top - offset);
44
50
  if (
45
51
  distance < closestDistance &&
46
52
  heading.getBoundingClientRect().top <= windowHeight * 0.3
@@ -56,14 +62,14 @@ export function DocsSideBar({ headings }: { headings: Heading[] }) {
56
62
  let currentActiveId = headings[0].id;
57
63
  for (const element of headingElements) {
58
64
  const rect = element.getBoundingClientRect();
59
- if (rect.top <= 0) {
65
+ if (rect.top <= offset) {
60
66
  currentActiveId = element.id;
61
67
  } else {
62
68
  break;
63
69
  }
64
70
  }
65
71
  setActiveId(currentActiveId);
66
- }, [headings]);
72
+ }, [headings, getScrollOffset]);
67
73
 
68
74
  useEffect(() => {
69
75
  if (headings.length === 0) return;
@@ -87,7 +93,10 @@ export function DocsSideBar({ headings }: { headings: Heading[] }) {
87
93
  const handleHeadingClick = (headingId: string) => {
88
94
  const element = document.getElementById(headingId);
89
95
  if (element) {
90
- element.scrollIntoView({ behavior: "smooth", block: "start" });
96
+ const offset = getScrollOffset();
97
+ const elementPosition =
98
+ element.getBoundingClientRect().top + window.scrollY;
99
+ window.scrollTo({ top: elementPosition - offset, behavior: "smooth" });
91
100
  }
92
101
  };
93
102
 
@@ -1 +1 @@
1
- export declare const iconTemplate = "import { icons } from \"lucide-react\";\n\nexport type IconProps = keyof typeof icons;\n\ninterface Props {\n name: string | IconProps;\n color?: string;\n size?: number;\n className?: string;\n}\n\nfunction transformIconName(name: string): string {\n return name\n .split(\"-\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n\nconst Icon = ({ name, color, size, className }: Props) => {\n const IconName = transformIconName(name as string);\n const LucideIcon = icons[IconName as keyof typeof icons];\n\n return <LucideIcon color={color} size={size} className={className} />;\n};\n\nexport { Icon };\n";
1
+ export declare const iconTemplate = "import { icons } from \"lucide-react\";\n\nexport type IconProps = keyof typeof icons;\n\ninterface Props {\n name: string | IconProps;\n color?: string;\n size?: number;\n className?: string;\n}\n\nfunction transformIconName(name: string): string {\n return name\n .split(\"-\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n\nconst Icon = ({ name, color, size, className }: Props) => {\n const IconName = transformIconName(name as string);\n const LucideIcon = icons[IconName as keyof typeof icons];\n if (!LucideIcon) return null;\n\n return <LucideIcon color={color} size={size} className={className} />;\n};\n\nexport { Icon };\n";
@@ -19,6 +19,7 @@ function transformIconName(name: string): string {
19
19
  const Icon = ({ name, color, size, className }: Props) => {
20
20
  const IconName = transformIconName(name as string);
21
21
  const LucideIcon = icons[IconName as keyof typeof icons];
22
+ if (!LucideIcon) return null;
22
23
 
23
24
  return <LucideIcon color={color} size={size} className={className} />;
24
25
  };
@@ -1 +1 @@
1
- export declare const staticLinksTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport linksData from \"@/links.json\";\n\ninterface LinkProps {\n title: string;\n url: string;\n icon?: string;\n}\n\nconst links = linksData as LinkProps[];\n\nconst StyledStaticLinks = styled.div<{ theme: Theme; $isChatOpen?: boolean }>`\n position: fixed;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n top: 70px;\n padding: 10px 20px;\n display: flex;\n justify-content: space-between;\n width: 100%;\n z-index: 999;\n transition: all 0.3s ease;\n margin: auto;\n background: ${({ theme }) => theme.colors.light};\n overflow-x: auto;\n left: 50%;\n transform: translateX(-50%);\n\n ${mq(\"lg\")} {\n padding: 20px;\n height: 73px;\n top: 0;\n max-width: calc(100vw - 640px);\n width: 100%;\n margin: auto;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 20px 120px 20px 20px;\n `}\n }\n`;\n\nconst StyledStaticLinksContent = styled.div`\n margin: auto 0;\n display: flex;\n gap: 16px;\n flex-wrap: nowrap;\n`;\n\nconst StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>`\n position: relative;\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primary : theme.colors.primary};\n padding: 0;\n display: flex;\n gap: 6px;\n transition: all 0.3s ease;\n font-weight: 600;\n white-space: nowrap;\n min-width: fit-content;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.1)};\n padding: 6px 8px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n ${interactiveStyles};\n\n ${({ $hasIcon }) =>\n $hasIcon &&\n css`\n padding-left: 30px;\n `}\n\n & * {\n margin: auto 0;\n }\n\n & svg {\n position: absolute;\n top: 50%;\n left: 8px;\n transform: translateY(-50%);\n }\n\n &:hover {\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n`;\n\nconst StyledEmpty = styled.div`\n width: 1px;\n max-width: 1px;\n min-width: 1px;\n overflow: hidden;\n text-indent: -9999px;\n`;\n\nfunction StaticLinks() {\n const { isOpen } = useContext(ChatContext);\n\n if (links.length === 0) {\n return null;\n }\n\n return (\n <>\n <StyledStaticLinks $isChatOpen={isOpen}>\n <StyledStaticLinksContent>\n {links.map((link, index) => (\n <StyledLink\n key={index}\n href={link.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n $hasIcon={link.icon ? true : false}\n >\n {link.icon && <Icon name={link.icon} size={16} />}\n <span>{link.title}</span>\n </StyledLink>\n ))}\n <StyledEmpty />\n </StyledStaticLinksContent>\n </StyledStaticLinks>\n </>\n );\n}\n\nexport { StaticLinks };\n";
1
+ export declare const staticLinksTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport linksData from \"@/links.json\";\n\ninterface LinkProps {\n title: string;\n url: string;\n icon?: string;\n}\n\nconst links = linksData as LinkProps[];\n\nconst StyledStaticLinks = styled.div<{ theme: Theme; $isChatOpen?: boolean }>`\n position: fixed;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n top: 70px;\n padding: 10px 20px;\n display: flex;\n justify-content: space-between;\n width: 100%;\n z-index: 999;\n transition: all 0.3s ease;\n margin: auto;\n background: ${({ theme }) => theme.colors.light};\n overflow-x: auto;\n left: 50%;\n transform: translateX(-50%);\n\n ${mq(\"lg\")} {\n padding: 20px;\n height: 73px;\n top: 0;\n max-width: calc(100vw - 640px);\n width: 100%;\n margin: auto;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 20px 120px 20px 20px;\n `}\n }\n`;\n\nconst StyledStaticLinksContent = styled.div`\n margin: auto 0;\n display: flex;\n gap: 16px;\n flex-wrap: nowrap;\n`;\n\nconst StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>`\n position: relative;\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primary : theme.colors.primary};\n padding: 0;\n display: flex;\n gap: 6px;\n transition: all 0.3s ease;\n font-weight: 600;\n white-space: nowrap;\n min-width: fit-content;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.1)};\n padding: 6px 8px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n ${interactiveStyles};\n\n ${({ $hasIcon }) =>\n $hasIcon &&\n css`\n padding-left: 30px;\n `}\n\n & * {\n margin: auto 0;\n }\n\n & svg {\n position: absolute;\n top: 50%;\n left: 8px;\n transform: translateY(-50%);\n }\n\n &:hover {\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n`;\n\nconst StyledEmpty = styled.div`\n width: 1px;\n max-width: 1px;\n min-width: 1px;\n overflow: hidden;\n text-indent: -9999px;\n`;\n\nfunction StaticLinks() {\n const { isOpen } = useContext(ChatContext);\n\n if (links.length === 0) {\n return null;\n }\n\n return (\n <>\n <StyledStaticLinks $isChatOpen={isOpen} id=\"static-links\">\n <StyledStaticLinksContent>\n {links.map((link, index) => (\n <StyledLink\n key={index}\n href={link.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n $hasIcon={link.icon ? true : false}\n >\n {link.icon && <Icon name={link.icon} size={16} />}\n <span>{link.title}</span>\n </StyledLink>\n ))}\n <StyledEmpty />\n </StyledStaticLinksContent>\n </StyledStaticLinks>\n </>\n );\n}\n\nexport { StaticLinks };\n";
@@ -114,7 +114,7 @@ function StaticLinks() {
114
114
 
115
115
  return (
116
116
  <>
117
- <StyledStaticLinks $isChatOpen={isOpen}>
117
+ <StyledStaticLinks $isChatOpen={isOpen} id="static-links">
118
118
  <StyledStaticLinksContent>
119
119
  {links.map((link, index) => (
120
120
  <StyledLink
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doccupine",
3
- "version": "0.0.47",
3
+ "version": "0.0.49",
4
4
  "description": "Document management system that allows you to store, organize, and share your documentation with ease. AI-ready.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {