Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/did-you-know-that-the-world-is-round.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@node-core/doc-kit': patch
---

Close Orama search when the target link is on the same page
5 changes: 5 additions & 0 deletions .changeset/yes-i-did-know-that-thank-you.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@node-core/doc-kit': patch
---

Render markdown `code` snippets in the sidebar
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,45 @@ import styles from './index.module.css';
import useOrama from '../../hooks/useOrama.mjs';
import { relativeOrAbsolute } from '../../utils/relativeOrAbsolute.mjs';

/**
* Dismisses the search modal the clicked hit sits in
*
* @param {MouseEvent} event
*/
const followSearchHit = event => {
if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {
return;
}

const target = new URL(event.currentTarget.href);

event.preventDefault();

// Escape is the only dismissal `SearchModal` exposes to the hits it renders.
event.currentTarget.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })
);

requestAnimationFrame(() => {
if (target.pathname !== window.location.pathname) {
window.location.href = target.href;
return;
}

requestAnimationFrame(() =>
document
.getElementById(decodeURIComponent(target.hash.slice(1)))
?.scrollIntoView()
);
Comment thread
avivkeller marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same-page search omits URL hash

Medium Severity

followSearchHit always calls preventDefault, then on same-pathname hits only scrollIntoViews the target. That closes the modal and moves the viewport, but never updates location.hash or history, so the URL no longer reflects the section the user opened via search (unlike a normal in-page anchor click).

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6e7b974. Configure here.

});
};

/**
* A search hit link that dismisses the modal when followed.
* @param {Object} props - Anchor props, as passed by `SearchHit`.
*/
const SearchHitLink = props => <a {...props} onClick={followSearchHit} />;

const SearchBox = ({ pathname }) => {
const client = useOrama(pathname);

Expand All @@ -21,6 +60,7 @@ const SearchBox = ({ pathname }) => {
noResultsTitle="No results found for"
onHit={hit => (
<SearchHit
as={SearchHitLink}
document={{
...hit.document,
href: relativeOrAbsolute(hit.document.href, pathname),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import SideBar from '@node-core/ui-components/Containers/Sidebar';

import styles from './index.module.css';
import { relativeOrAbsolute } from '../../utils/relativeOrAbsolute.mjs';
import { renderLabel } from '../../utils/renderLabel.jsx';

import { project, version, versions, pages } from '#theme/config';

Expand Down Expand Up @@ -37,7 +38,7 @@ export default ({ metadata }) => {
}));

const items = pages.map(([heading, path]) => ({
label: heading,
label: renderLabel(heading),
link:
metadata.path === path
? `${metadata.basename}.html`
Expand Down
26 changes: 26 additions & 0 deletions packages/core/src/generators/web/ui/utils/renderLabel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Renders a raw Markdown heading. We intentionally
* only account for backticks, since running a full
* markdown parse is much slower (and there's never
* a case where'd have extremely complex markdown
* within a sidebar like this)
*
* @param {string} label - Raw heading text.
* @returns {import('preact').ComponentChildren}
*/
export const renderLabel = label => {
const segments = label.split('`');

if (segments.length === 1) {
return label;
}

// Odd-indexed segments sat between a pair of backticks.
return segments.map((segment, index) =>
index % 2 ? (
<code key={`code:${segment}`}>{segment}</code>
) : (
<span key={`text:${segment}`}>{segment}</span>
)
);
};
28 changes: 1 addition & 27 deletions www/theme/SideBar.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import SideBar from '@node-core/ui-components/Containers/Sidebar';

import { relativeOrAbsolute } from '../../packages/core/src/generators/web/ui/utils/relativeOrAbsolute.mjs';
import { renderLabel } from '../../packages/core/src/generators/web/ui/utils/renderLabel.jsx';

import { pages } from '#theme/config';

Expand All @@ -22,33 +23,6 @@ const GUIDE_ORDER = [

const isGenerator = ([, path]) => path.startsWith('/generators/');

/**
* Page labels are raw heading text, so a heading like `` ## `web` Generator ``
* arrives with its backticks intact. The sidebar renders the label verbatim,
* showing the literal backticks. Split on backtick pairs and wrap the enclosed
* spans in `<code>` so they render as inline code; labels without backticks pass
* through as a plain string.
*
* @param {string} label
* @returns {import('react').ReactNode}
*/
const renderLabel = label => {
const segments = label.split('`');

if (segments.length === 1) {
return label;
}

// Odd-indexed segments sat between a pair of backticks.
return segments.map((segment, index) =>
index % 2 ? (
<code key={`code:${segment}`}>{segment}</code>
) : (
<span key={`text:${segment}`}>{segment}</span>
)
);
};

/**
* Orders the narrative pages by `GUIDE_ORDER`, leaving anything unlisted at the
* end in the alphabetical order `jsx-ast` already produced.
Expand Down
Loading