Skip to content
This repository was archived by the owner on Oct 10, 2025. It is now read-only.

Commit d4e12ec

Browse files
authored
Doc aesthetics wishlist change (#613)
- [x] Font change - [x] Light dark toggle switch (removed auto) - [x] SQL to cypher - [x] different highlight code colour - [x] syntax highlights for cypher - [x] tab synchronization - [x] vite-like sidebar - [x] wide content panel ## Nits (comments below) - [x] Improve green shade for callout boxes (red, yellow, purple all look nice in light and dark mode) - [x] Table borders - [x] Space between bullet points: increase it slightly (see the wasm page for an example of how crowded it looks right now) - [x] $ not selected when copying or selecting # Contributor agreement - [x] I have read and agree to the [Contributor Agreement](https://github.com/kuzudb/kuzu-docs/blob/main/CLA.md).
1 parent 25e2439 commit d4e12ec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1248
-294
lines changed

astro.config.mjs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import sitemap from "@astrojs/sitemap";
33
import starlight from "@astrojs/starlight";
44
import starlightLinksValidator from "starlight-links-validator";
55
import fs from "fs";
6+
import path from 'path'
7+
8+
const cypherGrammar = JSON.parse(
9+
fs.readFileSync(path.resolve('./src/styles/cypher.tmLanguage.json'), 'utf8')
10+
)
611

712
const site = "https://docs.kuzudb.com";
813

@@ -32,7 +37,13 @@ export default defineConfig({
3237
baseUrl: 'https://github.com/kuzudb/kuzu-docs/edit/main',
3338
},
3439
customCss: ['./src/styles/custom.css'],
35-
expressiveCode: true,
40+
expressiveCode: {
41+
shiki: {
42+
langs: [
43+
{ name: 'cypher', ...cypherGrammar }
44+
],
45+
}
46+
},
3647
head: [
3748
// Basic OG tags
3849
{
@@ -81,11 +92,23 @@ export default defineConfig({
8192
tag: 'meta',
8293
attrs: { name: 'twitter:image', content: site + '/img/og.png' },
8394
},
95+
// Script
96+
{
97+
tag: "script",
98+
attrs: { src: "/reb2b.js", type: "text/javascript", async: true }
99+
},
100+
{
101+
tag: "script",
102+
attrs: { src: "/remove-prompt.js", type: "text/javascript" }
103+
},
104+
84105
],
85106
components: {
86107
Header: './src/components/overrides/Header.astro',
87108
Banner: './src/components/overrides/Banner.astro',
88109
ThemeSelect: './src/components/overrides/ThemeSelect.astro',
110+
PageFrame: './src/components/overrides/PageFrame.astro',
111+
TwoColumnContent: './src/components/overrides/TwoColumnContent.astro',
89112
},
90113
sidebar: [
91114
{

public/remove-prompt.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Strip leading "$ " prompts when copying from terminal code blocks
2+
(function () {
3+
function isTerminalLang(lang) {
4+
if (!lang) return false;
5+
const l = String(lang).toLowerCase();
6+
return [
7+
'bash',
8+
'sh',
9+
'zsh',
10+
'shell',
11+
'shellsession',
12+
'console',
13+
].includes(l);
14+
}
15+
16+
function detectLangFromPre(pre) {
17+
if (!pre) return null;
18+
const dataLang = pre.getAttribute('data-lang') || pre.dataset?.lang;
19+
if (dataLang) return dataLang;
20+
const wrapperWithData = pre.closest?.('[data-language]');
21+
if (wrapperWithData) return wrapperWithData.getAttribute('data-language');
22+
const code = pre.querySelector('code');
23+
const m = code?.className?.match(/language-([a-z0-9]+)/i);
24+
return m?.[1] || null;
25+
}
26+
27+
function stripPrompts(text) {
28+
if (!text) return text;
29+
// Remove "$ " at the start of each line (optionally preceded by whitespace)
30+
return text.replace(/^(?:\s*\$\s+)/gm, '');
31+
}
32+
33+
function isTerminalFigure(element) {
34+
const fig = element?.closest?.('figure');
35+
return !!fig && fig.classList.contains('is-terminal');
36+
}
37+
38+
// Intercept user-initiated copy events (manual selection)
39+
document.addEventListener('copy', function (e) {
40+
const selection = window.getSelection?.();
41+
if (!selection || selection.isCollapsed) return;
42+
const anchorNode = selection.anchorNode;
43+
const node = anchorNode && (anchorNode.nodeType === 1 ? anchorNode : anchorNode.parentElement);
44+
const pre = node?.closest?.('pre');
45+
if (!pre) return;
46+
const lang = detectLangFromPre(pre);
47+
const terminalContext = isTerminalLang(lang) || isTerminalFigure(pre);
48+
if (!terminalContext) return;
49+
const original = selection.toString();
50+
const cleaned = stripPrompts(original);
51+
if (cleaned !== original) {
52+
e.preventDefault();
53+
e.clipboardData.setData('text/plain', cleaned);
54+
}
55+
});
56+
57+
// Clean the data-code attribute before Expressive Code reads it (capture phase)
58+
document.addEventListener('click', function (e) {
59+
const target = e.target;
60+
const button = target && (target.closest ? target.closest('button[data-code]') : null);
61+
if (!button) return;
62+
if (!isTerminalFigure(button)) return;
63+
const current = button.getAttribute('data-code') || '';
64+
const cleaned = stripPrompts(current);
65+
if (cleaned !== current) {
66+
button.setAttribute('data-code', cleaned);
67+
}
68+
}, true);
69+
70+
// Also wrap navigator.clipboard.writeText to catch copy button behavior
71+
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
72+
const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
73+
navigator.clipboard.writeText = async function (text) {
74+
try {
75+
// Try to infer if the active element is a copy button near a terminal block
76+
const active = document.activeElement;
77+
let pre = active?.closest?.('pre');
78+
if (!pre) {
79+
// Look for a sibling/ancestor that contains the pre (Expressive Code wraps code)
80+
const container = active?.closest?.('figure, div, section, article');
81+
pre = container?.querySelector?.('pre') || null;
82+
}
83+
const lang = detectLangFromPre(pre);
84+
const terminalContext = isTerminalLang(lang) || isTerminalFigure(active);
85+
const cleaned = terminalContext ? stripPrompts(text) : text;
86+
return originalWriteText(cleaned);
87+
} catch (_) {
88+
// Fallback to original behavior on any error
89+
return originalWriteText(text);
90+
}
91+
};
92+
}
93+
})();
94+
95+

src/components/overrides/Header.astro

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ import SocialIcons from "@astrojs/starlight/components/SocialIcons.astro";
66
import ThemeSelect from "./ThemeSelect.astro";
77
import { Icon } from "@astrojs/starlight/components";
88
import Banner from './Banner.astro';
9+
import ThemeSelect from './ThemeSelect.astro';
910
---
1011
<div class="header sl-flex">
11-
12-
<div class="sl-flex">
13-
<SiteTitle {...Astro.props} />
12+
<!-- Site Title - shows when sidebars collapse -->
13+
<div class="header-site-title">
14+
<a href="/" class="site-title">
15+
<img src="/src/assets/logo/kuzu-logo-inverse.png" alt="Kuzu" class="site-logo" />
16+
</a>
1417
</div>
18+
1519
<div class="sl-flex">
1620
<Search {...Astro.props} />
1721
</div>
@@ -30,9 +34,7 @@ import Banner from './Banner.astro';
3034
<div class="sl-flex social-icons">
3135
<SocialIcons {...Astro.props} />
3236
</div>
33-
<div class="sl-flex toggle-wrapper">
34-
<ThemeSelect {...Astro.props} />
35-
</div>
37+
<ThemeSelect />
3638
</div>
3739
</div>
3840

@@ -43,11 +45,47 @@ import Banner from './Banner.astro';
4345
align-items: center;
4446
height: 100%;
4547
}
48+
49+
/* Header Site Title Styles */
50+
.header-site-title {
51+
display: none; /* Hidden by default */
52+
}
53+
54+
.site-title {
55+
display: flex;
56+
align-items: center;
57+
gap: 0.75rem;
58+
text-decoration: none;
59+
color: var(--sl-color-white);
60+
font-weight: 600;
61+
font-size: 1.25rem;
62+
transition: opacity 0.2s ease;
63+
}
64+
65+
.site-title:hover {
66+
opacity: 0.8;
67+
}
68+
69+
.site-logo {
70+
height: 2rem;
71+
width: auto;
72+
}
73+
74+
.site-name {
75+
font-size: 1.5rem;
76+
font-weight: 700;
77+
}
78+
4679
.external-link-header {
4780
font-weight: 400;
4881
font-size: 0.875rem;
4982
}
50-
.right-group,
83+
.right-group {
84+
gap: 1rem;
85+
align-items: center;
86+
justify-content: flex-end;
87+
margin-left: auto;
88+
}
5189
.social-icons {
5290
gap: 1rem;
5391
align-items: center;
@@ -80,7 +118,7 @@ import Banner from './Banner.astro';
80118
);
81119
display: grid;
82120
grid-template-columns:
83-
/* 1 (site title): runs up until the main content columns left edge or the width of the title, whichever is the largest */
121+
/* 1 (site title): runs up until the main content column's left edge or the width of the title, whichever is the largest */
84122
minmax(
85123
calc(
86124
var(--__sidebar-width) +
@@ -96,6 +134,31 @@ import Banner from './Banner.astro';
96134
}
97135
}
98136

137+
/* Show header site title when sidebars are collapsed */
138+
@media (max-width: 71.99rem) {
139+
.header-site-title {
140+
display: block;
141+
}
142+
}
143+
144+
/* Show header site title when only one sidebar is present */
145+
@media (min-width: 72rem) {
146+
/* Show when no left sidebar */
147+
:global(:root:not([data-has-sidebar])) .header-site-title {
148+
display: block;
149+
}
150+
151+
/* Show when no right sidebar (TOC) */
152+
:global(:root:not([data-has-toc])) .header-site-title {
153+
display: block;
154+
}
155+
156+
/* Hide when both sidebars are present */
157+
:global(:root[data-has-sidebar][data-has-toc]) .header-site-title {
158+
display: none;
159+
}
160+
}
161+
99162
.action {
100163
gap: 0.5em;
101164
align-items: center;

0 commit comments

Comments
 (0)