diff --git a/cmd/tools/vdoc-resources/arrow.svg b/cmd/tools/vdoc-resources/arrow.svg new file mode 100644 index 0000000000..2a0456fad1 --- /dev/null +++ b/cmd/tools/vdoc-resources/arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cmd/tools/vdoc-resources/dark.svg b/cmd/tools/vdoc-resources/dark.svg new file mode 100644 index 0000000000..2067d05b94 --- /dev/null +++ b/cmd/tools/vdoc-resources/dark.svg @@ -0,0 +1 @@ + diff --git a/cmd/tools/vdoc-resources/doc.css b/cmd/tools/vdoc-resources/doc.css new file mode 100644 index 0000000000..94847183aa --- /dev/null +++ b/cmd/tools/vdoc-resources/doc.css @@ -0,0 +1,443 @@ +:root { + --background-color: #fff; + --timestamp-color: #b8c2cc; + --link-color: #2779bd; + --font-color: #000; + --ref-symbol-color: #dae1e7; + --ref-symbol-hover-color: #b8c2cc; + --title-bottom-line-color: #f1f5f8; + --code-background-color: #edf2f7; + --code-signature-border-color: #a0aec0; + --menu-background-color: #4b6c88; + --menu-font-color: #fff; + --menu-indent-line-color: #ffffff66; + --menu-indent-line-active-color: #00000066; + --menu-scrollbar-color: #a0aec0; + --menu-toggle-icon-color: #fff; + --menu-toggle-icon-hover-color: #00000044; + --menu-search-background-color: #00000044; + --menu-search-font-color: #fff; + --toc-font-color: #2779bd; + --toc-indent-line-color: #dae1e7; +} +:root.dark .dark-icon { + display: none; +} +:root:not(.dark) .light-icon { + display: none; +} + +body { + font-family: 'Inter', sans-serif; + background-color: #fff; + background-color: var(--background-color); + color: #000; + color: var(--font-color); +} + +.dark body { + --background-color: #1a202c; + --timestamp-color: #b8c2cc; + --font-color: #fff; + --link-color: #90cdf4; + --ref-symbol-color: #2d3748; + --ref-symbol-hover-color: #4a5568; + --title-bottom-line-color: #2d3748; + --code-background-color: #2d3748; + --code-signature-border-color: #4a5568; + --menu-background-color: #2d3748; + --menu-font-color: #fff; + --menu-indent-line-color: #4a5568; + --menu-indent-line-active-color: #90cdf4; + --menu-scrollbar-color: #4a5568; + --menu-toggle-icon-color: #fff; + --menu-search-background-color: #4a5568; + --menu-search-font-color: #fff; + --toc-font-color: #90cdf4; + --toc-indent-line-color: #1a202c; +} + +/** Reset for menus */ +.doc-nav ul, .doc-toc ul { + list-style: none; + padding: 0; + margin: 0; +} + +.doc-nav { + position: fixed; + width: 100%; + left: 0; + right: 0; + top: 0; + display: flex; + background-color: #4b6c88; + background-color: var(--menu-background-color); + color: #fff; + color: var(--menu-font-color); + flex-direction: column; + overflow-y: auto; + height: 100vh; + z-index: 10; + scrollbar-width: thin; + scrollbar-color: #a0aec0 transparent; + scrollbar-color: var(--menu-scrollbar-color) transparent; +} +*::-webkit-scrollbar { + width: 8px; +} +*::-webkit-scrollbar-track { + background: transparent; +} +*::-webkit-scrollbar-thumb { + background-color: #a0aec0; + background-color: var(--menu-scrollbar-color); + border: 3px solid transparent; +} +.doc-nav li { + line-height: 1.8; + font-weight: 300; +} +.doc-nav .content.show { + display: flex; +} +.doc-nav .content.hidden { + display: none; +} +.doc-nav #toggle-menu { + cursor: pointer; + padding: 0.3rem; + fill: #fff; + fill: var(--menu-toggle-icon-color); +} +.doc-nav #toggle-menu:active { + background-color: #00000044; + background-color: var(--menu-toggle-icon-hover-color); + border-radius: 5rem; +} +.doc-nav > .heading-container { + position: relative; /* IE11 */ + position: sticky; + position: -webkit-sticky; + top: 0; + background-color: #4b6c88; + background-color: var(--menu-background-color); + z-index: 10; +} +.doc-nav > .heading-container > .heading { + display: flex; + padding: 0 2rem; + height: 56px; +} +.doc-nav > .heading-container > .heading > .module { + font-size: 1.6rem; + font-weight: 500; + margin: 0; +} +.doc-nav > .heading-container > .heading > .toggle-version-container { + display: flex; + align-items: center; +} +.doc-nav > .heading-container > .heading > .toggle-version-container > #dark-mode-toggle { + cursor: pointer; + fill: #fff; + display: flex; + visibility: hidden; +} +.doc-nav > .heading-container > .heading > .toggle-version-container > #dark-mode-toggle > svg { + width: 1.2rem; + height: 1.2rem; +} +.doc-nav > .heading-container > .heading > #search { + margin-top: 1rem; + border: none; + border-radius: 0.2rem; + padding: 0.5rem 1rem; + outline: none; + background-color: #00000044; + background-color: var(--menu-search-background-color); + color: #fff; + color: var(--menu-search-font-color); + margin-left: -0.6rem; + margin-right: -0.6rem; +} +.doc-nav > .heading-container > .heading > #search::placeholder { + color: #edf2f7; + text-transform: uppercase; + font-size: 12px; + font-weight: 600; +} +.doc-nav > .heading-container > .heading > #search:-ms-input-placeholder { + color: #edf2f7; + text-transform: uppercase; + font-size: 12px; + font-weight: 600; +} +.doc-nav > .content { + padding: 0 2rem 2rem 2rem; + display: flex; + flex-direction: column; +} +.doc-nav > .content > ul > li.active { + font-weight: 600; +} +.doc-nav > .content > ul > li.open ul { + display: initial; +} +.doc-nav > .content > ul > li.open > .menu-row > .dropdown-arrow { + transform: initial; +} +.doc-nav > .content > ul > li > .menu-row { + display: flex; + align-items: center; +} +.doc-nav > .content > ul > li > .menu-row > .dropdown-arrow { + transform: rotate(-90deg); + height: 18px; + width: 18px; + margin-left: calc(-18px - 0.3rem); + margin-right: 0.3rem; + cursor: pointer; + fill: #fff; + pointer-events: all; +} +.doc-nav > .content > ul > li > ul { + margin: 0.4rem 0; + display: none; +} +.doc-nav > .content > ul > li > ul > li { + border-color: #ffffff66; + border-color: var(--menu-indent-line-color); + border-left-width: 1.7px; + border-left-style: solid; + padding-left: 0.7rem; +} +.doc-nav > .content > ul > li > ul > li.active { + border-color: #00000066; + border-color: var(--menu-indent-line-active-color); +} +.doc-nav > .content a { + color: #fff; + color: var(--menu-font-color); + text-decoration: none; + user-select: none; +} +.doc-nav > .content a:hover { + text-decoration: underline; +} + +.doc-container { + display: flex; + flex-direction: column-reverse; + margin-top: 56px; +} + +.doc-content { + padding: 1rem 2rem; + overflow: hidden; +} +.doc-content a { + color: var(--link-color); +} +.doc-content > .doc-node:not(:last-child) { + padding: 1rem 0 3rem 0; +} +.doc-content > .timestamp { + font-size: 0.8rem; + color: #b8c2cc; + color: var(--timestamp-color); +} +.doc-content > .doc-node > .title { + display: flex; + align-items: center; + margin-bottom: 1rem; + border-bottom: 1px solid #f1f5f8; + border-bottom: 1px solid var(--title-bottom-line-color); +} +.doc-content > .doc-node > .title > .link { + margin-left: auto; + fill: #dae1e7; + fill: var(--ref-symbol-color); +} +.doc-content > .doc-node > .title > .link:hover { + fill: var(--ref-symbol-hover-color); +} +.doc-content > .doc-node h1 { + font-size: 2.5rem; + font-weight: 400; +} +.doc-content > .doc-node .signature { + border-color: #a0aec0; + border-color: var(--code-signature-border-color); + border-left-width: 3px; + border-left-style: solid; +} +.doc-content > .doc-node > ul > li .task-list-item-checkbox { + margin-right: 0.5rem; +} +.doc-content pre > code { + font-family: 'Source Code Pro', monospace; + font-size: 0.9rem; + background-color: #edf2f7; + background-color: var(--code-background-color); + padding: 1rem; + display: block; + border-radius: 0.25rem; + white-space: pre; + overflow-x: auto; +} +.doc-content > .doc-node > .title h1, +.doc-content > .doc-node > .title h2, +.doc-content > .doc-node > .title h3, +.doc-content > .doc-node > .title h4, +.doc-content > .doc-node > .title h5, +.doc-content > .doc-node > .title h6 { + font-weight: 400; + padding-bottom: 0.8rem; + margin: 0; +} +.doc-content > .doc-node > .title h1 a, +.doc-content > .doc-node > .title h2 a, +.doc-content > .doc-node > .title h3 a, +.doc-content > .doc-node > .title h4 a, +.doc-content > .doc-node > .title h5 a, +.doc-content > .doc-node > .title h6 a { + text-decoration: none; + color: #dae1e7; + color: var(--ref-symbol-color); +} +.doc-content > .doc-node > .title h1 a:hover, +.doc-content > .doc-node > .title h2 a:hover, +.doc-content > .doc-node > .title h3 a:hover, +.doc-content > .doc-node > .title h4 a:hover, +.doc-content > .doc-node > .title h5 a:hover, +.doc-content > .doc-node > .title h6 a:hover { + color: var(--ref-symbol-hover-color); +} + +.doc-toc { + right: 0; + top: 0; + height: 100%; + overflow-y: auto; + padding: 2rem; + width: 100%; + box-sizing: border-box; + -ms-overflow-style: none; + scrollbar-width: none; +} +.doc-toc::-webkit-scrollbar { + display: none; +} +.doc-toc li { + line-height: 1.5; +} +.doc-toc a { + color: #2779bd; + color: var(--toc-font-color); + font-size: 0.9rem; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + display: block; + text-decoration: none; +} +.doc-toc a:hover { + text-decoration: underline; +} +.doc-toc li ul { + border-color :#dae1e7; + border-color: var(--toc-indent-line-color); + border-left-width: 2px; + border-left-style: solid; + padding-left: 0.8rem; + margin: 0.2rem 0 0.2rem; + font-size: 0.7rem; + list-style: none; +} +.doc-toc li ul a { + font-weight: 400; +} + +/* Medium screen and up */ +@media (min-width: 768px) { + .doc-container { + flex-direction: row; + } + + .doc-content { + flex: 1; + } + + .doc-toc { + padding: 1rem 1rem 0 1rem; + position: relative; /* IE11 */ + position: sticky; + position: -webkit-sticky; + align-self: flex-start; + top: 56px; + height: auto; + height: 100vh; + min-width: 200px; + width: auto; + max-width: 300px; + } + .doc-toc > ul { + padding-bottom: 1rem; + } +} + +@media (max-width: 1023px) { + .doc-nav.hidden { + height: auto; + } + .doc-nav > .heading-container > .heading { + align-items: center; + } + .doc-nav > .heading-container > .heading > .toggle-version-container { + flex-grow: 1; + padding: 0 1rem; + justify-content: space-between; + } + .doc-nav > .heading-container > .heading > #search { + display: none; + } +} + +@media (min-width: 1024px) { + .doc-nav { + width: 300px; + } + .doc-nav #toggle-menu { + display: none; + } + .doc-nav > .heading-container > .heading { + height: auto; + padding-top: 1rem; + padding-bottom: 1rem; + flex-direction: column-reverse; + justify-content: center; + } + .doc-nav > .heading-container > .heading > .toggle-version-container { + align-items: center; + margin-bottom: 0.2rem; + display: flex; + flex-direction: row-reverse; + } + .doc-nav > .heading-container > .heading > .toggle-version-container > #dark-mode-toggle { + margin-right: auto; + } + .doc-nav .content.show, + .doc-nav .content.hidden { + display: flex; + } + + .doc-container { + margin-top: 0; + margin-left: 300px; + } + + .doc-toc { + top: 0; + } +} diff --git a/cmd/tools/vdoc-resources/doc.js b/cmd/tools/vdoc-resources/doc.js new file mode 100644 index 0000000000..a643d6d564 --- /dev/null +++ b/cmd/tools/vdoc-resources/doc.js @@ -0,0 +1,66 @@ +(function() { + // Mobile view menu toggle button + var toggle = document.getElementById("toggle-menu"); + toggle.addEventListener("click", function(ev) { + document.querySelectorAll(".doc-nav").forEach(function(el) { + el.classList.toggle("hidden"); + }); + document.querySelectorAll(".doc-nav .content").forEach(function(el) { + el.classList.toggle("hidden"); + el.classList.toggle("show"); + }); + }); + + // Dark mode + var html = document.getElementsByTagName('html')[0]; + var darkModeToggle = document.getElementById('dark-mode-toggle'); + darkModeToggle.addEventListener('click', function() { + html.classList.toggle('dark'); + var isDarkModeEnabled = html.classList.contains('dark'); + localStorage.setItem('dark-mode', isDarkModeEnabled); + }); + if (localStorage.getItem('dark-mode') === 'true') { + html.classList.add('dark'); + } + + // Check if css var() is supported and enable dark mode toggle + if (window.CSS && CSS.supports('color', 'var(--fake-var)')) { + darkModeToggle.style.visibility = 'unset'; + } + + // Search + var searchInput = document.getElementById('search'); + searchInput.addEventListener('input', function(e) { + var searchValue = e.target.value.toLowerCase(); + var menuItems = document.querySelectorAll('.content > ul > li'); + for (var i = 0; i < menuItems.length; i++) { + var menuItem = menuItems[i]; + var links = menuItem.querySelectorAll('a'); + var hasResult = false; + for (var li = 0; li < links.length; li++) { + var link = links[li]; + if (!searchValue || link.text.toLowerCase().indexOf(searchValue) !== -1) { + hasResult = true; + } + if (li > 0) { + if (!searchValue || link.text.toLowerCase().indexOf(searchValue) !== -1) { + link.style.display = ''; + } else { + link.style.display = 'none'; + } + } + } + menuItem.style.display = !searchValue || hasResult ? '' : 'none'; + } + }); + + // Collapse + var dropdownArrows = document.querySelectorAll('.dropdown-arrow'); + for (var i = 0; i < dropdownArrows.length; i++) { + var dropdownArrow = dropdownArrows[i]; + dropdownArrow.addEventListener('click', function(e) { + var parent = e.target.parentElement.parentElement.parentElement; + parent.classList.toggle('open'); + }); + } +})(); diff --git a/cmd/tools/vdoc-resources/light.svg b/cmd/tools/vdoc-resources/light.svg new file mode 100644 index 0000000000..21606c9cd3 --- /dev/null +++ b/cmd/tools/vdoc-resources/light.svg @@ -0,0 +1 @@ + diff --git a/cmd/tools/vdoc-resources/link.svg b/cmd/tools/vdoc-resources/link.svg new file mode 100644 index 0000000000..60d93bb2e9 --- /dev/null +++ b/cmd/tools/vdoc-resources/link.svg @@ -0,0 +1 @@ + diff --git a/cmd/tools/vdoc-resources/menu.svg b/cmd/tools/vdoc-resources/menu.svg new file mode 100644 index 0000000000..c069b00d90 --- /dev/null +++ b/cmd/tools/vdoc-resources/menu.svg @@ -0,0 +1 @@ + diff --git a/cmd/tools/vdoc.v b/cmd/tools/vdoc.v index 0dd70b9ee2..db2ba25b59 100644 --- a/cmd/tools/vdoc.v +++ b/cmd/tools/vdoc.v @@ -10,6 +10,12 @@ import v.doc import v.util import v.vmod +const ( + exe_path = os.executable() + exe_dir = os.dir(exe_path) + res_path = os.join_path(exe_dir, 'vdoc-resources') +) + enum OutputType { html markdown @@ -19,15 +25,15 @@ enum OutputType { } struct DocConfig { - pub_only bool = true - show_loc bool = false // for plaintext - serve_http bool = false // for html - is_multi bool = false + pub_only bool = true + show_loc bool = false // for plaintext + serve_http bool = false // for html + is_multi bool = false include_readme bool = false mut: - opath string + opath string src_path string - docs []doc.Doc + docs []doc.Doc manifest vmod.Manifest } @@ -90,6 +96,9 @@ fn get_src_link(repo_url string, file_name string, line_nr int) string { 'git.sir.ht' { '/tree/master/$file_name' } else { '' } } + if repo_url.starts_with('https://github.com/vlang/v') && !url.path.contains('master/vlib') { + url.path = url.path.replace('/blob/master/$file_name', '/blob/master/vlib/$file_name') + } if url.path == '/' { return '' } url.fragment = 'L$line_nr' return url.str() @@ -123,20 +132,22 @@ fn (cfg DocConfig) gen_html(idx int) string { mut toc := strings.new_builder(200) mut doc_node_html := fn (dd doc.DocNode, link string, head bool) string { mut dnw := strings.new_builder(200) + link_svg := '' head_tag := if head { 'h1' } else { 'h2' } md_content := markdown.to_html(dd.comment) dnw.writeln('
') if dd.name != 'README' { - dnw.write('<$head_tag>${dd.name} #') + dnw.write('
<$head_tag>${dd.name} #') + if link.len != 0 { + dnw.write('$link_svg') + } + dnw.write('
') } if head { dnw.write(md_content) } else { - dnw.writeln('${dd.content}') + dnw.writeln('
${dd.content}
') dnw.writeln(md_content) - if link.len != 0 { - dnw.writeln('

[Source]

') - } } dnw.writeln('
') return dnw.str() @@ -155,62 +166,80 @@ fn (cfg DocConfig) gen_html(idx int) string { } toc.writeln('') } // write head - hw.write(' - + hw.write(' + + + + ${dcs.head.name} | vdoc - ') + + ') + + // get resources + doc_css_min := get_resource('doc.css', true) + light_icon := get_resource('light.svg', true) + dark_icon := get_resource('dark.svg', true) + menu_icon := get_resource('menu.svg', true) + arrow_icon := get_resource('arrow.svg', true) + // write css - hw.write(r'') - version := if cfg.manifest.version.len != 0 { '${cfg.manifest.version}' } else { '' } - repo_link := if cfg.manifest.repo_url.len != 0 { - ' - Repository - - ' - } else { '' } + hw.write('') + version := if cfg.manifest.version.len != 0 { cfg.manifest.version } else { '' } header_name := if cfg.is_multi && cfg.docs.len > 1 { os.file_name(os.real_path(cfg.src_path)) } else { dcs.head.name } - // write nav - hw.write('
-
-

${header_name}

- ${version} - + // write nav1 + hw.write(' + +
+
\n') if cfg.is_multi && cfg.docs.len > 1 && dcs.head.name != 'README' { - hw.write('
\n

Contents

\n
    \n${toc.str()}
\n
') + hw.write('
\n\n
    \n${toc.str()}
\n
') } - hw.write('
- + doc_js_min := get_resource('doc.js', true) + hw.write(' ') return hw.str() @@ -354,6 +378,7 @@ fn (mut config DocConfig) generate_docs_from_file() { } if 'vlib' in config.src_path { config.manifest.version = util.v_version + config.manifest.repo_url = 'https://github.com/vlang/v' } readme_path := if 'vlib' in config.src_path { os.join_path(os.base_dir(@VEXE), 'README.md') @@ -446,6 +471,15 @@ fn get_modules_list(path string) []string { return dirs } +fn get_resource(name string, minify bool) string { + path := os.join_path(res_path, name) + res := os.read_file(path) or { panic('could not read $path') } + if minify { + res.replace('\n', ' ') + } + return res +} + fn main() { args_after_doc := cmdline.options_after(os.args[1..], ['doc']) opts := cmdline.only_options(os.args[1..]) diff --git a/cmd/v/v.v b/cmd/v/v.v index 804ea0dd61..13b3168789 100644 --- a/cmd/v/v.v +++ b/cmd/v/v.v @@ -79,7 +79,7 @@ fn main_v() { return } 'vlib-docs' { - util.launch_tool(prefs.is_verbose, 'vdoc', ['doc', '-m', '-s', '-r', os.join_path(os.base_dir(@VEXE), 'vlib')]) + util.launch_tool(prefs.is_verbose, 'vdoc', ['doc', '-m', '-s', os.join_path(os.base_dir(@VEXE), 'vlib')]) } 'get' { println('V Error: Use `v install` to install modules from vpm.vlang.io') diff --git a/vlib/v/doc/doc.v b/vlib/v/doc/doc.v index 801e0e9f71..4c38a26bfd 100644 --- a/vlib/v/doc/doc.v +++ b/vlib/v/doc/doc.v @@ -144,7 +144,12 @@ pub fn (nodes []DocNode) find_children_of(parent_type string) []DocNode { } fn get_parent_mod(dir string) ?string { - if dir.len == 0 { return error('root folder reached') } + $if windows { + // windows root path is C: or D: + if dir.len <= 2 { return error('root folder reached') } + } $else { + if dir.len == 0 { return error('root folder reached') } + } base_dir := os.base_dir(dir) if os.file_name(base_dir) in ['encoding', 'v'] && 'vlib' in base_dir { return os.file_name(base_dir)