diff --git a/server/fixtures/llms/images.html b/server/fixtures/llms/images.html new file mode 100644 index 00000000..40c82429 --- /dev/null +++ b/server/fixtures/llms/images.html @@ -0,0 +1,2 @@ +
Paragraph before image.
+Paragraph after image.
Paragraph before image.
+Paragraph after image.
\ No newline at end of file diff --git a/server/llms/rehype-prepare-html.test.ts b/server/llms/rehype-prepare-html.test.ts index 2ce7472e..856e4847 100644 --- a/server/llms/rehype-prepare-html.test.ts +++ b/server/llms/rehype-prepare-html.test.ts @@ -146,6 +146,19 @@ describe("server/llms/rehype-prepare-html", () => { ); }); + test("Images: removes all img elements, preserving surrounding content", () => { + const result = transformer( + readFileSync(resolve("server/fixtures/llms/images.html"), "utf-8"), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/images.html"), + "utf-8", + ).trim(), + ); + }); + test("Irrelevant components: removes thumbsFeedback and checkpoint components, preserving other content", () => { const result = transformer( readFileSync( diff --git a/server/llms/rehype-prepare-html.ts b/server/llms/rehype-prepare-html.ts index 2ed5a4f6..88565bfb 100644 --- a/server/llms/rehype-prepare-html.ts +++ b/server/llms/rehype-prepare-html.ts @@ -11,6 +11,7 @@ import { visitParents, SKIP } from "unist-util-visit-parents"; * Code blocks: extract text content and replace with clean structure
* Card links: prevent duplicate links in the markdown output
* Heading anchor links: remove hash-link anchors from headings
+ * Images: remove all image elements from the output
* Irrelevant components: remove interactive UI components (thumbsFeedback, checkpoint, docsHeader)
* H1 repositioning: move the h1 heading to the top of the document
*/
@@ -86,6 +87,12 @@ const rehypePrepareHTML: Plugin<[], Root, Root> = function () {
return SKIP; // don't traverse children (e.g. avoids capturing an h1 inside a removed node)
}
+ // *** Images ***
+ if (element.tagName === "img") {
+ toRemove.push({ node: element, parent });
+ return SKIP;
+ }
+
// *** Headings ***
if (/^h[1-6]$/.test(element.tagName)) {
// Remove hash-link anchor children in-place
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index f55f4fbd..b7199658 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -7,6 +7,7 @@ import type {
import { clsx } from "clsx";
import styles from "./Button.module.css";
+import Link from "@docusaurus/Link";
export type ButtonVariant =
| "primary"
@@ -55,7 +56,7 @@ const Button = ({
};
if (as === "link") {
- return ;
+ return ;
}
return ;
diff --git a/src/components/PageActions/PageActions.module.css b/src/components/PageActions/PageActions.module.css
index a293a083..f16580f8 100644
--- a/src/components/PageActions/PageActions.module.css
+++ b/src/components/PageActions/PageActions.module.css
@@ -1,6 +1,6 @@
.pageActions {
display: flex;
- gap: var(--m-3);
+ gap: var(--m-2);
padding: var(--m-1-5) 0;
border-bottom: 1px solid var(--color-tonal-neutral-2);
margin-bottom: var(--m-5);
@@ -10,7 +10,7 @@
&::after {
content: "";
position: relative;
- transform: translateX(var(--m-1-5));
+ transform: translateX(var(--m-1));
width: 1px;
height: 100%;
background-color: var(--color-tonal-neutral-1);
@@ -18,7 +18,19 @@
}
}
- @media (--lg-scr) {
+ @media (--md-scr) {
+ gap: var(--m-3);
+
+ & > * {
+ &:not(:nth-last-child(2)) {
+ &::after {
+ transform: translateX(var(--m-1-5));
+ }
+ }
+ }
+ }
+
+ @media (--desktop-page-actions) {
gap: var(--m-4);
margin-bottom: var(--m-6);
& > * {
@@ -34,6 +46,23 @@
}
}
}
+
+ &.pageHasTOC {
+ @media screen and (min-width: 997px) and (max-width: 1301px) {
+ & > * {
+ &:not(:last-child) {
+ &::after {
+ display: none;
+ }
+ }
+ }
+ .githubLink,
+ .askAIButton,
+ .feedback {
+ white-space: wrap;
+ }
+ }
+ }
}
.askAIButton {
@@ -53,7 +82,8 @@
color: var(--color-foreground-slightly-muted);
display: flex;
align-items: center;
- font-size: var(--fs-text-lg);
+ font-size: var(--fs-text-sm);
+ white-space: nowrap;
letter-spacing: 0.024px;
line-height: 1.625;
margin: 0;
@@ -61,11 +91,11 @@
font-weight: var(--fw-medium);
& > svg {
- width: 20px;
- height: 20px;
- min-width: 20px;
- min-height: 20px;
- margin-right: var(--m-0-5);
+ width: var(--m-2);
+ height: var(--m-2);
+ min-width: var(--m-2);
+ min-height: var(--m-2);
+ margin-right: 2px;
& > path {
fill: var(--color-foreground-muted);
}
@@ -74,10 +104,24 @@
@media (--xs-scr) {
width: auto;
height: auto;
+ font-size: var(--fs-text-md);
+
+ & > svg {
+ margin-right: var(--m-0-5);
+ }
}
@media (--md-scr) {
line-height: 1.75;
+ font-size: var(--fs-text-lg);
+
+ & > svg {
+ width: 20px;
+ height: 20px;
+ min-width: 20px;
+ min-height: 20px;
+ margin-right: var(--m-0-5);
+ }
}
}
diff --git a/src/components/PageActions/PageActions.tsx b/src/components/PageActions/PageActions.tsx
index b0f1cc92..76f58508 100644
--- a/src/components/PageActions/PageActions.tsx
+++ b/src/components/PageActions/PageActions.tsx
@@ -1,4 +1,5 @@
import { getReportIssueURL } from "@site/src/utils/github-issue";
+import cn from "clsx";
import styles from "./PageActions.module.css";
import Icon from "../Icon";
import { useInkeepSearch } from "@site/src/hooks/useInkeepSearch";
@@ -10,12 +11,17 @@ import {
} from "@site/src/utils/markdown";
import { useMemo, useRef, useState } from "react";
import { useWindowSize } from "@docusaurus/theme-common";
+import { useDocTOC } from "@site/src/theme/DocItem/Layout";
+import { useDocTemplate } from "@site/src/hooks/useDocTemplate";
const PageActions: React.FC<{ pathname: string }> = ({ pathname }) => {
const { setIsOpen, ModalSearchAndChat, inkeepModalProps } = useInkeepSearch({
enableAIChat: true,
});
+ const { removeTOCSidebar } = useDocTemplate();
const windowSize = useWindowSize();
+ const docTOC = useDocTOC(!!removeTOCSidebar);
+
const [copiedMessage, setCopiedMessage] = useState("Copy for LLM");
const copyButtonRef = useRef(null);
@@ -26,7 +32,11 @@ const PageActions: React.FC<{ pathname: string }> = ({ pathname }) => {
);
return (
-
+
= ({ pathname }) => {
View as Markdown
-
+ {/* docTOC.canRender is passed to ThumbsFeedback instead of calling directly in the child, because the component is rendered in different places and contexts */}
+
}>
{() => {
return (
diff --git a/src/components/ThumbsFeedback/ThumbsFeedback.module.css b/src/components/ThumbsFeedback/ThumbsFeedback.module.css
index 9d6aa0a8..c8ccfc2c 100644
--- a/src/components/ThumbsFeedback/ThumbsFeedback.module.css
+++ b/src/components/ThumbsFeedback/ThumbsFeedback.module.css
@@ -3,10 +3,16 @@
display: none;
}
- @media (--lg-scr) {
+ @media (--desktop-page-actions) {
display: block !important;
}
+ &.pageHasTOC {
+ @media screen and (min-width: 997px) and (max-width: 1301px) {
+ display: none !important;
+ }
+ }
+
.feedbackTitle {
font-size: var(--fs-text-sm);
line-height: var(--lh-md);
@@ -83,7 +89,7 @@
color: var(--color-foreground-slightly-muted);
align-items: center;
gap: var(--m-1);
- font-size: var(--fs-text-lg);
+ font-size: var(--fs-text-md);
letter-spacing: 0.024px;
line-height: 1.625;
margin: 0;
@@ -92,6 +98,7 @@
@media (--md-scr) {
line-height: 1.75;
+ font-size: var(--fs-text-lg);
}
& > svg {
diff --git a/src/components/ThumbsFeedback/ThumbsFeedback.tsx b/src/components/ThumbsFeedback/ThumbsFeedback.tsx
index ef680973..ed333e6c 100644
--- a/src/components/ThumbsFeedback/ThumbsFeedback.tsx
+++ b/src/components/ThumbsFeedback/ThumbsFeedback.tsx
@@ -24,7 +24,7 @@ const MAX_PATHS_IN_STORAGE = 4;
const checkForExpiredFeedback = (
itemStr: string,
- currentPath: string
+ currentPath: string,
): StoredFeedback[] => {
let feedbackGivenPaths: StoredFeedback[] = [];
try {
@@ -38,17 +38,17 @@ const checkForExpiredFeedback = (
const now = new Date();
const foundCurrentPath = feedbackGivenPaths.find(
- (pathItem) => pathItem.path === currentPath
+ (pathItem) => pathItem.path === currentPath,
);
if (foundCurrentPath && now.getTime() > foundCurrentPath.expiry) {
// Remove expired path and update storage
feedbackGivenPaths = feedbackGivenPaths.filter(
- (pathItem) => pathItem.path !== currentPath
+ (pathItem) => pathItem.path !== currentPath,
);
localStorage.setItem(
"feedback_given_paths",
- JSON.stringify(feedbackGivenPaths)
+ JSON.stringify(feedbackGivenPaths),
);
}
} catch (e) {
@@ -134,7 +134,7 @@ const FeedbackForm: React.FC<{
}, [formActive]);
const handleSubmit = async (
- event: FormEvent
+ event: FormEvent,
): Promise => {
event.preventDefault();
@@ -149,11 +149,11 @@ const FeedbackForm: React.FC<{
let feedbackGivenPaths: StoredFeedback[] = checkForExpiredFeedback(
localStorage.getItem("feedback_given_paths"),
- currentPath
+ currentPath,
);
const foundCurrentPath = feedbackGivenPaths.find(
- (pathItem) => pathItem.path === currentPath
+ (pathItem) => pathItem.path === currentPath,
);
if (foundCurrentPath?.commented && isSubmitted) {
@@ -171,7 +171,7 @@ const FeedbackForm: React.FC<{
});
localStorage.setItem(
"feedback_given_paths",
- JSON.stringify(feedbackGivenPaths)
+ JSON.stringify(feedbackGivenPaths),
);
trackEvent({
@@ -185,7 +185,6 @@ const FeedbackForm: React.FC<{
setIsSubmitted(true);
return;
}
-
}
setIsSubmitted(true);
@@ -247,18 +246,20 @@ const FeedbackForm: React.FC<{
const ThumbsFeedback: React.FC<{
feedbackLabel?: string;
pagePosition?: "top" | "bottom";
+ pageHasTOC?: boolean;
emitEvent?: (name: string, params: any) => {};
}> = ({
feedbackLabel = "Is this page helpful?",
pagePosition = "top",
+ pageHasTOC = false,
emitEvent,
}): JSX.Element => {
const { feedback, isSubmitted, setFeedback, setIsSubmitted } = useContext(
- ThumbsFeedbackContext
+ ThumbsFeedbackContext,
);
const [comment, setComment] = useState("");
const [formActive, setFormActive] = useState<"positive" | "negative" | false>(
- false
+ false,
);
const location = useLocation();
@@ -272,11 +273,11 @@ const ThumbsFeedback: React.FC<{
const currentPath = location.pathname;
const feedbackGivenPaths = checkForExpiredFeedback(
localStorage.getItem("feedback_given_paths"),
- currentPath
+ currentPath,
);
const foundCurrentFeedbackPathClick = feedbackGivenPaths.find(
- (pathItem) => pathItem.path === currentPath
+ (pathItem) => pathItem.path === currentPath,
);
const feedbackCommented = foundCurrentFeedbackPathClick?.commented;
@@ -291,14 +292,14 @@ const ThumbsFeedback: React.FC<{
}, [location.pathname]);
const handleFeedbackClick = async (
- feedbackValue: FeedbackType
+ feedbackValue: FeedbackType,
): Promise => {
const currentPath = location.pathname;
const itemStr = localStorage.getItem("feedback_given_paths");
let clickedPaths = checkForExpiredFeedback(itemStr, currentPath);
const foundCurrentPath = clickedPaths.find(
- (pathItem) => pathItem.path === currentPath
+ (pathItem) => pathItem.path === currentPath,
);
if (foundCurrentPath && foundCurrentPath.signal === feedbackValue) {
@@ -307,7 +308,7 @@ const ThumbsFeedback: React.FC<{
// Remove existing entry for current path to update with new signal
// Also reset the submission state
clickedPaths = clickedPaths.filter(
- (pathItem) => pathItem.path !== currentPath
+ (pathItem) => pathItem.path !== currentPath,
);
setIsSubmitted(false);
}
@@ -336,13 +337,13 @@ const ThumbsFeedback: React.FC<{
if (newFeedbackThumbsClickedPaths.length > MAX_PATHS_IN_STORAGE) {
newFeedbackThumbsClickedPaths.splice(
0,
- newFeedbackThumbsClickedPaths.length - MAX_PATHS_IN_STORAGE
+ newFeedbackThumbsClickedPaths.length - MAX_PATHS_IN_STORAGE,
);
}
localStorage.setItem(
"feedback_given_paths",
- JSON.stringify(newFeedbackThumbsClickedPaths)
+ JSON.stringify(newFeedbackThumbsClickedPaths),
);
};
@@ -350,6 +351,7 @@ const ThumbsFeedback: React.FC<{
-
+
{icon}
{name}
-
+
>
);
}
diff --git a/src/styles/media.css b/src/styles/media.css
index a0c29905..21a13b73 100644
--- a/src/styles/media.css
+++ b/src/styles/media.css
@@ -1,6 +1,7 @@
-@custom-media --xs-scr (min-width: 381px);
+@custom-media --xs-scr (min-width: 412px);
@custom-media --sm-scr (max-width: 900px);
@custom-media --md-scr (min-width: 901px);
@custom-media --lg-scr (min-width: 1201px);
@custom-media --xl-scr (min-width: 1401px);
@custom-media --nav (min-width: 997px);
+@custom-media --desktop-page-actions (min-width: 696px);
diff --git a/src/theme/DocItem/Layout/index.tsx b/src/theme/DocItem/Layout/index.tsx
index 583332ae..4f030cba 100644
--- a/src/theme/DocItem/Layout/index.tsx
+++ b/src/theme/DocItem/Layout/index.tsx
@@ -25,7 +25,7 @@ interface ExtendedFrontMatter {
/**
* Decide if the toc should be rendered, on mobile or desktop viewports
*/
-function useDocTOC(removeTOCSidebar: boolean) {
+export function useDocTOC(removeTOCSidebar: boolean) {
const { frontMatter, toc } = useDoc();
const windowSize = useWindowSize();
@@ -45,6 +45,7 @@ function useDocTOC(removeTOCSidebar: boolean) {
return {
hidden,
removed,
+ canRender,
mobile,
desktop,
canRender,
@@ -62,8 +63,7 @@ function usePageExclusivityBanner() {
export default function DocItemLayout({ children }: Props): JSX.Element {
const { hideTitleSection, removeTOCSidebar, fullWidth, isLandingPage } =
useDocTemplate();
- const docTOC = useDocTOC(removeTOCSidebar);
-
+ const docTOC = useDocTOC(!!removeTOCSidebar);
const { exclusiveFeature } = usePageExclusivityBanner();
const {
metadata: { unlisted },