更新客户端渲染,更新了壳
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true
|
||||
},
|
||||
ignorePatterns: ['**/*.js'],
|
||||
extends: ['standard', 'eslint:recommended', 'plugin:@typescript-eslint/recommended'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
root: true,
|
||||
rules: {
|
||||
'space-before-function-paren': ['warn', 'never'],
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
*.min.js
|
||||
*.min.css
|
||||
*.map
|
||||
*.woff
|
||||
*.woff2
|
||||
|
||||
glyphicons-*.*
|
||||
|
||||
dist
|
||||
fonts
|
||||
node_modules
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "stylelint-config-standard-scss",
|
||||
"ignoreFiles": [
|
||||
"**/*.ts"
|
||||
],
|
||||
"rules": {
|
||||
"selector-class-pattern": null,
|
||||
"font-family-no-missing-generic-family-keyword": [ true, {
|
||||
"ignoreFontFamilies": [
|
||||
"bootstrap-icons"
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
This folder contains the source code for website themes used by docfx.exe.
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
const esbuild = require('esbuild')
|
||||
const { sassPlugin } = require('esbuild-sass-plugin')
|
||||
const bs = require('browser-sync')
|
||||
const { cpSync, rmSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
const { spawnSync } = require('child_process')
|
||||
const yargs = require('yargs/yargs')
|
||||
const { hideBin } = require('yargs/helpers')
|
||||
const argv = yargs(hideBin(process.argv)).argv
|
||||
|
||||
const watch = argv.watch
|
||||
const project = argv.project || '../samples/seed'
|
||||
const distdir = '../src/Docfx.App/templates'
|
||||
|
||||
const loader = {
|
||||
'.eot': 'file',
|
||||
'.svg': 'file',
|
||||
'.ttf': 'file',
|
||||
'.woff': 'file',
|
||||
'.woff2': 'file'
|
||||
}
|
||||
|
||||
build()
|
||||
|
||||
async function build() {
|
||||
|
||||
await buildWpfUiTemplate();
|
||||
|
||||
copyToDist()
|
||||
|
||||
if (watch) {
|
||||
serve()
|
||||
}
|
||||
}
|
||||
|
||||
async function buildWpfUiTemplate() {
|
||||
const config = {
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
splitting: true,
|
||||
minify: true,
|
||||
sourcemap: true,
|
||||
outExtension: {
|
||||
'.css': '.min.css',
|
||||
'.js': '.min.js'
|
||||
},
|
||||
outdir: 'wpfui/public',
|
||||
entryPoints: [
|
||||
'wpfui/src/docfx.ts',
|
||||
'wpfui/src/search-worker.ts',
|
||||
],
|
||||
plugins: [
|
||||
sassPlugin()
|
||||
],
|
||||
loader,
|
||||
}
|
||||
|
||||
if (watch) {
|
||||
const context = await esbuild.context(config)
|
||||
await context.watch()
|
||||
} else {
|
||||
await esbuild.build(config)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function copyToDist() {
|
||||
|
||||
rmSync(distdir, { recursive: true, force: true })
|
||||
|
||||
cpSync('wpfui', join(distdir, 'wpfui'), { recursive: true, overwrite: true, filter })
|
||||
|
||||
function filter(src) {
|
||||
const segments = src.split(/[/\\]/)
|
||||
return !segments.includes('node_modules') && !segments.includes('package-lock.json') && !segments.includes('src')
|
||||
}
|
||||
|
||||
function staticTocFilter(src) {
|
||||
return filter(src) && !src.includes('toc.html')
|
||||
}
|
||||
}
|
||||
+10381
File diff suppressed because it is too large
Load Diff
+53
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@docfx/template",
|
||||
"version": "1.0.0",
|
||||
"description": "Docfx static website templates",
|
||||
"keywords": [
|
||||
"docfx",
|
||||
"template"
|
||||
],
|
||||
"author": "docfx",
|
||||
"license": "MIT",
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "node build.js",
|
||||
"lint": "eslint wpfui/src && stylelint wpfui/src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@default/anchor-js": "npm:anchor-js@5.0.0",
|
||||
"@default/bootstrap": "npm:bootstrap@3.4.1",
|
||||
"@default/highlight.js": "npm:highlight.js@11.8.0",
|
||||
"@default/lunr": "npm:lunr@2.3.9",
|
||||
"@default/mark.js": "npm:mark.js@8.11.1",
|
||||
"@default/twbs-pagination": "josecebe/twbs-pagination#1.3.1",
|
||||
"@default/url": "npm:@websanova/url@2.6.3",
|
||||
"@websanova/url": "^2.6.3",
|
||||
"anchor-js": "^5.0.0",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
"highlight.js": "^11.8.0",
|
||||
"jquery": "3.7.0",
|
||||
"lit-html": "^2.8.0",
|
||||
"lunr": "2.3.9",
|
||||
"mathjax": "^3.2.2",
|
||||
"mermaid": "^10.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.1",
|
||||
"@typescript-eslint/parser": "^6.2.1",
|
||||
"browser-sync": "^2.29.3",
|
||||
"esbuild": "~0.18.17",
|
||||
"esbuild-sass-plugin": "~2.10.0",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"jest": "^29.6.2",
|
||||
"stylelint": "^15.10.2",
|
||||
"stylelint-config-standard-scss": "^10.0.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.1.6",
|
||||
"yargs": "^17.7.2"
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"removeComments": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
|
||||
{{!include(/^public/.*/)}}
|
||||
{{!include(favicon.ico)}}
|
||||
{{!include(logo.svg)}}
|
||||
{{!include(search-stopwords.json)}}
|
||||
<!DOCTYPE html>
|
||||
<html {{#_lang}}lang="{{_lang}}"{{/_lang}}>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
{{#redirect_url}}
|
||||
<meta http-equiv="refresh" content="0;URL='{{redirect_url}}'">
|
||||
{{/redirect_url}}
|
||||
{{^redirect_url}}
|
||||
<title>{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}</title>
|
||||
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
|
||||
<link rel="dns-prefetch" href="//fonts.gstatic.com" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="wpfui.lepo.co" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="title" content="{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}">
|
||||
{{#_description}}<meta name="description" content="{{_description}}">{{/_description}}
|
||||
<link rel="icon" href="{{_rel}}{{{_appFaviconPath}}}{{^_appFaviconPath}}favicon.ico{{/_appFaviconPath}}">
|
||||
<link rel="stylesheet" href="{{_rel}}public/docfx.min.css">
|
||||
<meta name="docfx:navrel" content="{{_navRel}}">
|
||||
<meta name="docfx:tocrel" content="{{_tocRel}}">
|
||||
{{#_noindex}}<meta name="searchOption" content="noindex">{{/_noindex}}
|
||||
{{#_enableSearch}}<meta name="docfx:rel" content="{{_rel}}">{{/_enableSearch}}
|
||||
{{#_disableNewTab}}<meta name="docfx:disablenewtab" content="true">{{/_disableNewTab}}
|
||||
{{#_disableTocFilter}}<meta name="docfx:disabletocfilter" content="true">{{/_disableTocFilter}}
|
||||
{{#docurl}}<meta name="docfx:docurl" content="{{docurl}}">{{/docurl}}
|
||||
{{/redirect_url}}
|
||||
</head>
|
||||
|
||||
{{^redirect_url}}
|
||||
<script type="module">
|
||||
import { init } from './{{_rel}}public/docfx.min.js'
|
||||
init()
|
||||
</script>
|
||||
|
||||
<script>
|
||||
const theme = localStorage.getItem('theme') || 'auto'
|
||||
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
|
||||
</script>
|
||||
|
||||
{{#_googleAnalyticsTagId}}
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{_googleAnalyticsTagId}}"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() { dataLayer.push(arguments); }
|
||||
gtag('js', new Date());
|
||||
gtag('config', '{{_googleAnalyticsTagId}}');
|
||||
</script>
|
||||
{{/_googleAnalyticsTagId}}
|
||||
|
||||
<body class="tex2jax_ignore" data-layout="{{_layout}}{{layout}}" data-yaml-mime="{{yamlmime}}">
|
||||
<header class="bg-body border-bottom">
|
||||
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
|
||||
<div class="container-xxl flex-nowrap">
|
||||
<a class="navbar-brand" href="{{_appLogoUrl}}{{^_appLogoUrl}}{{_rel}}index.html{{/_appLogoUrl}}">
|
||||
<img id="logo" class="svg" src="{{_rel}}{{{_appLogoPath}}}{{^_appLogoPath}}logo.svg{{/_appLogoPath}}" alt="{{_appName}}" >
|
||||
{{_appName}}
|
||||
</a>
|
||||
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<i class="bi bi-three-dots"></i>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navpanel">
|
||||
<div id="navbar">
|
||||
{{#_enableSearch}}
|
||||
<form class="search" role="search" id="search">
|
||||
<i class="bi bi-search"></i>
|
||||
<input class="form-control" id="search-query" type="search" disabled placeholder="{{__global.search}}" autocomplete="off" aria-label="Search">
|
||||
</form>
|
||||
{{/_enableSearch}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container-xxl">
|
||||
<div class="toc-offcanvas">
|
||||
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<nav class="toc" id="toc"></nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="actionbar">
|
||||
<button class="btn btn-lg border-0 d-md-none" style="margin-top: -.65em; margin-left: -.8em"
|
||||
type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas"
|
||||
aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
|
||||
<nav id="breadcrumb"></nav>
|
||||
</div>
|
||||
|
||||
<article data-uid="{{uid}}">
|
||||
{{!body}}
|
||||
</article>
|
||||
|
||||
{{^_disableContribution}}
|
||||
<div class="contribution d-print-none">
|
||||
{{#sourceurl}}
|
||||
<a href="{{sourceurl}}" class="edit-link">Edit this page</a>
|
||||
{{/sourceurl}}
|
||||
{{^sourceurl}}{{#docurl}}
|
||||
<a href="{{docurl}}" class="edit-link">Edit this page</a>
|
||||
{{/docurl}}{{/sourceurl}}
|
||||
</div>
|
||||
{{/_disableContribution}}
|
||||
|
||||
{{^_disableNextArticle}}
|
||||
<div class="next-article d-print-none border-top" id="nextArticle"></div>
|
||||
{{/_disableNextArticle}}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="affix">
|
||||
<nav id="affix"></nav>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{{#_enableSearch}}
|
||||
<div class="container-xxl search-results" id="search-results"></div>
|
||||
{{/_enableSearch}}
|
||||
|
||||
<footer class="border-top">
|
||||
<div class="container-xxl">
|
||||
<div class="row"><div class="col-12">WPF UI<a target="_blank" rel="noopener nofollow noreferrer" href="https://github.com/lepoco/wpfui"><code> https://github.com/lepoco/wpfui </code></a></div><div class="col-12">Build with<a target="_blank" rel="noopener nofollow noreferrer" href="https://dotnet.github.io/docfx/"><code> docfx </code></a>using <a target="_blank" rel="noopener nofollow noreferrer" href="https://highlightjs.org/"><code> highlight.js</code></a><a target="_blank" rel="noopener nofollow noreferrer" href="https://getbootstrap.com/"><code> bootstrap</code></a><a target="_blank" rel="noopener nofollow noreferrer" href="https://github.com/microsoft/fluentui-system-icons"><code> fluent-system-icons</code></a></div><div class="col-12">Copyright © 2026 lepo.co | Leszek Pomianowski and Open-Source Contributors</div></div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
{{/redirect_url}}
|
||||
</html>
|
||||
@@ -0,0 +1,158 @@
|
||||
{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
|
||||
|
||||
<h1 id="{{id}}" data-uid="{{uid}}" class="text-break">
|
||||
{{>partials/title}}
|
||||
{{#sourceurl}}<a class="header-action link-secondary" title="View source" href="{{sourceurl}}"><i class="bi bi-code-slash"></i></a>{{/sourceurl}}
|
||||
</h1>
|
||||
|
||||
<div class="facts text-secondary">
|
||||
<dl><dt>{{__global.namespace}}</dt><dd>{{{namespace.specName.0.value}}}</dd></dl>
|
||||
{{#assemblies.0}}<dl><dt>{{__global.assembly}}</dt><dd>{{assemblies.0}}.dll</dd></dl>{{/assemblies.0}}
|
||||
</div>
|
||||
|
||||
<div class="markdown summary">{{{summary}}}</div>
|
||||
<div class="markdown conceptual">{{{conceptual}}}</div>
|
||||
|
||||
{{#syntax.content.0.value}}
|
||||
<div class="codewrapper">
|
||||
<pre><code class="lang-csharp hljs">{{syntax.content.0.value}}</code></pre>
|
||||
</div>
|
||||
{{/syntax.content.0.value}}
|
||||
|
||||
{{#syntax.parameters.0}}
|
||||
<h4 class="section">{{__global.parameters}}</h4>
|
||||
<dl class="parameters">
|
||||
{{/syntax.parameters.0}}
|
||||
{{#syntax.parameters}}
|
||||
<dt><code>{{{id}}}</code> {{{type.specName.0.value}}}</dt>
|
||||
<dd>{{{description}}}</dd>
|
||||
{{/syntax.parameters}}
|
||||
{{#syntax.parameters.0}}
|
||||
</dl>
|
||||
{{/syntax.parameters.0}}
|
||||
|
||||
{{#syntax.return}}
|
||||
<h4 class="section">{{__global.returns}}</h4>
|
||||
<dl class="parameters">
|
||||
<dt>{{{type.specName.0.value}}}</dt>
|
||||
<dd>{{{description}}}</dd>
|
||||
</dl>
|
||||
{{/syntax.return}}
|
||||
|
||||
{{#syntax.typeParameters.0}}
|
||||
<h4 class="section">{{__global.typeParameters}}</h4>
|
||||
<dl class="parameters">
|
||||
{{/syntax.typeParameters.0}}
|
||||
{{#syntax.typeParameters}}
|
||||
<dt><code>{{{id}}}</code></dt>
|
||||
<dd>{{{description}}}</dd>
|
||||
{{/syntax.typeParameters}}
|
||||
{{#syntax.typeParameters.0}}
|
||||
</dl>
|
||||
{{/syntax.typeParameters.0}}
|
||||
|
||||
{{#inClass}}
|
||||
{{#inheritance.0}}
|
||||
<dl class="typelist inheritance">
|
||||
<dt>{{__global.inheritance}}</dt>
|
||||
<dd>
|
||||
{{/inheritance.0}}
|
||||
{{#inheritance}}
|
||||
<div>{{{specName.0.value}}}</div>
|
||||
{{/inheritance}}
|
||||
<div><span class="xref">{{name.0.value}}</span></div>
|
||||
{{#inheritance.0}}
|
||||
</dd>
|
||||
</dl>
|
||||
{{/inheritance.0}}
|
||||
{{/inClass}}
|
||||
|
||||
{{#implements.0}}
|
||||
<dl class="typelist implements">
|
||||
<dt>{{__global.implements}}</dt>
|
||||
<dd>
|
||||
{{/implements.0}}
|
||||
{{#implements}}
|
||||
<div>{{{specName.0.value}}}</div>
|
||||
{{/implements}}
|
||||
{{#implements.0}}
|
||||
</dd>
|
||||
</dl>
|
||||
{{/implements.0}}
|
||||
|
||||
{{#inClass}}
|
||||
{{#derivedClasses.0}}
|
||||
<dl class="typelist derived">
|
||||
<dt>{{__global.derived}}</dt>
|
||||
<dd>
|
||||
{{/derivedClasses.0}}
|
||||
{{#derivedClasses}}
|
||||
<div>{{{specName.0.value}}}</div>
|
||||
{{/derivedClasses}}
|
||||
{{#derivedClasses.0}}
|
||||
</dd>
|
||||
</dl>
|
||||
{{/derivedClasses.0}}
|
||||
{{/inClass}}
|
||||
|
||||
{{#inheritedMembers.0}}
|
||||
<dl class="typelist derived">
|
||||
<dt>{{__global.inheritedMembers}}</dt>
|
||||
<dd>
|
||||
{{/inheritedMembers.0}}
|
||||
{{#inheritedMembers}}
|
||||
<div>
|
||||
{{#definition}}
|
||||
<xref uid="{{definition}}" text="{{nameWithType.0.value}}" alt="{{fullName.0.value}}"/>
|
||||
{{/definition}}
|
||||
{{^definition}}
|
||||
<xref uid="{{uid}}" text="{{nameWithType.0.value}}" alt="{{fullName.0.value}}"/>
|
||||
{{/definition}}
|
||||
</div>
|
||||
{{/inheritedMembers}}
|
||||
{{#inheritedMembers.0}}
|
||||
</dl>
|
||||
{{/inheritedMembers.0}}
|
||||
|
||||
{{#extensionMethods.0}}
|
||||
<dl class="typelist extensionMethods">
|
||||
<dt>{{__global.extensionMethods}}</dt>
|
||||
<dd>
|
||||
{{/extensionMethods.0}}
|
||||
{{#extensionMethods}}
|
||||
<div>
|
||||
{{#definition}}
|
||||
<xref uid="{{definition}}" altProperty="fullName" displayProperty="nameWithType"/>
|
||||
{{/definition}}
|
||||
{{^definition}}
|
||||
<xref uid="{{uid}}" altProperty="fullName" displayProperty="nameWithType"/>
|
||||
{{/definition}}
|
||||
</div>
|
||||
{{/extensionMethods}}
|
||||
{{#extensionMethods.0}}
|
||||
</dl>
|
||||
{{/extensionMethods.0}}
|
||||
|
||||
{{#isEnum}}
|
||||
{{#children}}
|
||||
<h2 id="{{id}}">{{>partials/classSubtitle}}</h2>
|
||||
<dl class="parameters">
|
||||
{{#children}}
|
||||
<dt id="{{id}}"><code>{{syntax.content.0.value}}</code></dt>
|
||||
<dd>{{{summary}}}</dd>
|
||||
{{/children}}
|
||||
</dl>
|
||||
{{/children}}
|
||||
{{/isEnum}}
|
||||
|
||||
{{#example.0}}
|
||||
<h2 id="{{id}}_examples">{{__global.examples}}</h2>
|
||||
{{/example.0}}
|
||||
{{#example}}
|
||||
{{{.}}}
|
||||
{{/example}}
|
||||
|
||||
{{#remarks}}
|
||||
<h2 id="{{id}}_remarks">{{__global.remarks}}</h2>
|
||||
<div class="markdown level0 remarks">{{{remarks}}}</div>
|
||||
{{/remarks}}
|
||||
@@ -0,0 +1,31 @@
|
||||
{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
|
||||
|
||||
{{>partials/class.header}}
|
||||
|
||||
{{#children}}
|
||||
<h2 class="section" id="{{id}}">{{>partials/classSubtitle}}</h2>
|
||||
|
||||
{{#children}}
|
||||
<dl class="jumplist">
|
||||
<dt><xref uid="{{uid}}" altProperty="fullName" displayProperty="name"/></dt>
|
||||
<dd>{{{summary}}}</dd>
|
||||
</dl>
|
||||
{{/children}}
|
||||
|
||||
{{/children}}
|
||||
|
||||
{{#seealso.0}}
|
||||
<h2 id="seealso">{{__global.seealso}}</h2>
|
||||
<div class="seealso">
|
||||
{{/seealso.0}}
|
||||
{{#seealso}}
|
||||
{{#isCref}}
|
||||
<div>{{{type.specName.0.value}}}</div>
|
||||
{{/isCref}}
|
||||
{{^isCref}}
|
||||
<div>{{{url}}}</div>
|
||||
{{/isCref}}
|
||||
{{/seealso}}
|
||||
{{#seealso.0}}
|
||||
</div>
|
||||
{{/seealso.0}}
|
||||
@@ -0,0 +1,147 @@
|
||||
{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
|
||||
|
||||
{{>partials/class.header}}
|
||||
|
||||
{{#children}}
|
||||
|
||||
{{^_splitReference}}
|
||||
<h2 class="section" id="{{id}}">{{>partials/classSubtitle}}</h2>
|
||||
{{/_splitReference}}
|
||||
|
||||
{{#children}}
|
||||
|
||||
{{#overload}}
|
||||
<a id="{{id}}" data-uid="{{uid}}"></a>
|
||||
{{/overload}}
|
||||
|
||||
<h3 id="{{id}}" data-uid="{{uid}}">
|
||||
{{name.0.value}}
|
||||
{{#sourceurl}}<a class="header-action link-secondary" title="View source" href="{{sourceurl}}"><i class="bi bi-code-slash"></i></a>{{/sourceurl}}
|
||||
</h3>
|
||||
|
||||
<div class="markdown level1 summary">{{{summary}}}</div>
|
||||
<div class="markdown level1 conceptual">{{{conceptual}}}</div>
|
||||
|
||||
{{#syntax}}
|
||||
<div class="codewrapper">
|
||||
<pre><code class="lang-csharp hljs">{{syntax.content.0.value}}</code></pre>
|
||||
</div>
|
||||
|
||||
{{#syntax.parameters.0}}
|
||||
<h4 class="section">{{__global.parameters}}</h4>
|
||||
<dl class="parameters">
|
||||
{{/syntax.parameters.0}}
|
||||
{{#syntax.parameters}}
|
||||
<dt><code>{{{id}}}</code> {{{type.specName.0.value}}}</dt>
|
||||
<dd>{{{description}}}</dd>
|
||||
{{/syntax.parameters}}
|
||||
{{#syntax.parameters.0}}
|
||||
</dl>
|
||||
{{/syntax.parameters.0}}
|
||||
|
||||
{{#syntax.return}}
|
||||
<h4 class="section">{{__global.returns}}</h4>
|
||||
<dl class="parameters">
|
||||
<dt>{{{type.specName.0.value}}}</dt>
|
||||
<dd>{{{description}}}</dd>
|
||||
</dl>
|
||||
{{/syntax.return}}
|
||||
|
||||
{{#syntax.typeParameters.0}}
|
||||
<h4 class="section">{{__global.typeParameters}}</h4>
|
||||
<dl class="parameters">
|
||||
{{/syntax.typeParameters.0}}
|
||||
{{#syntax.typeParameters}}
|
||||
<dt><code>{{{id}}}</code></dt>
|
||||
<dd>{{{description}}}</dd>
|
||||
{{/syntax.typeParameters}}
|
||||
{{#syntax.typeParameters.0}}
|
||||
</dl>
|
||||
{{/syntax.typeParameters.0}}
|
||||
|
||||
{{#fieldValue}}
|
||||
<h4 class="section">{{__global.fieldValue}}</h4>
|
||||
<dl class="parameters">
|
||||
<dt>{{{type.specName.0.value}}}</dt>
|
||||
<dd>{{{description}}}</dd>
|
||||
</dl>
|
||||
{{/fieldValue}}
|
||||
|
||||
{{#propertyValue}}
|
||||
<h4 class="section">{{__global.propertyValue}}</h4>
|
||||
<dl class="parameters">
|
||||
<dt>{{{type.specName.0.value}}}</dt>
|
||||
<dd>{{{description}}}</dd>
|
||||
</dl>
|
||||
{{/propertyValue}}
|
||||
|
||||
{{#eventType}}
|
||||
<h4 class="section">{{__global.eventType}}</h4>
|
||||
<dl class="parameters">
|
||||
<dt>{{{type.specName.0.value}}}</dt>
|
||||
<dd>{{{description}}}</dd>
|
||||
</dl>
|
||||
{{/eventType}}
|
||||
|
||||
{{/syntax}}
|
||||
|
||||
{{#example.0}}
|
||||
<h4 class="section" id="{{id}}_examples">{{__global.examples}}</h4>
|
||||
{{/example.0}}
|
||||
{{#example}}
|
||||
{{{.}}}
|
||||
{{/example}}
|
||||
|
||||
{{#remarks}}
|
||||
<h4 class="section" id="{{id}}_remarks">{{__global.remarks}}</h4>
|
||||
<div class="markdown level1 remarks">{{{remarks}}}</div>
|
||||
{{/remarks}}
|
||||
|
||||
{{#exceptions.0}}
|
||||
<h4 class="section">{{__global.exceptions}}</h4>
|
||||
<dl class="parameters">
|
||||
{{/exceptions.0}}
|
||||
{{#exceptions}}
|
||||
<dt>{{{type.specName.0.value}}}</dt>
|
||||
<dd>{{{description}}}</dd>
|
||||
{{/exceptions}}
|
||||
{{#exceptions.0}}
|
||||
</dl>
|
||||
{{/exceptions.0}}
|
||||
|
||||
{{#seealso.0}}
|
||||
<dl class="typelist seealso">
|
||||
<dt>{{__global.seealso}}</dt>
|
||||
<dd>
|
||||
{{/seealso.0}}
|
||||
{{#seealso}}
|
||||
{{#isCref}}
|
||||
<div>{{{type.specName.0.value}}}</div>
|
||||
{{/isCref}}
|
||||
{{^isCref}}
|
||||
<div>{{{url}}}</div>
|
||||
{{/isCref}}
|
||||
{{/seealso}}
|
||||
{{#seealso.0}}
|
||||
</dd>
|
||||
</dl>
|
||||
{{/seealso.0}}
|
||||
|
||||
{{/children}}
|
||||
{{/children}}
|
||||
|
||||
{{#seealso.0}}
|
||||
<h2 id="seealso">{{__global.seealso}}</h2>
|
||||
<div class="seealso">
|
||||
{{/seealso.0}}
|
||||
{{#seealso}}
|
||||
{{#isCref}}
|
||||
<div>{{{type.specName.0.value}}}</div>
|
||||
{{/isCref}}
|
||||
{{^isCref}}
|
||||
<div>{{{url}}}</div>
|
||||
{{/isCref}}
|
||||
{{/seealso}}
|
||||
{{#seealso.0}}
|
||||
</div>
|
||||
{{/seealso.0}}
|
||||
@@ -0,0 +1,3 @@
|
||||
{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
|
||||
|
||||
{{>partials/class}}
|
||||
@@ -0,0 +1,10 @@
|
||||
{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
|
||||
{{!Add your own custom template for the content for ManagedReference here}}
|
||||
{{#_splitReference}}
|
||||
{{#isCollection}}
|
||||
{{>partials/collection}}
|
||||
{{/isCollection}}
|
||||
{{#isItem}}
|
||||
{{>partials/item}}
|
||||
{{/isItem}}
|
||||
{{/_splitReference}}
|
||||
@@ -0,0 +1,19 @@
|
||||
{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
|
||||
|
||||
{{>partials/class.header}}
|
||||
|
||||
{{#seealso.0}}
|
||||
<h2 id="seealso">{{__global.seealso}}</h2>
|
||||
<div class="seealso">
|
||||
{{/seealso.0}}
|
||||
{{#seealso}}
|
||||
{{#isCref}}
|
||||
<div>{{{type.specName.0.value}}}</div>
|
||||
{{/isCref}}
|
||||
{{^isCref}}
|
||||
<div>{{{url}}}</div>
|
||||
{{/isCref}}
|
||||
{{/seealso}}
|
||||
{{#seealso.0}}
|
||||
</div>
|
||||
{{/seealso.0}}
|
||||
@@ -0,0 +1,3 @@
|
||||
{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
|
||||
|
||||
{{>partials/class.header}}
|
||||
@@ -0,0 +1,16 @@
|
||||
{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
|
||||
|
||||
<h1 id="{{id}}" data-uid="{{uid}}" class="text-break">{{>partials/title}}</h1>
|
||||
<div class="markdown level0 summary">{{{summary}}}</div>
|
||||
<div class="markdown level0 conceptual">{{{conceptual}}}</div>
|
||||
<div class="markdown level0 remarks">{{{remarks}}}</div>
|
||||
|
||||
{{#children}}
|
||||
<h3 id="{{id}}">{{>partials/namespaceSubtitle}}</h3>
|
||||
{{#children}}
|
||||
<dl class="jumplist">
|
||||
<dt><xref uid="{{uid}}" altProperty="fullName" displayProperty="name"/></dt>
|
||||
<dd>{{{summary}}}</dd>
|
||||
</dl>
|
||||
{{/children}}
|
||||
{{/children}}
|
||||
@@ -0,0 +1,78 @@
|
||||
@import "https://fonts.googleapis.com/css2?family=Montserrat:wght@100;300;500;700;900&family=Raleway:wght@300;500;700;900&display=swap";
|
||||
|
||||
/**
|
||||
* Licensed to the .NET Foundation under one or more agreements.
|
||||
* The .NET Foundation licenses this file to you under the MIT license.
|
||||
*/
|
||||
|
||||
$enable-important-utilities: false;
|
||||
$container-max-widths: (
|
||||
xxl: 1768px
|
||||
) !default;
|
||||
|
||||
@import "mixins";
|
||||
@import "bootstrap/scss/bootstrap";
|
||||
@import "highlight";
|
||||
@import "layout";
|
||||
@import "nav";
|
||||
@import "toc";
|
||||
@import "markdown";
|
||||
@import "search";
|
||||
@import "dotnet";
|
||||
@import "wpfui";
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
.xref,
|
||||
.text-break {
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 0 5px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
article {
|
||||
// For REST API view source link
|
||||
span.small.pull-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.codewrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sample-response .response-content {
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
@media (width <= 768px) {
|
||||
#mobile-indicator {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobile-hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* workaround for #hashtag url is no longer needed */
|
||||
h1::before,
|
||||
h2::before,
|
||||
h3::before,
|
||||
h4::before {
|
||||
content: "";
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
import 'bootstrap'
|
||||
import { DocfxOptions } from './options'
|
||||
import { highlight } from './highlight'
|
||||
import { renderMarkdown } from './markdown'
|
||||
import { enableSearch } from './search'
|
||||
import { renderToc } from './toc'
|
||||
import { initTheme } from './theme'
|
||||
import { renderBreadcrumb, renderInThisArticle, renderNavbar } from './nav'
|
||||
import { renderIndexStats } from './wpfui-index-stats'
|
||||
|
||||
import 'bootstrap-icons/font/bootstrap-icons.scss'
|
||||
import './docfx.scss'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
docfx: DocfxOptions & {
|
||||
ready?: boolean,
|
||||
searchReady?: boolean,
|
||||
searchResultReady?: boolean,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function init() {
|
||||
const options = {
|
||||
defaultTheme: 'dark'
|
||||
} as DocfxOptions
|
||||
|
||||
window.docfx = Object.assign({}, options)
|
||||
|
||||
initTheme()
|
||||
enableSearch()
|
||||
renderInThisArticle()
|
||||
renderIndexStats()
|
||||
|
||||
await Promise.all([
|
||||
renderMarkdown(),
|
||||
renderNav(),
|
||||
highlight()
|
||||
])
|
||||
|
||||
window.docfx.ready = true
|
||||
|
||||
async function renderNav() {
|
||||
const [navbar, toc] = await Promise.all([renderNavbar(), renderToc()])
|
||||
renderBreadcrumb([...navbar, ...toc])
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Licensed to the .NET Foundation under one or more agreements.
|
||||
* The .NET Foundation licenses this file to you under the MIT license.
|
||||
*/
|
||||
|
||||
body[data-yaml-mime="ManagedReference"] article {
|
||||
h1[data-uid] {
|
||||
position: relative;
|
||||
padding-right: 1.6rem;
|
||||
}
|
||||
|
||||
h3[data-uid] {
|
||||
position: relative;
|
||||
font-weight: 400;
|
||||
margin-top: 3rem;
|
||||
padding-bottom: 5px;
|
||||
padding-right: 1.6rem;
|
||||
}
|
||||
|
||||
h2.section {
|
||||
margin-top: 3rem;
|
||||
|
||||
+h3[data-uid], +a+h3[data-uid] {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4.section {
|
||||
font-weight: 300;
|
||||
margin-top: 1.6rem;
|
||||
}
|
||||
|
||||
dl>dt {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
dl>dd {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
dl.typelist {
|
||||
>dt {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
>dd {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
>dd>div {
|
||||
display: inline-block;
|
||||
|
||||
&:not(:last-child)::after {
|
||||
content: ', ';
|
||||
}
|
||||
}
|
||||
|
||||
&.inheritance>dd>div:not(:last-child)::after {
|
||||
font-family: bootstrap-icons;
|
||||
content: '\F12C';
|
||||
position: relative;
|
||||
top: .2em;
|
||||
opacity: .8;
|
||||
}
|
||||
}
|
||||
|
||||
dl.parameters {
|
||||
>dt>code {
|
||||
margin-right: .2em;
|
||||
}
|
||||
}
|
||||
|
||||
div.facts {
|
||||
font-size: 14px;
|
||||
margin: 2rem 0 1rem;
|
||||
|
||||
>dl {
|
||||
margin: 0;
|
||||
|
||||
>dd {
|
||||
margin-left: .25rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
>dt {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
>dt::after {
|
||||
content: ":";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-action {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: .2rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
td.term {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: block;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
li>span.term {
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
content: '-';
|
||||
margin: 0 .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
import { breakWord } from './helper'
|
||||
|
||||
test('break-text', () => {
|
||||
expect(breakWord('Other APIs')).toEqual(['Other APIs'])
|
||||
expect(breakWord('System.CodeDom')).toEqual(['System.', 'Code', 'Dom'])
|
||||
expect(breakWord('System.Collections.Dictionary<string, object>')).toEqual(['System.', 'Collections.', 'Dictionary<', 'string,', ' object>'])
|
||||
expect(breakWord('https://github.com/dotnet/docfx')).toEqual(['https://github.', 'com/', 'dotnet/', 'docfx'])
|
||||
})
|
||||
@@ -0,0 +1,56 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
import { html, TemplateResult } from 'lit-html'
|
||||
|
||||
/**
|
||||
* Get the value of an HTML meta tag.
|
||||
*/
|
||||
export function meta(name: string): string {
|
||||
return (document.querySelector(`meta[name="${name}"]`) as HTMLMetaElement)?.content
|
||||
}
|
||||
|
||||
/**
|
||||
* Add <wbr> into long word.
|
||||
*/
|
||||
export function breakWord(text: string): string[] {
|
||||
const regex = /([a-z0-9])([A-Z]+[a-z])|([a-zA-Z0-9][.,/<>_])/g
|
||||
const result = []
|
||||
let start = 0
|
||||
while (true) {
|
||||
const match = regex.exec(text)
|
||||
if (!match) {
|
||||
break
|
||||
}
|
||||
const index = match.index + (match[1] || match[3]).length
|
||||
result.push(text.slice(start, index))
|
||||
start = index
|
||||
}
|
||||
if (start < text.length) {
|
||||
result.push(text.slice(start))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Add <wbr> into long word.
|
||||
*/
|
||||
export function breakWordLit(text: string): TemplateResult {
|
||||
const result = []
|
||||
breakWord(text).forEach(word => {
|
||||
if (result.length > 0) {
|
||||
result.push(html`<wbr>`)
|
||||
}
|
||||
result.push(html`${word}`)
|
||||
})
|
||||
return html`${result}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the url is external.
|
||||
* @param url The url to check.
|
||||
* @returns True if the url is external.
|
||||
*/
|
||||
export function isExternalHref(url: URL): boolean {
|
||||
return url.hostname !== window.location.hostname || url.protocol !== window.location.protocol
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Licensed to the .NET Foundation under one or more agreements.
|
||||
* The .NET Foundation licenses this file to you under the MIT license.
|
||||
*/
|
||||
|
||||
@import "highlight.js/scss/vs";
|
||||
|
||||
@include color-mode(dark) {
|
||||
/* stylelint-disable-next-line no-invalid-position-at-import-rule */
|
||||
@import "highlight.js/scss/vs2015";
|
||||
}
|
||||
|
||||
.hljs {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* For code snippet line highlight */
|
||||
pre > code .line-highlight {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
@include color-mode(dark) {
|
||||
pre > code .line-highlight {
|
||||
background-color: #4a4a00;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
export async function highlight() {
|
||||
const codeBlocks = document.querySelectorAll('pre code')
|
||||
if (codeBlocks.length <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const { default: hljs } = await import('highlight.js')
|
||||
|
||||
window.docfx.configureHljs?.(hljs)
|
||||
|
||||
document.querySelectorAll('pre code').forEach(block => {
|
||||
hljs.highlightElement(block as HTMLElement)
|
||||
})
|
||||
|
||||
document.querySelectorAll('pre code[highlight-lines]').forEach(block => {
|
||||
if (block.innerHTML === '') {
|
||||
return
|
||||
}
|
||||
|
||||
const queryString = block.getAttribute('highlight-lines')
|
||||
if (!queryString) {
|
||||
return
|
||||
}
|
||||
|
||||
const lines = block.innerHTML.split('\n')
|
||||
const ranges = queryString.split(',')
|
||||
for (const range of ranges) {
|
||||
let start = 0
|
||||
let end = 0
|
||||
const found = range.match(/^(\d+)-(\d+)?$/)
|
||||
if (found) {
|
||||
// consider region as `{startlinenumber}-{endlinenumber}`, in which {endlinenumber} is optional
|
||||
start = +found[1]
|
||||
end = +found[2]
|
||||
if (isNaN(end) || end > lines.length) {
|
||||
end = lines.length
|
||||
}
|
||||
} else {
|
||||
// consider region as a sigine line number
|
||||
if (isNaN(Number(range))) {
|
||||
continue
|
||||
}
|
||||
start = +range
|
||||
end = start
|
||||
}
|
||||
if (start <= 0 || end <= 0 || start > end || start > lines.length) {
|
||||
// skip current region if invalid
|
||||
continue
|
||||
}
|
||||
lines[start - 1] = '<span class="line-highlight">' + lines[start - 1]
|
||||
lines[end - 1] = lines[end - 1] + '</span>'
|
||||
}
|
||||
|
||||
block.innerHTML = lines.join('\n')
|
||||
})
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Licensed to the .NET Foundation under one or more agreements.
|
||||
* The .NET Foundation licenses this file to you under the MIT license.
|
||||
*/
|
||||
|
||||
$header-height: 80px;
|
||||
$footer-height: 120px;
|
||||
$main-padding-top: 1.6rem;
|
||||
$main-padding-bottom: 4rem;
|
||||
|
||||
// Makes a div sticky to top
|
||||
@mixin sticky-top {
|
||||
@include media-breakpoint-up(md) {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1030;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin stick-to-header {
|
||||
@include media-breakpoint-up(md) {
|
||||
position: sticky;
|
||||
top: calc($header-height + $main-padding-top);
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
width: calc(100vw - var(--scrollbar-width));
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body,
|
||||
body[data-layout="landing"] {
|
||||
width: calc(100vw - var(--scrollbar-width));
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
>header {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
@include sticky-top;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
height: $header-height;
|
||||
}
|
||||
|
||||
>nav {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
>footer {
|
||||
padding: 2rem 1rem;
|
||||
height: $footer-height;
|
||||
|
||||
>div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
>main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding-top: $main-padding-top;
|
||||
padding-bottom: $main-padding-bottom;
|
||||
|
||||
>.content {
|
||||
>:not(article) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
>article [id] {
|
||||
scroll-margin-top: $header-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>:not(.content) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
>header, >footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media not print {
|
||||
// Search layout
|
||||
body[data-search] {
|
||||
>main {
|
||||
display: none;
|
||||
}
|
||||
|
||||
>.search-results {
|
||||
display: block;
|
||||
flex: 1;
|
||||
padding-top: $main-padding-top;
|
||||
padding-bottom: $main-padding-bottom;
|
||||
}
|
||||
}
|
||||
|
||||
body:not([data-search]) {
|
||||
>.search-results {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Default layout: with header, footer, actionbar, affix, and toc
|
||||
&[data-layout=""],
|
||||
&[data-layout="conceptual"] {
|
||||
>main {
|
||||
padding-bottom: 0;
|
||||
|
||||
>.toc-offcanvas {
|
||||
flex: .35;
|
||||
display: block;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
max-width: 360px;
|
||||
max-height: calc(100vh - $header-height - $main-padding-top);
|
||||
|
||||
@include stick-to-header;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
flex: 0;
|
||||
}
|
||||
}
|
||||
|
||||
>.content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: 0 3rem;
|
||||
padding-bottom: $main-padding-bottom;
|
||||
|
||||
>.actionbar {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-top: .5rem;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
>.contribution,
|
||||
>.next-article {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
>.affix {
|
||||
display: block;
|
||||
width: 230px;
|
||||
max-height: calc(100vh - #{$header-height});
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
@include stick-to-header;
|
||||
|
||||
@media only screen and (width <= 1140px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chromeless layout: with no header, footer, actionbar, affix, and toc
|
||||
&[data-layout="chromeless"] {
|
||||
>header, >footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Licensed to the .NET Foundation under one or more agreements.
|
||||
* The .NET Foundation licenses this file to you under the MIT license.
|
||||
*/
|
||||
|
||||
/* External link icon */
|
||||
a.external[href]::after {
|
||||
font-family: bootstrap-icons;
|
||||
content: "\F1C5";
|
||||
font-size: .6rem;
|
||||
margin: 0 .2em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
.alert h5 {
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
|
||||
&::before {
|
||||
@include adjust-icon;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-info h5::before {
|
||||
content: "\F431";
|
||||
}
|
||||
|
||||
.alert-warning h5::before {
|
||||
content: "\F333";
|
||||
}
|
||||
|
||||
.alert-danger h5::before {
|
||||
content: "\F623";
|
||||
}
|
||||
|
||||
/* For Embedded Video */
|
||||
div.embeddedvideo {
|
||||
padding-top: 56.25%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
div.embeddedvideo iframe {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* For code actions */
|
||||
pre {
|
||||
position: relative;
|
||||
|
||||
>.code-action {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: .25rem;
|
||||
right: .2rem;
|
||||
|
||||
.bi-check-lg {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
>.code-action {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* For tabbed content */
|
||||
.tabGroup {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
>section {
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
border-top: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
}
|
||||
+454
@@ -0,0 +1,454 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
import { breakWord, meta } from './helper'
|
||||
import AnchorJs from 'anchor-js'
|
||||
import { html, render } from 'lit-html'
|
||||
import { getTheme } from './theme'
|
||||
|
||||
/**
|
||||
* Initialize markdown rendering.
|
||||
*/
|
||||
export async function renderMarkdown() {
|
||||
renderWordBreaks()
|
||||
renderTables()
|
||||
renderAlerts()
|
||||
renderLinks()
|
||||
renderTabs()
|
||||
renderAnchor()
|
||||
renderCodeCopy()
|
||||
renderClickableImage()
|
||||
|
||||
await Promise.all([
|
||||
renderMath(),
|
||||
renderMermaid()
|
||||
])
|
||||
}
|
||||
|
||||
async function renderMath() {
|
||||
const math = document.querySelectorAll('.math')
|
||||
if (math.length > 0) {
|
||||
await import('mathjax/es5/tex-svg-full.js')
|
||||
}
|
||||
}
|
||||
|
||||
let mermaidRenderCount = 0
|
||||
|
||||
/**
|
||||
* Render mermaid diagrams.
|
||||
*/
|
||||
async function renderMermaid() {
|
||||
const diagrams = document.querySelectorAll<HTMLElement>('pre code.lang-mermaid')
|
||||
if (diagrams.length <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const { default: mermaid } = await import('mermaid')
|
||||
const theme = getTheme() === 'dark' ? 'dark' : 'default'
|
||||
|
||||
// Turn off deterministic ids on re-render
|
||||
const deterministicIds = mermaidRenderCount === 0
|
||||
mermaid.initialize(Object.assign({ startOnLoad: false, deterministicIds, theme }, window.docfx.mermaid))
|
||||
mermaidRenderCount++
|
||||
|
||||
const nodes = []
|
||||
diagrams.forEach(e => {
|
||||
// Rerender when elements becomes visible due to https://github.com/mermaid-js/mermaid/issues/1846
|
||||
if (e.offsetParent) {
|
||||
nodes.push(e.parentElement)
|
||||
e.parentElement.classList.add('mermaid')
|
||||
e.parentElement.innerHTML = e.innerHTML
|
||||
}
|
||||
})
|
||||
|
||||
await mermaid.run({ nodes })
|
||||
}
|
||||
|
||||
/**
|
||||
* Add <wbr> to break long text.
|
||||
*/
|
||||
function renderWordBreaks() {
|
||||
document.querySelectorAll<HTMLElement>('article h1,h2,h3,h4,h5,h6,.xref,.text-break').forEach(e => {
|
||||
if (e.innerHTML?.trim() === e.innerText?.trim()) {
|
||||
const children: (string | Node)[] = []
|
||||
for (const text of breakWord(e.innerText)) {
|
||||
if (children.length > 0) {
|
||||
children.push(document.createElement('wbr'))
|
||||
}
|
||||
children.push(text)
|
||||
}
|
||||
e.replaceChildren(...children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Make images in articles clickable by wrapping the image in an anchor tag.
|
||||
* The image is clickable only if its size is larger than 200x200 and it is not already been wrapped in an anchor tag.
|
||||
*/
|
||||
function renderClickableImage() {
|
||||
const MIN_CLICKABLE_IMAGE_SIZE = 200
|
||||
const imageLinks = Array.from(document.querySelectorAll<HTMLImageElement>('article a img[src]'))
|
||||
|
||||
document.querySelectorAll<HTMLImageElement>('article img[src]').forEach(img => {
|
||||
if (shouldMakeClickable()) {
|
||||
makeClickable()
|
||||
} else {
|
||||
img.addEventListener('load', () => {
|
||||
if (shouldMakeClickable()) {
|
||||
makeClickable()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function makeClickable() {
|
||||
const a = document.createElement('a')
|
||||
a.target = '_blank'
|
||||
a.rel = 'noopener noreferrer nofollow'
|
||||
a.href = img.src
|
||||
img.replaceWith(a)
|
||||
a.appendChild(img)
|
||||
}
|
||||
|
||||
function shouldMakeClickable(): boolean {
|
||||
return img.naturalWidth > MIN_CLICKABLE_IMAGE_SIZE &&
|
||||
img.naturalHeight > MIN_CLICKABLE_IMAGE_SIZE &&
|
||||
!imageLinks.includes(img)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Styling for tables in conceptual documents using Bootstrap.
|
||||
* See http://getbootstrap.com/css/#tables
|
||||
*/
|
||||
function renderTables() {
|
||||
document.querySelectorAll('table').forEach(table => {
|
||||
table.classList.add('table', 'table-bordered', 'table-condensed')
|
||||
const wrapper = document.createElement('div')
|
||||
wrapper.className = 'table-responsive'
|
||||
table.parentElement.insertBefore(wrapper, table)
|
||||
wrapper.appendChild(table)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Styling for alerts.
|
||||
*/
|
||||
function renderAlerts() {
|
||||
document.querySelectorAll('.NOTE, .TIP').forEach(e => e.classList.add('alert', 'alert-info'))
|
||||
document.querySelectorAll('.WARNING').forEach(e => e.classList.add('alert', 'alert-warning'))
|
||||
document.querySelectorAll('.IMPORTANT, .CAUTION').forEach(e => e.classList.add('alert', 'alert-danger'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Open external links to different host in a new window.
|
||||
*/
|
||||
function renderLinks() {
|
||||
if (meta('docfx:disablenewtab') === 'true') {
|
||||
return
|
||||
}
|
||||
|
||||
document.querySelectorAll<HTMLAnchorElement>('article a[href]').forEach(a => {
|
||||
if (a.hostname !== window.location.hostname && a.innerText.trim() !== '') {
|
||||
a.target = '_blank'
|
||||
a.rel = 'noopener noreferrer nofollow'
|
||||
a.classList.add('external')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Render anchor # for headings
|
||||
*/
|
||||
function renderAnchor() {
|
||||
const anchors = new AnchorJs()
|
||||
anchors.options = Object.assign({
|
||||
visible: 'hover',
|
||||
icon: '#'
|
||||
}, window.docfx.anchors)
|
||||
|
||||
anchors.add('article h2:not(.no-anchor), article h3:not(.no-anchor), article h4:not(.no-anchor)')
|
||||
}
|
||||
|
||||
/**
|
||||
* Render code copy button.
|
||||
*/
|
||||
function renderCodeCopy() {
|
||||
document.querySelectorAll<HTMLElement>('pre>code').forEach(code => {
|
||||
if (code.innerText.trim().length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let copied = false
|
||||
renderCore()
|
||||
|
||||
function renderCore() {
|
||||
const dom = copied
|
||||
? html`<a class='btn border-0 link-success code-action'><i class='bi bi-check-lg'></i></a>`
|
||||
: html`<a class='btn border-0 code-action' title='copy' href='#' @click=${copy}><i class='bi bi-clipboard'></i></a>`
|
||||
render(dom, code.parentElement)
|
||||
|
||||
async function copy(e) {
|
||||
e.preventDefault()
|
||||
await navigator.clipboard.writeText(code.innerText)
|
||||
copied = true
|
||||
renderCore()
|
||||
setTimeout(() => {
|
||||
copied = false
|
||||
renderCore()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Render tabbed content.
|
||||
*/
|
||||
function renderTabs() {
|
||||
updateTabStyle()
|
||||
|
||||
const contentAttrs = {
|
||||
id: 'data-bi-id',
|
||||
name: 'data-bi-name',
|
||||
type: 'data-bi-type'
|
||||
}
|
||||
|
||||
const Tab = (function() {
|
||||
function Tab(li, a, section) {
|
||||
this.li = li
|
||||
this.a = a
|
||||
this.section = section
|
||||
}
|
||||
Object.defineProperty(Tab.prototype, 'tabIds', {
|
||||
get: function() { return this.a.getAttribute('data-tab').split(' ') },
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
})
|
||||
Object.defineProperty(Tab.prototype, 'condition', {
|
||||
get: function() { return this.a.getAttribute('data-condition') },
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
})
|
||||
Object.defineProperty(Tab.prototype, 'visible', {
|
||||
get: function() { return !this.li.hasAttribute('hidden') },
|
||||
set: function(value) {
|
||||
if (value) {
|
||||
this.li.removeAttribute('hidden')
|
||||
this.li.removeAttribute('aria-hidden')
|
||||
} else {
|
||||
this.li.setAttribute('hidden', 'hidden')
|
||||
this.li.setAttribute('aria-hidden', 'true')
|
||||
}
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
})
|
||||
Object.defineProperty(Tab.prototype, 'selected', {
|
||||
get: function() { return !this.section.hasAttribute('hidden') },
|
||||
set: function(value) {
|
||||
if (value) {
|
||||
this.a.setAttribute('aria-selected', 'true')
|
||||
this.a.classList.add('active')
|
||||
this.a.tabIndex = 0
|
||||
this.section.removeAttribute('hidden')
|
||||
this.section.removeAttribute('aria-hidden')
|
||||
} else {
|
||||
this.a.setAttribute('aria-selected', 'false')
|
||||
this.a.classList.remove('active')
|
||||
this.a.tabIndex = -1
|
||||
this.section.setAttribute('hidden', 'hidden')
|
||||
this.section.setAttribute('aria-hidden', 'true')
|
||||
}
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
})
|
||||
Tab.prototype.focus = function() {
|
||||
this.a.focus()
|
||||
}
|
||||
return Tab
|
||||
}())
|
||||
|
||||
initTabs(document.body)
|
||||
|
||||
function initTabs(container) {
|
||||
const queryStringTabs = readTabsQueryStringParam()
|
||||
const elements = container.querySelectorAll('.tabGroup')
|
||||
const state = { groups: [], selectedTabs: [] }
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const group = initTabGroup(elements.item(i))
|
||||
if (!group.independent) {
|
||||
updateVisibilityAndSelection(group, state)
|
||||
state.groups.push(group)
|
||||
}
|
||||
}
|
||||
container.addEventListener('click', function(event) { return handleClick(event, state) })
|
||||
if (state.groups.length === 0) {
|
||||
return state
|
||||
}
|
||||
selectTabs(queryStringTabs)
|
||||
updateTabsQueryStringParam(state)
|
||||
return state
|
||||
}
|
||||
|
||||
function initTabGroup(element) {
|
||||
const group = {
|
||||
independent: element.hasAttribute('data-tab-group-independent'),
|
||||
tabs: []
|
||||
}
|
||||
let li = element.firstElementChild.firstElementChild
|
||||
while (li) {
|
||||
const a = li.firstElementChild
|
||||
a.setAttribute(contentAttrs.name, 'tab')
|
||||
const dataTab = a.getAttribute('data-tab').replace(/\+/g, ' ')
|
||||
a.setAttribute('data-tab', dataTab)
|
||||
const section = element.querySelector('[id="' + a.getAttribute('aria-controls') + '"]')
|
||||
const tab = new Tab(li, a, section)
|
||||
group.tabs.push(tab)
|
||||
li = li.nextElementSibling
|
||||
}
|
||||
element.setAttribute(contentAttrs.name, 'tab-group')
|
||||
element.tabGroup = group
|
||||
return group
|
||||
}
|
||||
|
||||
function updateVisibilityAndSelection(group, state) {
|
||||
let anySelected = false
|
||||
let firstVisibleTab
|
||||
for (let _i = 0, _a = group.tabs; _i < _a.length; _i++) {
|
||||
const tab = _a[_i]
|
||||
tab.visible = tab.condition === null || state.selectedTabs.indexOf(tab.condition) !== -1
|
||||
if (tab.visible) {
|
||||
if (!firstVisibleTab) {
|
||||
firstVisibleTab = tab
|
||||
}
|
||||
}
|
||||
tab.selected = tab.visible && arraysIntersect(state.selectedTabs, tab.tabIds)
|
||||
anySelected = anySelected || tab.selected
|
||||
}
|
||||
if (!anySelected) {
|
||||
for (let _b = 0, _c = group.tabs; _b < _c.length; _b++) {
|
||||
const tabIds = _c[_b].tabIds
|
||||
for (let _d = 0, tabIds1 = tabIds; _d < tabIds1.length; _d++) {
|
||||
const tabId = tabIds1[_d]
|
||||
const index = state.selectedTabs.indexOf(tabId)
|
||||
if (index === -1) {
|
||||
continue
|
||||
}
|
||||
state.selectedTabs.splice(index, 1)
|
||||
}
|
||||
}
|
||||
const tab = firstVisibleTab
|
||||
tab.selected = true
|
||||
state.selectedTabs.push(tab.tabIds[0])
|
||||
}
|
||||
}
|
||||
|
||||
function getTabInfoFromEvent(event) {
|
||||
if (!(event.target instanceof HTMLElement)) {
|
||||
return null
|
||||
}
|
||||
const anchor = event.target.closest('a[data-tab]')
|
||||
if (anchor === null) {
|
||||
return null
|
||||
}
|
||||
const tabIds = anchor.getAttribute('data-tab').split(' ')
|
||||
const group = anchor.parentElement.parentElement.parentElement.tabGroup
|
||||
if (group === undefined) {
|
||||
return null
|
||||
}
|
||||
return { tabIds, group, anchor }
|
||||
}
|
||||
|
||||
function handleClick(event, state) {
|
||||
const info = getTabInfoFromEvent(event)
|
||||
if (info === null) {
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
info.anchor.href = 'javascript:'
|
||||
setTimeout(function() {
|
||||
info.anchor.href = '#' + info.anchor.getAttribute('aria-controls')
|
||||
})
|
||||
const tabIds = info.tabIds; const group = info.group
|
||||
const originalTop = info.anchor.getBoundingClientRect().top
|
||||
if (group.independent) {
|
||||
for (let _i = 0, _a = group.tabs; _i < _a.length; _i++) {
|
||||
const tab = _a[_i]
|
||||
tab.selected = arraysIntersect(tab.tabIds, tabIds)
|
||||
}
|
||||
} else {
|
||||
if (arraysIntersect(state.selectedTabs, tabIds)) {
|
||||
return
|
||||
}
|
||||
const previousTabId = group.tabs.filter(function(t) { return t.selected })[0].tabIds[0]
|
||||
state.selectedTabs.splice(state.selectedTabs.indexOf(previousTabId), 1, tabIds[0])
|
||||
for (let _b = 0, _c = state.groups; _b < _c.length; _b++) {
|
||||
const group1 = _c[_b]
|
||||
updateVisibilityAndSelection(group1, state)
|
||||
}
|
||||
updateTabsQueryStringParam(state)
|
||||
}
|
||||
notifyContentUpdated()
|
||||
const top = info.anchor.getBoundingClientRect().top
|
||||
if (top !== originalTop && event instanceof MouseEvent) {
|
||||
window.scrollTo(0, window.pageYOffset + top - originalTop)
|
||||
}
|
||||
}
|
||||
|
||||
function selectTabs(tabIds) {
|
||||
for (let _i = 0, tabIds1 = tabIds; _i < tabIds1.length; _i++) {
|
||||
const tabId = tabIds1[_i]
|
||||
const a = document.querySelector('.tabGroup > ul > li > a[data-tab="' + tabId + '"]:not([hidden])')
|
||||
if (a === null) {
|
||||
return
|
||||
}
|
||||
a.dispatchEvent(new CustomEvent('click', { bubbles: true }))
|
||||
}
|
||||
}
|
||||
|
||||
function readTabsQueryStringParam() {
|
||||
const qs = new URLSearchParams(window.location.search)
|
||||
const t = qs.get('tabs')
|
||||
if (!t) {
|
||||
return []
|
||||
}
|
||||
return t.split(',')
|
||||
}
|
||||
|
||||
function updateTabsQueryStringParam(state) {
|
||||
const qs = new URLSearchParams(window.location.search)
|
||||
qs.set('tabs', state.selectedTabs.join())
|
||||
const url = location.protocol + '//' + location.host + location.pathname + '?' + qs.toString() + location.hash
|
||||
if (location.href === url) {
|
||||
return
|
||||
}
|
||||
history.replaceState({}, document.title, url)
|
||||
}
|
||||
|
||||
function arraysIntersect(a, b) {
|
||||
for (let _i = 0, a1 = a; _i < a1.length; _i++) {
|
||||
const itemA = a1[_i]
|
||||
for (let _a = 0, b1 = b; _a < b1.length; _a++) {
|
||||
const itemB = b1[_a]
|
||||
if (itemA === itemB) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function updateTabStyle() {
|
||||
document.querySelectorAll('div.tabGroup>ul').forEach(e => e.classList.add('nav', 'nav-tabs'))
|
||||
document.querySelectorAll('div.tabGroup>ul>li').forEach(e => e.classList.add('nav-item'))
|
||||
document.querySelectorAll('div.tabGroup>ul>li>a').forEach(e => e.classList.add('nav-link'))
|
||||
document.querySelectorAll('div.tabGroup>section').forEach(e => e.classList.add('card'))
|
||||
}
|
||||
|
||||
function notifyContentUpdated() {
|
||||
renderMermaid()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Licensed to the .NET Foundation under one or more agreements.
|
||||
* The .NET Foundation licenses this file to you under the MIT license.
|
||||
*/
|
||||
|
||||
@mixin adjust-icon {
|
||||
font-family: bootstrap-icons;
|
||||
position: relative;
|
||||
margin-right: 0.5em;
|
||||
top: 0.2em;
|
||||
font-size: 1.25em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@mixin underline-on-hover {
|
||||
text-decoration: none;
|
||||
|
||||
&:hover, &:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Licensed to the .NET Foundation under one or more agreements.
|
||||
* The .NET Foundation licenses this file to you under the MIT license.
|
||||
*/
|
||||
|
||||
.breadcrumb {
|
||||
font-size: 14px;
|
||||
|
||||
a {
|
||||
@include underline-on-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.next-article {
|
||||
display: flex;
|
||||
|
||||
&:not(:has(div)) {
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
&:has(div) {
|
||||
margin-top: 3rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
&>div {
|
||||
flex: 1;
|
||||
|
||||
&.next {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&>span {
|
||||
opacity: .66;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&>a {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar {
|
||||
padding: 2rem 1rem;
|
||||
|
||||
.navbar-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
#navbar {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
|
||||
>i.bi {
|
||||
position: absolute;
|
||||
left: .8rem;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
>input {
|
||||
padding-left: 2.5rem;
|
||||
}
|
||||
|
||||
&.search {
|
||||
order: 50;
|
||||
}
|
||||
|
||||
&.icons {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
#navbar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
form {
|
||||
margin: 1rem 0 0;
|
||||
|
||||
&.search {
|
||||
align-self: stretch;
|
||||
order: 30;
|
||||
}
|
||||
|
||||
&.icons {
|
||||
align-self: center;
|
||||
order: 40;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.affix {
|
||||
font-size: 14px;
|
||||
|
||||
h5 {
|
||||
display: inline-block;
|
||||
font-weight: 300;
|
||||
text-transform: uppercase;
|
||||
padding: 1em 0 .5em;
|
||||
font-size: 14px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
ul {
|
||||
flex-direction: column;
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
margin-left: 0;
|
||||
|
||||
h6 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: .4rem 0;
|
||||
|
||||
a {
|
||||
@include underline-on-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contribution {
|
||||
margin-top: 2rem;
|
||||
|
||||
a.edit-link {
|
||||
@include underline-on-hover;
|
||||
|
||||
&::before {
|
||||
content: "\F4CA";
|
||||
display: inline-block;
|
||||
|
||||
@include adjust-icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
import { html, render, TemplateResult } from 'lit-html'
|
||||
import { breakWordLit, isExternalHref, meta } from './helper'
|
||||
import { themePicker } from './theme'
|
||||
import { TocNode } from './toc'
|
||||
|
||||
export type NavItem = {
|
||||
name: string
|
||||
href: URL
|
||||
}
|
||||
|
||||
export type NavItemContainer = {
|
||||
name: string
|
||||
items: NavItem[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns active navbar items
|
||||
*/
|
||||
export async function renderNavbar(): Promise<NavItem[]> {
|
||||
const navbar = document.getElementById('navbar')
|
||||
if (!navbar) {
|
||||
return
|
||||
}
|
||||
|
||||
const navItems = await loadNavItems()
|
||||
const activeItem = findActiveItem(navItems)
|
||||
|
||||
const menuItem = item => {
|
||||
const current = (item === activeItem ? 'page' : false)
|
||||
const active = (item === activeItem ? 'active' : null)
|
||||
return html`<li class='nav-item'><a class='nav-link ${active}' aria-current=${current} href=${item.href}>${breakWordLit(item.name)}</a></li>`
|
||||
}
|
||||
|
||||
const menu = html`
|
||||
<ul class='navbar-nav'>${navItems.map(item => {
|
||||
if ('items' in item) {
|
||||
const active = item.items.some(i => i === activeItem) ? 'active' : null
|
||||
return html`
|
||||
<li class='nav-item dropdown'>
|
||||
<a class='nav-link dropdown-toggle ${active}' href='#' role='button' data-bs-toggle='dropdown' aria-expanded='false'>
|
||||
${breakWordLit(item.name)}
|
||||
</a>
|
||||
<ul class='dropdown-menu'>${item.items.map(menuItem)}</ul>
|
||||
</li>`
|
||||
} else {
|
||||
return menuItem(item)
|
||||
}
|
||||
})
|
||||
}</ul>`
|
||||
|
||||
function renderCore() {
|
||||
const icons = html`
|
||||
<form class="icons">
|
||||
${window.docfx.iconLinks?.map(i => html`<a href="${i.href}" title="${i.title}" class="btn border-0"><i class="bi bi-${i.icon}"></i></a>`)}
|
||||
<a href="https://github.com/lepoco/wpfui" target="_blank" rel="noopener noreferrer" title="WPF UI on GitHub" class="btn border-0"><i class="bi bi-github"></i></a>
|
||||
${themePicker(renderCore)}
|
||||
<a class="btn btn-border-0 btn-colorful mr-05" target="_blank" rel="noopener noreferrer" href="https://github.com/sponsors/pomianowski">Sponsor</a>
|
||||
</form>`
|
||||
|
||||
render(html`${menu} ${icons}`, navbar)
|
||||
}
|
||||
|
||||
renderCore()
|
||||
|
||||
return activeItem ? [activeItem] : []
|
||||
|
||||
async function loadNavItems(): Promise<(NavItem | NavItemContainer)[]> {
|
||||
const navrel = meta('docfx:navrel')
|
||||
if (!navrel) {
|
||||
return []
|
||||
}
|
||||
|
||||
const navUrl = new URL(navrel.replace(/.html$/gi, '.json'), window.location.href)
|
||||
const { items } = await fetch(navUrl).then(res => res.json())
|
||||
return items.map((a: NavItem | NavItemContainer) => {
|
||||
if ('items' in a) {
|
||||
return { name: a.name, items: a.items.map(i => ({ name: i.name, href: new URL(i.href, navUrl) })) }
|
||||
}
|
||||
return { name: a.name, href: new URL(a.href, navUrl) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function renderBreadcrumb(breadcrumb: (NavItem | TocNode)[]) {
|
||||
const container = document.getElementById('breadcrumb')
|
||||
if (container) {
|
||||
render(
|
||||
html`
|
||||
<ol class="breadcrumb">
|
||||
${breadcrumb.map(i => html`<li class="breadcrumb-item"><a href="${i.href}">${breakWordLit(i.name)}</a></li>`)}
|
||||
</ol>`,
|
||||
container)
|
||||
}
|
||||
}
|
||||
|
||||
export function renderInThisArticle() {
|
||||
const affix = document.getElementById('affix')
|
||||
const windowPathname = window.location.pathname
|
||||
|
||||
if (windowPathname === '' || windowPathname === '/' || windowPathname === '/index.html') {
|
||||
return
|
||||
}
|
||||
|
||||
if (affix) {
|
||||
render(document.body.getAttribute('data-yaml-mime') === 'ManagedReference' ? inThisArticleForManagedReference() : inThisArticleForConceptual(), affix)
|
||||
}
|
||||
}
|
||||
|
||||
function inThisArticleForConceptual() {
|
||||
const headings = document.querySelectorAll<HTMLHeadingElement>('article h2')
|
||||
if (headings.length > 0) {
|
||||
return html`
|
||||
<h5 class="border-bottom">In this article</h5>
|
||||
<ul>${Array.from(headings).map(h => html`<li><a class="link-secondary" href="#${h.id}">${breakWordLit(h.innerText)}</a></li>`)}</ul>`
|
||||
}
|
||||
}
|
||||
|
||||
function inThisArticleForManagedReference(): TemplateResult {
|
||||
let headings = Array.from(document.querySelectorAll<HTMLHeadingElement>('article h2, article h3'))
|
||||
headings = headings.filter((h, i) => h.tagName === 'H3' || headings[i + 1]?.tagName === 'H3')
|
||||
|
||||
if (headings.length > 0) {
|
||||
return html`
|
||||
<h5 class="border-bottom">In this article</h5>
|
||||
<ul>${headings.map(h => {
|
||||
return h.tagName === 'H2'
|
||||
? html`<li><h6>${breakWordLit(h.innerText)}</h6></li>`
|
||||
: html`<li><a class="link-secondary" href="#${h.id}">${breakWordLit(h.innerText)}</a></li>`
|
||||
})}</ul>`
|
||||
}
|
||||
}
|
||||
|
||||
function findActiveItem(items: (NavItem | NavItemContainer)[]): NavItem {
|
||||
const url = new URL(window.location.href)
|
||||
let activeItem: NavItem
|
||||
let maxPrefix = 0
|
||||
for (const item of items.map(i => 'items' in i ? i.items : i).flat()) {
|
||||
if (isExternalHref(item.href)) {
|
||||
continue
|
||||
}
|
||||
const prefix = commonUrlPrefix(url, item.href)
|
||||
if (prefix > maxPrefix) {
|
||||
maxPrefix = prefix
|
||||
activeItem = item
|
||||
}
|
||||
}
|
||||
return activeItem
|
||||
}
|
||||
|
||||
function commonUrlPrefix(url: URL, base: URL): number {
|
||||
const urlSegments = url.pathname.split('/')
|
||||
const baseSegments = base.pathname.split('/')
|
||||
let i = 0
|
||||
while (i < urlSegments.length && i < baseSegments.length && urlSegments[i] === baseSegments[i]) {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
import BootstrapIcons from 'bootstrap-icons/font/bootstrap-icons.json'
|
||||
import { HLJSApi } from 'highlight.js'
|
||||
import { AnchorJSOptions } from 'anchor-js'
|
||||
import { MermaidConfig } from 'mermaid'
|
||||
|
||||
export type Theme = 'light' | 'dark' | 'auto'
|
||||
|
||||
export type IconLink = {
|
||||
/** A [bootstrap-icons](https://icons.getbootstrap.com/) name */
|
||||
icon: keyof typeof BootstrapIcons,
|
||||
|
||||
/** The URL of this icon link */
|
||||
href: string,
|
||||
|
||||
/** The title of this icon link shown on mouse hover */
|
||||
title?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables customization of the website through the global `window.docfx` object.
|
||||
*/
|
||||
export type DocfxOptions = {
|
||||
/** Configures the default theme */
|
||||
defaultTheme?: Theme,
|
||||
|
||||
/** A list of icons to show in the header next to the theme picker */
|
||||
iconLinks?: IconLink[],
|
||||
|
||||
/** Configures [anchor-js](https://www.bryanbraun.com/anchorjs#options) options */
|
||||
anchors?: AnchorJSOptions,
|
||||
|
||||
/** Configures mermaid diagram options */
|
||||
mermaid?: MermaidConfig,
|
||||
|
||||
/** Configures [hightlight.js](https://highlightjs.org/) */
|
||||
configureHljs?: (hljs: HLJSApi) => void,
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
import lunr from 'lunr'
|
||||
|
||||
let lunrIndex
|
||||
|
||||
let stopWords = null
|
||||
let searchData = {}
|
||||
|
||||
lunr.tokenizer.separator = /[\s\-.()]+/
|
||||
|
||||
const stopWordsRequest = new XMLHttpRequest()
|
||||
stopWordsRequest.open('GET', '../search-stopwords.json')
|
||||
stopWordsRequest.onload = function() {
|
||||
if (this.status !== 200) {
|
||||
return
|
||||
}
|
||||
stopWords = JSON.parse(this.responseText)
|
||||
buildIndex()
|
||||
}
|
||||
stopWordsRequest.send()
|
||||
|
||||
const searchDataRequest = new XMLHttpRequest()
|
||||
|
||||
searchDataRequest.open('GET', '../index.json')
|
||||
searchDataRequest.onload = function() {
|
||||
if (this.status !== 200) {
|
||||
return
|
||||
}
|
||||
searchData = JSON.parse(this.responseText)
|
||||
|
||||
buildIndex()
|
||||
|
||||
postMessage({ e: 'index-ready' })
|
||||
}
|
||||
searchDataRequest.send()
|
||||
|
||||
onmessage = function(oEvent) {
|
||||
const q = oEvent.data.q
|
||||
const results = []
|
||||
if (lunrIndex) {
|
||||
const hits = lunrIndex.search(q)
|
||||
hits.forEach(function(hit) {
|
||||
const item = searchData[hit.ref]
|
||||
results.push({ href: item.href, title: item.title, keywords: item.keywords })
|
||||
})
|
||||
}
|
||||
postMessage({ e: 'query-ready', q, d: results })
|
||||
}
|
||||
|
||||
function buildIndex() {
|
||||
if (stopWords !== null && !isEmpty(searchData)) {
|
||||
lunrIndex = lunr(function() {
|
||||
this.pipeline.remove(lunr.stopWordFilter)
|
||||
this.ref('href')
|
||||
this.field('title', { boost: 50 })
|
||||
this.field('keywords', { boost: 20 })
|
||||
|
||||
for (const prop in searchData) {
|
||||
if (Object.prototype.hasOwnProperty.call(searchData, prop)) {
|
||||
this.add(searchData[prop])
|
||||
}
|
||||
}
|
||||
|
||||
const docfxStopWordFilter = lunr.generateStopWordFilter(stopWords)
|
||||
lunr.Pipeline.registerFunction(docfxStopWordFilter, 'docfxStopWordFilter')
|
||||
this.pipeline.add(docfxStopWordFilter)
|
||||
this.searchPipeline.add(docfxStopWordFilter)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function isEmpty(obj) {
|
||||
if (!obj) return true
|
||||
|
||||
for (const prop in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, prop)) { return false }
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Licensed to the .NET Foundation under one or more agreements.
|
||||
* The .NET Foundation licenses this file to you under the MIT license.
|
||||
*/
|
||||
|
||||
#search-results {
|
||||
line-height: 1.8;
|
||||
|
||||
>.search-list {
|
||||
font-size: .9em;
|
||||
color: $secondary;
|
||||
}
|
||||
|
||||
>.sr-items {
|
||||
flex: 1;
|
||||
|
||||
.sr-item {
|
||||
margin-bottom: 1.5em;
|
||||
|
||||
>.item-title {
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
>.item-href {
|
||||
color: #093;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
>.item-brief {
|
||||
font-size: small;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
import { meta } from './helper'
|
||||
import { html, render, TemplateResult } from 'lit-html'
|
||||
import { classMap } from 'lit-html/directives/class-map.js'
|
||||
|
||||
type SearchHit = {
|
||||
href: string
|
||||
title: string
|
||||
keywords: string
|
||||
}
|
||||
|
||||
let query
|
||||
|
||||
/**
|
||||
* Support full-text-search
|
||||
*/
|
||||
export function enableSearch() {
|
||||
const searchQuery = document.getElementById('search-query') as HTMLInputElement
|
||||
if (!searchQuery || !window.Worker) {
|
||||
return
|
||||
}
|
||||
|
||||
const relHref = meta('docfx:rel') || ''
|
||||
const worker = new Worker(relHref + 'public/search-worker.min.js', { type: 'module' })
|
||||
worker.onmessage = function(oEvent) {
|
||||
switch (oEvent.data.e) {
|
||||
case 'index-ready':
|
||||
searchQuery.disabled = false
|
||||
searchQuery.addEventListener('input', onSearchQueryInput)
|
||||
window.docfx.searchReady = true
|
||||
break
|
||||
case 'query-ready':
|
||||
document.body.setAttribute('data-search', 'true')
|
||||
renderSearchResults(oEvent.data.d, 0)
|
||||
window.docfx.searchResultReady = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function onSearchQueryInput() {
|
||||
query = searchQuery.value
|
||||
if (query.length < 3) {
|
||||
document.body.removeAttribute('data-search')
|
||||
} else {
|
||||
worker.postMessage({ q: query })
|
||||
}
|
||||
}
|
||||
|
||||
function relativeUrlToAbsoluteUrl(currentUrl, relativeUrl) {
|
||||
const currentItems = currentUrl.split(/\/+/)
|
||||
const relativeItems = relativeUrl.split(/\/+/)
|
||||
let depth = currentItems.length - 1
|
||||
const items = []
|
||||
for (let i = 0; i < relativeItems.length; i++) {
|
||||
if (relativeItems[i] === '..') {
|
||||
depth--
|
||||
} else if (relativeItems[i] !== '.') {
|
||||
items.push(relativeItems[i])
|
||||
}
|
||||
}
|
||||
return currentItems.slice(0, depth).concat(items).join('/')
|
||||
}
|
||||
|
||||
function extractContentBrief(content) {
|
||||
const briefOffset = 512
|
||||
const words = query.split(/\s+/g)
|
||||
const queryIndex = content.indexOf(words[0])
|
||||
if (queryIndex > briefOffset) {
|
||||
return '...' + content.slice(queryIndex - briefOffset, queryIndex + briefOffset) + '...'
|
||||
} else if (queryIndex <= briefOffset) {
|
||||
return content.slice(0, queryIndex + briefOffset) + '...'
|
||||
}
|
||||
}
|
||||
|
||||
function renderSearchResults(hits: SearchHit[], page: number) {
|
||||
const numPerPage = 10
|
||||
const totalPages = Math.ceil(hits.length / numPerPage)
|
||||
|
||||
render(
|
||||
renderPage(page),
|
||||
document.getElementById('search-results'))
|
||||
|
||||
function renderPage(page: number): TemplateResult {
|
||||
if (hits.length === 0) {
|
||||
return html`<div class="search-list">No results for "${query}"</div>`
|
||||
}
|
||||
|
||||
const start = page * numPerPage
|
||||
const curHits = hits.slice(start, start + numPerPage)
|
||||
|
||||
const items = html`
|
||||
<div class="search-list">${hits.length} results for "${query}"</div>
|
||||
<div class="sr-items">${curHits.map(hit => {
|
||||
const currentUrl = window.location.href
|
||||
const itemRawHref = relativeUrlToAbsoluteUrl(currentUrl, relHref + hit.href)
|
||||
const itemHref = relHref + hit.href + '?q=' + query
|
||||
const itemBrief = extractContentBrief(hit.keywords)
|
||||
|
||||
return html`
|
||||
<div class="sr-item">
|
||||
<div class="item-title"><a href="${itemHref}" target="_blank" rel="noopener noreferrer">${mark(hit.title, query)}</a></div>
|
||||
<div class="item-href">${mark(itemRawHref, query)}</div>
|
||||
<div class="item-brief">${mark(itemBrief, query)}</div>
|
||||
</div>`
|
||||
})}
|
||||
</div>`
|
||||
|
||||
return html`${items} ${renderPagination()}`
|
||||
}
|
||||
|
||||
function renderPagination() {
|
||||
const maxVisiblePages = 5
|
||||
const startPage = Math.max(0, Math.min(page - 2, totalPages - maxVisiblePages))
|
||||
const endPage = Math.min(totalPages, startPage + maxVisiblePages)
|
||||
const pages = Array.from(new Array(endPage - startPage).keys()).map(i => i + startPage)
|
||||
|
||||
if (pages.length <= 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return html`
|
||||
<nav>
|
||||
<ul class="pagination">
|
||||
<li class="page-item">
|
||||
<a class="page-link ${classMap({ disabled: page <= 0 })}" href="#" aria-label="Previous"
|
||||
@click="${() => gotoPage(page - 1)}">
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
</li>
|
||||
${pages.map(i => html`
|
||||
<li class="page-item">
|
||||
<a class="page-link ${classMap({ active: page === i })}" href="#"
|
||||
@click="${() => gotoPage(i)}">${i + 1}</a></li>`)}
|
||||
<li class="page-item">
|
||||
<a class="page-link ${classMap({ disabled: page >= totalPages - 1 })}" href="#" aria-label="Next"
|
||||
@click="${() => gotoPage(page + 1)}">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>`
|
||||
|
||||
function gotoPage(page: number) {
|
||||
if (page >= 0 && page < totalPages) {
|
||||
renderSearchResults(hits, page)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mark(text: string, query: string): TemplateResult {
|
||||
const words = query.split(/\s+/g)
|
||||
const wordsLower = words.map(w => w.toLowerCase())
|
||||
const textLower = text.toLowerCase()
|
||||
const result = []
|
||||
let lastEnd = 0
|
||||
for (let i = 0; i < wordsLower.length; i++) {
|
||||
const word = wordsLower[i]
|
||||
const index = textLower.indexOf(word, lastEnd)
|
||||
if (index >= 0) {
|
||||
result.push(html`${text.slice(lastEnd, index)}`)
|
||||
result.push(html`<b>${text.slice(index, index + word.length)}</b>`)
|
||||
lastEnd = index + word.length
|
||||
}
|
||||
}
|
||||
result.push(html`${text.slice(lastEnd)}`)
|
||||
return html`${result}`
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
import { html } from 'lit-html'
|
||||
import { Theme } from './options'
|
||||
|
||||
function setTheme(theme: Theme) {
|
||||
localStorage.setItem('theme', theme)
|
||||
if (theme === 'auto') {
|
||||
document.documentElement.setAttribute('data-bs-theme', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-bs-theme', theme)
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultTheme() {
|
||||
return localStorage.getItem('theme') as Theme || window.docfx.defaultTheme || 'auto'
|
||||
}
|
||||
|
||||
export function initTheme() {
|
||||
setTheme(getDefaultTheme())
|
||||
}
|
||||
|
||||
export function getTheme(): 'light' | 'dark' {
|
||||
return document.documentElement.getAttribute('data-bs-theme') as 'light' | 'dark'
|
||||
}
|
||||
|
||||
export function themePicker(refresh: () => void) {
|
||||
const theme = getDefaultTheme()
|
||||
const icon = theme === 'light' ? 'sun' : theme === 'dark' ? 'moon' : 'circle-half'
|
||||
|
||||
return html`
|
||||
<div class='dropdown'>
|
||||
<a title='Change theme' class='btn border-0 dropdown-toggle mr-05' data-bs-toggle='dropdown' aria-expanded='false'>
|
||||
<i class='bi bi-${icon}'></i>
|
||||
</a>
|
||||
<ul class='dropdown-menu'>
|
||||
<li><a class='dropdown-item' href='#' @click=${e => changeTheme(e, 'light')}><i class='bi bi-sun'></i> Light</a></li>
|
||||
<li><a class='dropdown-item' href='#' @click=${e => changeTheme(e, 'dark')}><i class='bi bi-moon'></i> Dark</a></li>
|
||||
<li><a class='dropdown-item' href='#' @click=${e => changeTheme(e, 'auto')}><i class='bi bi-circle-half'></i> Auto</a></li>
|
||||
</ul>
|
||||
</div>`
|
||||
|
||||
function changeTheme(e, theme: Theme) {
|
||||
e.preventDefault()
|
||||
setTheme(theme)
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Licensed to the .NET Foundation under one or more agreements.
|
||||
* The .NET Foundation licenses this file to you under the MIT license.
|
||||
*/
|
||||
|
||||
$expand-stub-width: .85rem;
|
||||
|
||||
.toc {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
|
||||
ul {
|
||||
font-size: 14px;
|
||||
flex-direction: column;
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
li {
|
||||
font-weight: normal;
|
||||
margin: .6em 0;
|
||||
padding-left: $expand-stub-width;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
li > a {
|
||||
display: inline;
|
||||
|
||||
@include underline-on-hover;
|
||||
}
|
||||
|
||||
li > ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
li.expanded > ul {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.expand-stub::before {
|
||||
display: inline-block;
|
||||
width: $expand-stub-width;
|
||||
cursor: pointer;
|
||||
font-family: bootstrap-icons;
|
||||
font-size: .8em;
|
||||
content: "\F285";
|
||||
position: absolute;
|
||||
margin-top: .2em;
|
||||
margin-left: -$expand-stub-width;
|
||||
transition: transform 0.35s ease;
|
||||
transform-origin: .5em 50%;
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
& {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li.expanded > .expand-stub::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
span.name-only {
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
margin: .4rem 0;
|
||||
}
|
||||
|
||||
form.filter {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
>i.bi {
|
||||
position: absolute;
|
||||
left: .6rem;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
>input {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
>.no-result {
|
||||
font-size: .9em;
|
||||
color: $secondary;
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
import { TemplateResult, html, render } from 'lit-html'
|
||||
import { classMap } from 'lit-html/directives/class-map.js'
|
||||
import { breakWordLit, meta, isExternalHref } from './helper'
|
||||
|
||||
export type TocNode = {
|
||||
name: string
|
||||
href?: string
|
||||
expanded?: boolean
|
||||
items?: TocNode[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns active TOC nodes
|
||||
*/
|
||||
export async function renderToc(): Promise<TocNode[]> {
|
||||
const tocrel = meta('docfx:tocrel')
|
||||
if (!tocrel) {
|
||||
return []
|
||||
}
|
||||
|
||||
const disableTocFilter = meta('docfx:disabletocfilter') === 'true'
|
||||
|
||||
let tocFilter = disableTocFilter ? '' : (localStorage?.getItem('tocFilter') || '')
|
||||
|
||||
const tocUrl = new URL(tocrel.replace(/.html$/gi, '.json'), window.location.href)
|
||||
const { items } = await (await fetch(tocUrl)).json()
|
||||
|
||||
const activeNodes = []
|
||||
const selectedNodes = []
|
||||
items.forEach(initTocNodes)
|
||||
|
||||
const tocContainer = document.getElementById('toc')
|
||||
if (tocContainer) {
|
||||
renderToc()
|
||||
|
||||
const activeElements = tocContainer.querySelectorAll('li.active')
|
||||
const lastActiveElement = activeElements[activeElements.length - 1]
|
||||
if (lastActiveElement) {
|
||||
lastActiveElement.scrollIntoView({ block: 'nearest' })
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedNodes.length > 0) {
|
||||
renderNextArticle(items, selectedNodes[0])
|
||||
}
|
||||
|
||||
return activeNodes.slice(0, -1)
|
||||
|
||||
function initTocNodes(node: TocNode): boolean {
|
||||
let active
|
||||
if (node.href) {
|
||||
const url = new URL(node.href, tocUrl)
|
||||
node.href = url.href
|
||||
active = isExternalHref(url) ? false : normalizeUrlPath(url) === normalizeUrlPath(window.location)
|
||||
if (active) {
|
||||
if (node.items) {
|
||||
node.expanded = true
|
||||
}
|
||||
selectedNodes.push(node)
|
||||
}
|
||||
}
|
||||
|
||||
if (node.items) {
|
||||
for (const child of node.items) {
|
||||
if (initTocNodes(child)) {
|
||||
active = true
|
||||
node.expanded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (active) {
|
||||
activeNodes.unshift(node)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function renderToc() {
|
||||
render(html`${renderTocFilter()} ${renderTocNodes(items) || renderNoFilterResult()}`, tocContainer)
|
||||
}
|
||||
|
||||
function renderTocNodes(nodes: TocNode[]): TemplateResult {
|
||||
const result = nodes.map(node => {
|
||||
const { href, name, items, expanded } = node
|
||||
const isLeaf = !items || items.length <= 0
|
||||
|
||||
const children = isLeaf ? null : renderTocNodes(items)
|
||||
if (tocFilter !== '' && !children && !name.toLowerCase().includes(tocFilter.toLowerCase())) {
|
||||
return null
|
||||
}
|
||||
|
||||
const dom = href
|
||||
? html`<a class='${classMap({ 'nav-link': !activeNodes.includes(node) })}' href=${href}>${breakWordLit(name)}</a>`
|
||||
: (isLeaf
|
||||
? html`<span class='text-body-tertiary name-only'>${breakWordLit(name)}</a>`
|
||||
: html`<a class='${classMap({ 'nav-link': !activeNodes.includes(node) })}' href='#' @click=${toggleExpand}>${breakWordLit(name)}</a>`)
|
||||
|
||||
const isExpanded = (tocFilter !== '' && expanded !== false && children != null) || expanded === true
|
||||
|
||||
return html`
|
||||
<li class=${classMap({ expanded: isExpanded, active: activeNodes.includes(node) })}>
|
||||
${isLeaf ? null : html`<span class='expand-stub' @click=${toggleExpand}></span>`}
|
||||
${dom}
|
||||
${children}
|
||||
</li>`
|
||||
|
||||
function toggleExpand(e) {
|
||||
e.preventDefault()
|
||||
node.expanded = !isExpanded
|
||||
renderToc()
|
||||
}
|
||||
}).filter(node => node)
|
||||
|
||||
return result.length > 0 ? html`<ul>${result}</ul>` : null
|
||||
}
|
||||
|
||||
function renderTocFilter(): TemplateResult {
|
||||
return disableTocFilter
|
||||
? null
|
||||
: html`
|
||||
<form class='filter'>
|
||||
<i class='bi bi-filter'></i>
|
||||
<input class='form-control' @input=${filterToc} value='${tocFilter}' type='search' placeholder='Filter by title' autocomplete='off' aria-label='Filter by title'>
|
||||
</form>`
|
||||
|
||||
function filterToc(e: Event) {
|
||||
tocFilter = (<HTMLInputElement>e.target).value.trim()
|
||||
localStorage?.setItem('tocFilter', tocFilter)
|
||||
renderToc()
|
||||
}
|
||||
}
|
||||
|
||||
function renderNoFilterResult(): TemplateResult {
|
||||
return tocFilter === '' ? null : html`<div class='no-result'>No results for "${tocFilter}"</div>`
|
||||
}
|
||||
|
||||
function normalizeUrlPath(url: { pathname: string }): string {
|
||||
return url.pathname.replace(/\/index\.html$/gi, '/')
|
||||
}
|
||||
}
|
||||
|
||||
function renderNextArticle(items: TocNode[], node: TocNode) {
|
||||
const nextArticle = document.getElementById('nextArticle')
|
||||
if (!nextArticle) {
|
||||
return
|
||||
}
|
||||
|
||||
const tocNodes = flattenTocNodesWithHref(items)
|
||||
const i = tocNodes.findIndex(n => n === node)
|
||||
const prev = tocNodes[i - 1]
|
||||
const next = tocNodes[i + 1]
|
||||
if (!prev && !next) {
|
||||
return
|
||||
}
|
||||
|
||||
const prevButton = prev ? html`<div class="prev"><span><i class='bi bi-chevron-left'></i> Previous</span> <a href="${prev.href}" rel="prev">${breakWordLit(prev.name)}</a></div>` : null
|
||||
const nextButton = next ? html`<div class="next"><span>Next <i class='bi bi-chevron-right'></i></span> <a href="${next.href}" rel="next">${breakWordLit(next.name)}</a></div>` : null
|
||||
|
||||
render(html`${prevButton} ${nextButton}`, nextArticle)
|
||||
|
||||
function flattenTocNodesWithHref(items: TocNode[]) {
|
||||
const result = []
|
||||
for (const item of items) {
|
||||
if (item.href) {
|
||||
result.push(item)
|
||||
}
|
||||
if (item.items) {
|
||||
result.push(...flattenTocNodesWithHref(item.items))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
function updateBaseStats() {
|
||||
console.debug('Index stats initialized')
|
||||
}
|
||||
|
||||
export function renderIndexStats() {
|
||||
const windowPathname = window.location.pathname
|
||||
|
||||
if (windowPathname === '' || windowPathname === '/' || windowPathname === '/index.html') {
|
||||
updateBaseStats()
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
.h1,
|
||||
.h2,
|
||||
.h3,
|
||||
.h4,
|
||||
.h5,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
font-family: Montserrat, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||
Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji,
|
||||
Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-family: Montserrat, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||
Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji,
|
||||
Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.typelist.derived {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mr-05 {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.card-call-to-action {
|
||||
border-radius: 1rem;
|
||||
padding: 1rem;
|
||||
background-color: rgb(15, 163, 180);
|
||||
background-image: linear-gradient(
|
||||
140deg,
|
||||
rgb(0, 128, 154),
|
||||
rgb(19, 104, 145) 50%,
|
||||
rgb(32, 135, 135) 75%
|
||||
);
|
||||
transition: background-position 0.5s ease-in-out;
|
||||
background-size: 200% 200%;
|
||||
background-position: 0% 0%;
|
||||
|
||||
&:hover {
|
||||
background-position: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-colorful {
|
||||
background-color: rgb(15, 163, 180);
|
||||
background-image: linear-gradient(
|
||||
140deg,
|
||||
rgb(0, 128, 154),
|
||||
rgb(19, 104, 145) 50%,
|
||||
rgb(32, 135, 135) 75%
|
||||
);
|
||||
transition: background-position 0.5s ease-in-out;
|
||||
background-size: 200% 200%;
|
||||
background-position: 0% 0%;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background-position: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 4rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
#logo {
|
||||
max-width: 30px;
|
||||
margin-right: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.spaced-page {
|
||||
padding: 6rem 0;
|
||||
}
|
||||
|
||||
.display-1,
|
||||
.display-4 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.spaced-page-separator {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.colorful {
|
||||
background-color: #1fc8db;
|
||||
background-image: linear-gradient(
|
||||
140deg,
|
||||
#55e2fd,
|
||||
#58b2dd 50%,
|
||||
#61cece 75%
|
||||
);
|
||||
border-radius: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.btn-image {
|
||||
max-width: 185px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.btn-image:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.p-2 {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.mr-05 {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.stats {
|
||||
padding: 4rem 0;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
|
||||
strong {
|
||||
font-family: Montserrat, -apple-system, BlinkMacSystemFont, Segoe UI,
|
||||
Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif,
|
||||
Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
// border-top: 1px solid #2c2c2c;
|
||||
// color: #6b6b6b;
|
||||
font-size: 0.8rem;
|
||||
padding: 1rem;
|
||||
|
||||
a {
|
||||
color: #71a1ff;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.col-12 {
|
||||
.card {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
.card-body {
|
||||
img {
|
||||
margin-bottom: 2rem;
|
||||
max-width: 100%;
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
exports.transform = function (model) {
|
||||
|
||||
if (model.memberLayout === 'SeparatePages') {
|
||||
model = transformMemberPage(model);
|
||||
}
|
||||
|
||||
for (var key in model) {
|
||||
if (key[0] === '_') {
|
||||
delete model[key]
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: JSON.stringify(model)
|
||||
};
|
||||
}
|
||||
|
||||
function transformMemberPage(model) {
|
||||
var groupNames = {
|
||||
"constructor": { key: "constructorsInSubtitle" },
|
||||
"field": { key: "fieldsInSubtitle" },
|
||||
"property": { key: "propertiesInSubtitle" },
|
||||
"method": { key: "methodsInSubtitle" },
|
||||
"event": { key: "eventsInSubtitle" },
|
||||
"operator": { key: "operatorsInSubtitle" },
|
||||
"eii": { key: "eiisInSubtitle" },
|
||||
};
|
||||
|
||||
groupChildren(model);
|
||||
transformItem(model, 1);
|
||||
return model;
|
||||
|
||||
function groupChildren(item) {
|
||||
if (!item || !item.items || item.items.length == 0) {
|
||||
return;
|
||||
}
|
||||
var grouped = {};
|
||||
var items = [];
|
||||
item.items.forEach(function (element) {
|
||||
groupChildren(element);
|
||||
if (element.type) {
|
||||
var type = element.isEii ? "eii" : element.type.toLowerCase();
|
||||
if (!grouped.hasOwnProperty(type)) {
|
||||
if (!groupNames.hasOwnProperty(type)) {
|
||||
groupNames[type] = {
|
||||
name: element.type
|
||||
};
|
||||
console.log(type + " is not predefined type, use its type name as display name.")
|
||||
}
|
||||
grouped[type] = [];
|
||||
}
|
||||
grouped[type].push(element);
|
||||
} else {
|
||||
items.push(element);
|
||||
}
|
||||
}, this);
|
||||
|
||||
// With order defined in groupNames
|
||||
for (var key in groupNames) {
|
||||
if (groupNames.hasOwnProperty(key) && grouped.hasOwnProperty(key)) {
|
||||
items.push({
|
||||
name: model.__global[groupNames[key].key] || groupNames[key].name,
|
||||
items: grouped[key]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
item.items = items;
|
||||
}
|
||||
|
||||
function transformItem(item, level) {
|
||||
// set to null in case mustache looks up
|
||||
item.topicHref = item.topicHref || null;
|
||||
item.tocHref = item.tocHref || null;
|
||||
item.name = item.name || null;
|
||||
|
||||
item.level = level;
|
||||
|
||||
if (item.items && item.items.length > 0) {
|
||||
item.leaf = false;
|
||||
var length = item.items.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
transformItem(item.items[i], level + 1);
|
||||
};
|
||||
} else {
|
||||
item.items = [];
|
||||
item.leaf = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
|
||||
|
||||
{{{content}}}
|
||||
Reference in New Issue
Block a user