更新客户端渲染,更新了壳

This commit is contained in:
QWQLwToo
2026-07-06 23:05:40 +08:00
parent e7dd87bf7e
commit 31d778710b
1311 changed files with 172662 additions and 1582 deletions
+16
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1 @@
This folder contains the source code for website themes used by docfx.exe.
+84
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+53
View File
@@ -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
View File
@@ -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}}
+78
View File
@@ -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
View File
@@ -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
View File
@@ -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'])
})
+56
View File
@@ -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;
}
}
+59
View File
@@ -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
View File
@@ -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
View File
@@ -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()
}
}
+22
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+40
View File
@@ -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
}
+34
View File
@@ -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
View File
@@ -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">&laquo;</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">&raquo;</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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}
}
+3
View File
@@ -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}}}