From d0a3309774c73d82f2ef3f697a57bbed10b2e006 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Sun, 20 Sep 2026 21:36:19 +0530 Subject: [PATCH 01/11] feat: add a dark theme - dark token set in goravel.css, following the system setting, with a toggle in the nav bar - dark palette for code highlighting, and code grounds darker than the page - warning and error code lines, callout code, keys and badges use the theme tokens - homepage grid markers follow the real column count - file name headers also accept dotfiles such as .env --- .vitepress/config/community.ts | 2 +- .vitepress/config/goravel-code.ts | 45 +++++---- .vitepress/config/shared.ts | 5 +- .vitepress/theme/components/NavBar.vue | 38 +++++++- .vitepress/theme/components/NotFound.vue | 2 +- .vitepress/theme/goravel.css | 111 +++++++++++++++++++++-- .vitepress/theme/home/GoravelMark.vue | 11 ++- .vitepress/theme/home/HomeFacades.vue | 24 ++++- .vitepress/theme/home/HomeLite.vue | 2 +- .vitepress/theme/home/home.css | 6 +- .vitepress/theme/i18n.ts | 1 + .vitepress/theme/index.ts | 4 +- .vitepress/theme/shell.css | 2 +- 13 files changed, 205 insertions(+), 48 deletions(-) diff --git a/.vitepress/config/community.ts b/.vitepress/config/community.ts index 1f530fe3a..bad997cc5 100644 --- a/.vitepress/config/community.ts +++ b/.vitepress/config/community.ts @@ -26,7 +26,7 @@ export function community(words: Words, prefix = '', videos: 'youtube' | 'bilibi text: words.connect, items: [ row('Discord', LINKS.discord, 'icon-[simple-icons--discord]', '#5865F2'), - row('X', LINKS.x, 'icon-[simple-icons--x]', '#000000') + row('X', LINKS.x, 'icon-[simple-icons--x]') ] }, { diff --git a/.vitepress/config/goravel-code.ts b/.vitepress/config/goravel-code.ts index 051585ec7..1a2851bdd 100644 --- a/.vitepress/config/goravel-code.ts +++ b/.vitepress/config/goravel-code.ts @@ -1,26 +1,31 @@ import type { ThemeOptions } from 'vitepress' -const INK = '#101820' -const GREY = '#68747d' -const CYAN = '#0077b3' -const RED = '#b02b2b' +interface Palette { + ink: string + grey: string + cyan: string + red: string +} + +const LIGHT: Palette = { ink: '#101820', grey: '#636f78', cyan: '#0074ae', red: '#b02b2b' } +const DARK: Palette = { ink: '#e6edf2', grey: '#8a99a5', cyan: '#5cc4f7', red: '#f59393' } const rule = (scope: string[], foreground: string) => ({ scope, settings: { foreground } }) -export const goravelCode: ThemeOptions = { - name: 'goravel', - type: 'light', +const theme = (type: 'light' | 'dark', { ink, grey, cyan, red }: Palette) => ({ + name: `goravel-${type}`, + type, // transparent, so a callout's lighter ground shows through - colors: { 'editor.background': '#00000000', 'editor.foreground': INK }, + colors: { 'editor.background': '#00000000', 'editor.foreground': ink }, tokenColors: [ - { settings: { foreground: INK } }, - rule(['comment', 'punctuation.definition.comment', 'string.comment'], GREY), - rule(['keyword', 'storage', 'constant.language', 'variable.language'], INK), - rule(['entity.name.function', 'support.function', 'entity.name.command'], CYAN), - rule(['string', 'constant.numeric', 'constant.character'], GREY), - rule(['punctuation', 'keyword.operator', 'meta.brace'], GREY), - rule(['markup.inserted', 'meta.diff.header.to-file'], CYAN), - rule(['markup.deleted', 'meta.diff.header.from-file'], RED), + { settings: { foreground: ink } }, + rule(['comment', 'punctuation.definition.comment', 'string.comment'], grey), + rule(['keyword', 'storage', 'constant.language', 'variable.language'], ink), + rule(['entity.name.function', 'support.function', 'entity.name.command'], cyan), + rule(['string', 'constant.numeric', 'constant.character'], grey), + rule(['punctuation', 'keyword.operator', 'meta.brace'], grey), + rule(['markup.inserted', 'meta.diff.header.to-file'], cyan), + rule(['markup.deleted', 'meta.diff.header.from-file'], red), // narrower scopes that override the grey and cyan rules above rule( [ @@ -28,9 +33,11 @@ export const goravelCode: ThemeOptions = { 'support.type', 'support.class', 'storage.type.numeric.go', 'storage.type.string.go', 'storage.type.boolean.go', 'storage.type.byte.go', 'storage.type.error.go', 'variable', 'meta.definition.variable', 'punctuation.definition.variable.php', - 'string.unquoted.argument.shell', 'constant.other.option' + 'string.unquoted.argument.shell', 'constant.other.option', 'entity.name.tag.yaml' ], - INK + ink ) ] -} +}) + +export const goravelCode: ThemeOptions = { light: theme('light', LIGHT), dark: theme('dark', DARK) } diff --git a/.vitepress/config/shared.ts b/.vitepress/config/shared.ts index bf48bcf00..9162ae580 100644 --- a/.vitepress/config/shared.ts +++ b/.vitepress/config/shared.ts @@ -40,7 +40,7 @@ function liftFileNames(md: MarkdownRenderer) { const fence = md.renderer.rules.fence! md.renderer.rules.fence = (tokens, idx, options, env, self) => { const token = tokens[idx] - const path = token.content.match(/^(?:\/\/|#|--) ?([\w.@-]+(?:\/[\w.@-]+)*\.\w+)\n/) + const path = token.content.match(/^(?:\/\/|#|--) ?((?:[\w.@-]+\/)*[\w.@-]*\.\w+)\n/) // line highlights like {2,4} count from the first line, so a block using them keeps it if (!path || /\{[\d,-]+\}/.test(token.info)) return fence(tokens, idx, options, env, self) token.content = token.content.slice(path[0].length) @@ -67,8 +67,7 @@ export const shared = defineConfig({ 'en/:rest*': ':rest*' }, - // there is no dark design - appearance: false, + appearance: true, lastUpdated: true, cleanUrls: false, metaChunk: true, diff --git a/.vitepress/theme/components/NavBar.vue b/.vitepress/theme/components/NavBar.vue index 69fee9b93..870698e0d 100644 --- a/.vitepress/theme/components/NavBar.vue +++ b/.vitepress/theme/components/NavBar.vue @@ -15,9 +15,9 @@ import SelectMenu from './SelectMenu.vue' defineProps<{ isScreenOpen: boolean }>() defineEmits<{ (e: 'toggle-screen'): void }>() -const { theme, localeIndex } = useData() +const { theme, localeIndex, isDark } = useData() const { hasSidebar } = useSidebar() -const { languages, currentLang } = useI18n() +const { tr, languages, currentLang } = useI18n() const route = useRoute() const home = computed(() => (localeIndex.value === 'root' ? '/' : `/${localeIndex.value}/`)) @@ -67,6 +67,9 @@ const onFocusOut = useDismiss(navRoot, () => (menuOpen.value = false))
+
@@ -300,6 +303,37 @@ const onFocusOut = useDismiss(navRoot, () => (menuOpen.value = false)) margin-left: 20px; } +.theme { + display: grid; + place-items: center; + width: var(--g-control); + height: var(--g-control); + margin: 0 -8px 0 -6px; + border-radius: 2px; + color: var(--g-grey); +} + +.theme:hover { + color: var(--g-ink); +} + +.theme span { + width: 17px; + height: 17px; +} + +.moon { + display: none; +} + +:global(.dark .bar .theme .sun) { + display: none; +} + +:global(.dark .bar .theme .moon) { + display: block; +} + .social { margin-left: 2px; } diff --git a/.vitepress/theme/components/NotFound.vue b/.vitepress/theme/components/NotFound.vue index 651989c67..fa34688d6 100644 --- a/.vitepress/theme/components/NotFound.vue +++ b/.vitepress/theme/components/NotFound.vue @@ -71,7 +71,7 @@ const suggestions = [ background: var(--g-cyan); font-size: 15px; font-weight: 600; - color: var(--g-white); + color: var(--g-on-accent); } .home:hover { diff --git a/.vitepress/theme/goravel.css b/.vitepress/theme/goravel.css index ddc8db6ea..91323e7af 100644 --- a/.vitepress/theme/goravel.css +++ b/.vitepress/theme/goravel.css @@ -1,14 +1,21 @@ :root { --g-white: #ffffff; --g-soft: #f7f9fa; + --g-code: #f7f9fa; --g-hover: #eef2f5; --g-ink: #101820; --g-grey: #68747d; --g-line: #e5eaed; + --g-control-line: #e5eaed; --g-cyan: #009fe8; - --g-cyan-text: #0077b3; + --g-cyan-text: #0074ae; --g-cyan-tint: #ebf7fd; --g-construct: rgba(104, 116, 125, 0.4); + --g-on-accent: #ffffff; + --g-link-rule: rgba(0, 116, 174, 0.32); + --g-glass: rgba(255, 255, 255, 0.66); + --g-glass-strong: rgba(255, 255, 255, 0.72); + --g-scrim: rgba(16, 24, 32, 0.32); --g-amber: #e08700; --g-amber-text: #8f5a00; @@ -48,6 +55,8 @@ --vp-code-block-color: var(--g-ink); --vp-code-line-highlight-color: var(--g-cyan-tint); + --vp-code-line-warning-color: var(--g-amber-tint); + --vp-code-line-error-color: var(--g-red-tint); --vp-code-line-number-color: var(--g-grey); --vp-code-copy-code-hover-bg: var(--g-white); --vp-code-tab-divider: var(--g-line); @@ -59,6 +68,32 @@ --vp-sidebar-width: 272px; } +.dark { + color-scheme: dark; + + --g-white: #0d141b; + --g-soft: #121b24; + --g-code: #0a0f15; + --g-hover: #19242f; + --g-ink: #e6edf2; + --g-grey: #8a99a5; + --g-line: #222f3b; + --g-control-line: #46576a; + --g-cyan-text: #5cc4f7; + --g-cyan-tint: #0f2b3d; + --g-construct: rgba(138, 153, 165, 0.34); + --g-on-accent: #0b1218; + --g-link-rule: rgba(92, 196, 247, 0.4); + --g-glass: rgba(13, 20, 27, 0.5); + --g-glass-strong: rgba(13, 20, 27, 0.6); + --g-scrim: rgba(0, 0, 0, 0.6); + + --g-amber-text: #f2b765; + --g-amber-tint: #2a2113; + --g-red-text: #f59393; + --g-red-tint: #2e181a; +} + /* ------------------------------------------------------------------ labels and marks */ .g-label, .vp-doc .custom-block .custom-block-title, @@ -200,7 +235,7 @@ .vp-doc a { font-weight: 400; - text-decoration: underline 1px rgba(0, 119, 179, 0.32); + text-decoration: underline 1px var(--g-link-rule); text-underline-offset: 3px; } @@ -211,7 +246,7 @@ .vp-doc :not(pre) > code { padding: 2px 6px; border-radius: 0; - background: var(--g-soft); + background: var(--g-code); font-size: 14px; color: var(--g-ink); } @@ -246,7 +281,7 @@ /* ------------------------------------------------------------------ code */ /* The ground and rules are painted past the box, so the text keeps the article's edge. */ .vp-doc { - --g-code-ground: var(--g-soft); + --g-code-ground: var(--g-code); --g-code-left: var(--g-left); --g-code-right: var(--g-right); } @@ -521,7 +556,7 @@ } .vp-doc .custom-block { - --g-code-ground: rgba(255, 255, 255, 0.66); + --g-code-ground: var(--g-glass); padding-top: 16px; padding-bottom: 18px; border-radius: 0; @@ -600,17 +635,31 @@ } .vp-doc .custom-block code { - background: rgba(255, 255, 255, 0.72); + background: var(--g-glass-strong); } .vp-doc .custom-block.info code { - background: var(--g-soft); + background: var(--g-code); } .vp-doc .custom-block div[class*='language-'], .vp-doc .custom-block .vp-code-group { margin-top: 14px; margin-bottom: 14px; + border-radius: 0; +} + +.vp-doc .custom-block .vp-code-group .tabs { + border-radius: 0; +} + +.vp-doc .custom-block:has(> div[class*='language-']:last-child, > .vp-code-group:last-child) { + padding-bottom: 0; +} + +.vp-doc .custom-block > div[class*='language-']:last-child, +.vp-doc .custom-block > .vp-code-group:last-child { + margin-bottom: 0; } .vp-doc .custom-block.details { @@ -730,6 +779,7 @@ } .vp-doc th { + overflow-wrap: normal; padding: 0 24px 10px 0; background: transparent; text-align: left; @@ -759,7 +809,8 @@ overflow-wrap: anywhere; } -.vp-doc td:first-child > a { +.vp-doc td:first-child > a, +.vp-doc td:first-child > code { overflow-wrap: normal; } @@ -861,7 +912,7 @@ width: 15px; height: 15px; margin: 7px 0 0; - border: 1.5px solid var(--g-line); + border: 1.5px solid var(--g-control-line); border-radius: 2px; background: var(--g-white) center / 11px no-repeat; } @@ -977,3 +1028,45 @@ :lang(zh) .g-home .g-h2 { line-height: 1.25; } + +/* ------------------------------------------------------------------ keys and badges */ +.vp-doc kbd { + padding: 1px 6px; + border: 1px solid var(--g-control-line); + border-bottom-width: 2px; + border-radius: 2px; + background: var(--g-code); + font-family: var(--vp-font-family-mono); + font-size: 13px; +} + +.vp-doc .VPBadge { + padding: 0 6px; + border: 0; + border-radius: 2px; + font-family: var(--vp-font-family-mono); + font-size: 11px; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.vp-doc .VPBadge.info { + background: var(--g-hover); + color: var(--g-ink); +} + +.vp-doc .VPBadge.tip { + background: var(--g-cyan-tint); + color: var(--g-cyan-text); +} + +.vp-doc .VPBadge.warning { + background: var(--g-amber-tint); + color: var(--g-amber-text); +} + +.vp-doc .VPBadge.danger { + background: var(--g-red-tint); + color: var(--g-red-text); +} diff --git a/.vitepress/theme/home/GoravelMark.vue b/.vitepress/theme/home/GoravelMark.vue index 8b6dddfa7..831977af8 100644 --- a/.vitepress/theme/home/GoravelMark.vue +++ b/.vitepress/theme/home/GoravelMark.vue @@ -1,5 +1,6 @@ diff --git a/.vitepress/theme/home/HomeOpenSource.vue b/.vitepress/theme/home/HomeOpenSource.vue index cbfcacc95..1ec32d445 100644 --- a/.vitepress/theme/home/HomeOpenSource.vue +++ b/.vitepress/theme/home/HomeOpenSource.vue @@ -511,8 +511,14 @@ const onFocusOut = useDismiss(joins, () => (qr.value = null)) content: none; } - .os-row.is-join .os-cell:nth-child(n + 3) { - border-top: 1px solid var(--g-line); + .os-row.is-join .os-cell:nth-child(3)::after { + content: ''; + position: absolute; + top: 0; + left: calc(var(--g-bleed-left) * -1); + right: calc(-100% - var(--g-bleed-right)); + height: 1px; + background: var(--g-line); } From 43195a11307e35cf079f1fdc681cef7a29271b47 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Mon, 21 Sep 2026 00:20:26 +0530 Subject: [PATCH 06/11] refactor: replace the method index with a two level outline - the outline lists ## and ### headings, the way the Laravel docs do, at every screen width - remove the method index component, its detection rules, its strings and the methods heading attribute - the outline title stays pinned while a long list scrolls - the writing guide no longer teaches the method index --- .vitepress/config/en.ts | 1 + .vitepress/config/zh_CN.ts | 1 + .vitepress/theme/Layout.vue | 2 - .vitepress/theme/components/MethodIndex.vue | 199 -------------------- .vitepress/theme/goravel.css | 2 - .vitepress/theme/i18n.ts | 3 - .vitepress/theme/shell.css | 20 +- en/prologue/writing-docs.md | 66 +------ zh_CN/prologue/writing-docs.md | 66 +------ 9 files changed, 15 insertions(+), 345 deletions(-) delete mode 100644 .vitepress/theme/components/MethodIndex.vue diff --git a/.vitepress/config/en.ts b/.vitepress/config/en.ts index f92d6a907..575fea8b7 100644 --- a/.vitepress/config/en.ts +++ b/.vitepress/config/en.ts @@ -89,6 +89,7 @@ export const config = defineConfig({ next: 'Next page' }, outline: { + level: [2, 3], label: 'On this page' }, lastUpdated: { diff --git a/.vitepress/config/zh_CN.ts b/.vitepress/config/zh_CN.ts index 5180fe9a1..07150e006 100644 --- a/.vitepress/config/zh_CN.ts +++ b/.vitepress/config/zh_CN.ts @@ -74,6 +74,7 @@ export const config = defineConfig({ next: "下一页" }, outline: { + level: [2, 3], label: "页面导航" }, lastUpdated: { diff --git a/.vitepress/theme/Layout.vue b/.vitepress/theme/Layout.vue index 5ee90bc50..d4be1a10f 100644 --- a/.vitepress/theme/Layout.vue +++ b/.vitepress/theme/Layout.vue @@ -1,7 +1,6 @@ - - - - diff --git a/.vitepress/theme/goravel.css b/.vitepress/theme/goravel.css index 91323e7af..036bc1564 100644 --- a/.vitepress/theme/goravel.css +++ b/.vitepress/theme/goravel.css @@ -100,7 +100,6 @@ .vp-doc th, .vp-doc .footnotes::before, .VPDocAsideOutline .outline-title, -.goravel-methods .outline-title, .VPNavScreenMenuGroupSection .title { font-family: var(--vp-font-family-mono); font-size: 12px; @@ -120,7 +119,6 @@ .g-diamond::before, .vp-doc .custom-block .custom-block-title::before, .VPDocAsideOutline .outline-title::before, -.goravel-methods .outline-title::before, .VPNavScreenMenuGroupSection .title::before, .VPSidebarItem.level-1.is-active > .item::before, .VPDocOutlineItem .outline-link.active::before { diff --git a/.vitepress/theme/i18n.ts b/.vitepress/theme/i18n.ts index 6027cd094..69d46d099 100644 --- a/.vitepress/theme/i18n.ts +++ b/.vitepress/theme/i18n.ts @@ -59,9 +59,6 @@ const zh_CN: Dict = { 'Try these': '试试这些', 'Upgrading To v1.18 From v1.17': '从 v1.17 升级到 v1.18', 'Excellent Packages': '优秀扩展包', - Methods: '方法', - Filter: '筛选', - 'Filter methods': '筛选方法', 'Familiar facades, familiar method names': '熟悉的门面,熟悉的方法名', 'The same file layout, artisan included': '相同的目录结构,自带 artisan', 'Laravel, line for line.': '与 Laravel 逐行对应。', diff --git a/.vitepress/theme/shell.css b/.vitepress/theme/shell.css index bb18b389b..aa876e689 100644 --- a/.vitepress/theme/shell.css +++ b/.vitepress/theme/shell.css @@ -259,21 +259,14 @@ min-height: 0; } - .VPDoc .VPDocAside:has(.goravel-methods) .VPDocAsideOutline.has-outline { + .VPDoc .VPDocAsideOutline.has-outline, + .VPDoc .VPDocAsideOutline .content { display: flex; - flex: 0 1 auto; flex-direction: column; min-height: 0; - max-height: 46%; } - .VPDoc .VPDocAside:has(.goravel-methods) .VPDocAsideOutline .content { - display: flex; - flex-direction: column; - min-height: 0; - } - - .VPDoc .VPDocAside:has(.goravel-methods) .VPDocAsideOutline .content > .VPDocOutlineItem { + .VPDoc .VPDocAsideOutline .content > .VPDocOutlineItem { min-height: 0; overflow-y: auto; scrollbar-width: thin; @@ -296,9 +289,7 @@ display: none; } -/* the method index's heading is drawn the same way */ -.VPDocAsideOutline .outline-title, -.goravel-methods .outline-title { +.VPDocAsideOutline .outline-title { display: flex; align-items: center; gap: 8px; @@ -307,8 +298,7 @@ border-bottom: 1px solid var(--g-line); } -.VPDocAsideOutline .outline-title::before, -.goravel-methods .outline-title::before { +.VPDocAsideOutline .outline-title::before { background: var(--g-construct); } diff --git a/en/prologue/writing-docs.md b/en/prologue/writing-docs.md index 8ce6c21e8..142e09564 100644 --- a/en/prologue/writing-docs.md +++ b/en/prologue/writing-docs.md @@ -22,7 +22,7 @@ The reader has a task and wants to get back to their code. A good page gets them - **It answers "how do I do X".** Organise a page by what the reader wants to do, not by how the package is built inside. - **It can be pasted.** Every example runs when it is copied into a fresh project. -- **It can be scanned.** The reader finds the section from the outline, and the method from the method index, without reading from the top. +- **It can be scanned.** The outline on the right lists every `##` and `###` heading, so the reader finds the section without reading from the top. - **It is true.** Every method, option and config key exists in the framework today. - **It feels familiar.** Goravel follows Laravel. Use the names a Laravel developer already knows, and say where Goravel differs. @@ -33,7 +33,7 @@ Order the sections the way a reader meets them: introduction, installation or co One section covers one task. Write it in this order. 1. **A heading that names the task.** "Storing Items For A Limited Time", not "Put". The reader searches for what they want to do. Reference pages with one short section per method are the exception, and there the method is the heading. -2. **One sentence that names the method** in inline code. It tells the reader what to look for in the example, and it registers the method in the page's method index. +2. **One sentence that names the method** in inline code. It tells the reader what to look for in the example. 3. **A complete example** that starts with its file path. 4. **What comes back, and what can go wrong**, when the reader could be surprised. 5. **A callout, only when needed.** One gotcha that would cost the reader an hour belongs in a warning. Most sections need none. @@ -86,64 +86,6 @@ Examples are the part of the docs people actually use. - **One idea per block.** Two alternatives go in a code group, not in one long block with comments between them. - **Always name the language.** A block without one has no highlighting. Use `go`, `shell`, `json`, `yaml`, `sql`, `dockerfile`, `html`, `php`, `diff`, or `text` for directory trees and plain output. -## Make Every Method Findable - -A page that documents six or more methods gets a **Methods** list under the outline, with a filter on long pages. Readers use it to jump straight to a method, so a method that is missing from it is a method people will not find. You never write the list. The sections below register their methods in the three ways a page can, and together they produce the list on this page. - -After writing a page, open it and read its Methods list. If a method is missing, or a wrong name is listed, say it on the heading. - -### Say it on the heading - -This is explicit and always wins. The attribute is invisible, does not change the anchor, and is copied as it is into the Chinese page, because method names are the same in every language. A section with `methods` lists exactly those names, and `{methods=""}` lists nothing. It combines with a fixed anchor: `{#input methods="Input InputInt"}`. - -````md demo -### Retrieving Items {methods="Get GetString Pull"} - -```go -value := facades.Cache().Get("user", "default") -name := facades.Cache().GetString("name") -token := facades.Cache().Pull("token") -``` -```` - -### Name it in the text - -With no attribute, a method is listed when the text names it in inline code and the code of the same section calls it. The link lands on that sentence. - -````md demo -### Checking And Removing Items - -You may use the `Has` method to check that an item exists, the `Forget` method to remove one item, and the `Flush` method to remove them all: - -```go -if facades.Cache().Has("user") { - facades.Cache().Forget("user") -} - -facades.Cache().Flush() -``` -```` - -### Use the method as the heading - -On a reference page with one short section per method, the heading is the method: a name with an inner capital such as `WithSession`, names joined by ` / `, inline code such as `path.App()`, or one word that the code in its section calls. - -````md demo -#### Forever - -```go -facades.Cache().Forever("site", "goravel.dev") -``` - -#### Add - -```go -stored := facades.Cache().Add("user", "Goravel", 5*time.Minute) -``` -```` - -A long page can also open with a table. Every row with a method name in its first cell and a link to an anchor on the same page in its second is listed. - ## Callouts Use a callout for what the reader must not miss. Never write `Note:`, `Tip:` or `Attention:` as plain text. @@ -283,7 +225,7 @@ A badge takes the type `info`, `tip`, `warning` or `danger`. ## Two Languages - Every page exists in `en/` and `zh_CN/` with the same headings in the same order and the same code blocks. Change both in the same PR. -- Translate the text. Keep code, method names, file paths, config keys and the `methods` attribute as they are. +- Translate the text. Keep code, method names, file paths and config keys as they are. - Link to a page with a relative path to the `.md` file, such as `../architecture-concepts/facades.md#install-uninstall-facades`. - An anchor is the heading in lower case with hyphens for spaces. A Chinese page has Chinese anchors, so never copy an English anchor into a Chinese page. Give a heading a fixed anchor when it may be renamed: `### Update columns {#update-columns}`. - A new page goes into the sidebar in both `.vitepress/config/en.ts` and `.vitepress/config/zh_CN.ts`. @@ -301,6 +243,6 @@ Switch the theme with the button in the top bar and check your page in both. - [ ] Each section names its task in the heading and its method in the first sentence - [ ] Every example is complete, starts with its file path, and uses methods that exist in the framework - [ ] Every code block has a language, and shell comments use `#` -- [ ] The Methods list on the page shows every method the page documents +- [ ] The outline reads as a list of tasks, because every `##` and `###` heading is in it - [ ] English and Chinese have the same headings and code blocks, and Chinese pages use Chinese anchors - [ ] You looked at the page in both the light and the dark theme diff --git a/zh_CN/prologue/writing-docs.md b/zh_CN/prologue/writing-docs.md index 9809d96de..81c96960b 100644 --- a/zh_CN/prologue/writing-docs.md +++ b/zh_CN/prologue/writing-docs.md @@ -22,7 +22,7 @@ head: - **回答“我该如何做 X”。** 按读者想做的事情组织页面,而不是按包的内部结构组织。 - **可以直接粘贴。** 每个示例复制到新项目中都能运行。 -- **便于浏览。** 读者通过大纲找到章节,通过方法索引找到方法,不必从头读起。 +- **便于浏览。** 右侧大纲会列出每个 `##` 和 `###` 标题,读者不必从头读起就能找到章节。 - **内容真实。** 每个方法、选项和配置项在当前框架中都真实存在。 - **令人熟悉。** Goravel 遵循 Laravel 的风格。 使用 Laravel 开发者熟悉的名称,并说明 Goravel 的不同之处。 @@ -33,7 +33,7 @@ head: 一个章节只讲一个任务,请按以下顺序编写。 1. **用标题说明任务。** 写“在限定时间内存储数据”,而不是“Put”。 读者搜索的是他们想做的事。 每个方法一小节的参考类页面是例外,此时标题就是方法名。 -2. **用一句话写出方法名**,并使用行内代码。 它告诉读者在示例中关注什么,同时把方法注册到页面的方法索引中。 +2. **用一句话写出方法名**,并使用行内代码。 它告诉读者在示例中关注什么。 3. **一个完整的示例**,以文件路径开头。 4. **返回什么,可能出什么错**,当读者可能感到意外时说明。 5. **只在需要时使用提示块。** 一个可能让读者耗费一小时的坑,应该放进 warning。 大多数章节不需要提示块。 @@ -86,64 +86,6 @@ func (r *UserController) Show(ctx http.Context) http.Response { - **一个代码块只表达一件事。** 两种替代写法请放进代码组,不要写成一个中间夹着注释的长代码块。 - **始终标明语言。** 没有标明语言的代码块不会高亮。 可使用 `go`、`shell`、`json`、`yaml`、`sql`、`dockerfile`、`html`、`php`、`diff`,目录树和纯文本输出使用 `text`。 -## 让每个方法都能被找到 - -介绍了六个或更多方法的页面,会在大纲下方显示 **方法** 列表,较长的页面还会显示筛选框。 读者通过它直接跳到某个方法,所以没有出现在列表中的方法,就是读者找不到的方法。 这个列表不需要手写。 下面的章节分别用页面支持的三种方式注册方法,它们共同生成了本页的列表。 - -写完页面后,请打开页面查看方法列表。 如果缺少某个方法,或列出了错误的名称,请在标题上声明。 - -### 在标题上声明 - -这是显式的方式,并且始终优先。 该属性在页面上不可见,不会改变锚点,并且原样复制到中文页面,因为方法名在所有语言中都相同。 带有 `methods` 的章节只会列出这些名称,`{methods=""}` 则什么都不列出。 它可以与固定锚点一起使用:`{#input methods="Input InputInt"}`。 - -````md demo -### 获取数据 {methods="Get GetString Pull"} - -```go -value := facades.Cache().Get("user", "default") -name := facades.Cache().GetString("name") -token := facades.Cache().Pull("token") -``` -```` - -### 在正文中写出方法名 - -没有该属性时,如果正文用行内代码写出了方法名,并且同一章节的代码调用了它,该方法就会被列出。 链接会定位到这句话。 - -````md demo -### 检查和删除数据 - -你可以使用 `Has` 方法检查数据是否存在,使用 `Forget` 方法删除一条数据,使用 `Flush` 方法删除全部数据: - -```go -if facades.Cache().Has("user") { - facades.Cache().Forget("user") -} - -facades.Cache().Flush() -``` -```` - -### 使用方法名作为标题 - -在每个方法一小节的参考类页面中,标题就是方法名:包含内部大写字母的名称(如 `WithSession`)、用 ` / ` 连接的多个名称、行内代码(如 `path.App()`),或者被本章节代码调用的单个单词。 - -````md demo -#### Forever - -```go -facades.Cache().Forever("site", "goravel.dev") -``` - -#### Add - -```go -stored := facades.Cache().Add("user", "Goravel", 5*time.Minute) -``` -```` - -较长的页面也可以在开头放一个表格。 第一格是方法名、第二格链接到本页锚点的每一行都会被列出。 - ## 提示块 需要读者特别留意的内容请使用提示块。 不要用纯文本写 `注意:`、`提示:` 或 `警告:`。 @@ -283,7 +225,7 @@ go run . artisan key:generate ## 两种语言 - 每个页面在 `en/` 和 `zh_CN/` 中都存在,标题及其顺序、代码块保持一致。 请在同一个 PR 中修改两种语言。 -- 翻译正文。 代码、方法名、文件路径、配置项和 `methods` 属性保持原样。 +- 翻译正文。 代码、方法名、文件路径和配置项保持原样。 - 使用指向 `.md` 文件的相对路径链接页面,例如 `../architecture-concepts/facades.md#安装-卸载-facades`。 - 锚点是标题转为小写、空格替换为连字符后的结果。 中文页面使用中文锚点,不要把英文锚点复制到中文页面。 如果标题以后可能改名,请为它指定固定的锚点:`### 更新字段 {#update-columns}`。 - 新页面需要同时加入 `.vitepress/config/en.ts` 和 `.vitepress/config/zh_CN.ts` 的侧边栏。 @@ -301,6 +243,6 @@ go run . artisan key:generate - [ ] 每个章节的标题说明了任务,第一句话写出了方法名 - [ ] 每个示例都完整,以文件路径开头,并且使用框架中真实存在的方法 - [ ] 每个代码块都标明了语言,shell 注释使用 `#` -- [ ] 页面上的方法列表包含了本页介绍的每个方法 +- [ ] 大纲读起来是一份任务列表,因为每个 `##` 和 `###` 标题都会出现在其中 - [ ] 中英文页面的标题和代码块一致,中文页面使用中文锚点 - [ ] 你已在浅色和深色两种主题下查看过页面 From 957ec12ffa1f2b70808bf8eaf2bd659d3c7eddaa Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Mon, 21 Sep 2026 00:32:41 +0530 Subject: [PATCH 07/11] fix: outline marker, nav rule and dropdown border - the active outline marker sits inside its link, so the link no longer crops it - remove the spacing left under the outline by the old Methods list - scope the Brand styles, which leaked an 8px margin onto the nav brand and kept the nav rule from reaching the sidebar border - align the On this page label with the content - stop Tailwind generating its outline utility, which drew a border on the dropdown list --- .vitepress/theme/components/Brand.vue | 2 +- .vitepress/theme/shell.css | 10 +++++++--- .vitepress/theme/styles.css | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.vitepress/theme/components/Brand.vue b/.vitepress/theme/components/Brand.vue index e9baa8e73..57302284c 100644 --- a/.vitepress/theme/components/Brand.vue +++ b/.vitepress/theme/components/Brand.vue @@ -10,7 +10,7 @@ defineProps<{ laravel?: boolean; goravel?: boolean; no?: boolean }>() -