web: Fix inline documentation rendering (#13379)
web: Fix issues surrounding markdown rendering. - Fix issue where Mermaid diagrams do not render. - Fix link colors in dark mode. - Fix anchored links triggering router. - Fix issue where links occasionally link to missing page.
This commit is contained in:
		
							
								
								
									
										12
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| { | ||||
|     "name": "@goauthentik/authentik", | ||||
|     "version": "2025.2.1", | ||||
|     "lockfileVersion": 3, | ||||
|     "requires": true, | ||||
|     "packages": { | ||||
|         "": { | ||||
|             "name": "@goauthentik/authentik", | ||||
|             "version": "2025.2.1" | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -13,6 +13,7 @@ const importInlinePatterns = [ | ||||
|     'import AKGlobal from "@goauthentik/common/styles/authentik\\.css', | ||||
|     'import PF.+ from "@patternfly/patternfly/\\S+\\.css', | ||||
|     'import ThemeDark from "@goauthentik/common/styles/theme-dark\\.css', | ||||
|     'import OneDark from "@goauthentik/common/styles/one-dark\\.css', | ||||
|     'import styles from "\\./LibraryPageImpl\\.css', | ||||
| ]; | ||||
|  | ||||
| @ -39,6 +40,10 @@ const config: StorybookConfig = { | ||||
|             from: "../src/common/styles/theme-dark.css", | ||||
|             to: "@goauthentik/common/styles/theme-dark.css", | ||||
|         }, | ||||
|         { | ||||
|             from: "../src/common/styles/one-dark.css", | ||||
|             to: "@goauthentik/common/styles/one-dark.css", | ||||
|         }, | ||||
|     ], | ||||
|     framework: { | ||||
|         name: "@storybook/web-components-vite", | ||||
|  | ||||
| @ -71,7 +71,7 @@ export default [ | ||||
|                 ...globals.node, | ||||
|             }, | ||||
|         }, | ||||
|         files: ["scripts/*.mjs", "*.ts", "*.mjs"], | ||||
|         files: ["scripts/**/*.mjs", "*.ts", "*.mjs"], | ||||
|         rules: { | ||||
|             "no-unused-vars": "off", | ||||
|             // We WANT our scripts to output to the console! | ||||
|  | ||||
							
								
								
									
										4375
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4375
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -17,6 +17,8 @@ | ||||
|         "@lit/localize": "^0.12.2", | ||||
|         "@lit/reactive-element": "^2.0.4", | ||||
|         "@lit/task": "^1.0.1", | ||||
|         "@mdx-js/esbuild": "^3.1.0", | ||||
|         "@mdx-js/mdx": "^3.1.0", | ||||
|         "@open-wc/lit-helpers": "^0.7.0", | ||||
|         "@patternfly/elements": "^4.0.2", | ||||
|         "@patternfly/patternfly": "^4.224.2", | ||||
| @ -36,9 +38,11 @@ | ||||
|         "guacamole-common-js": "^1.5.0", | ||||
|         "lit": "^3.2.0", | ||||
|         "md-front-matter": "^1.0.4", | ||||
|         "mdx-mermaid": "^2.0.3", | ||||
|         "mermaid": "^11.4.1", | ||||
|         "rapidoc": "^9.3.7", | ||||
|         "showdown": "^2.1.0", | ||||
|         "react": "^18.3.1", | ||||
|         "react-dom": "^18.3.1", | ||||
|         "style-mod": "^4.1.2", | ||||
|         "ts-pattern": "^5.4.0", | ||||
|         "webcomponent-qr-code": "^1.2.0", | ||||
| @ -66,13 +70,13 @@ | ||||
|         "@types/guacamole-common-js": "^1.5.2", | ||||
|         "@types/mocha": "^10.0.8", | ||||
|         "@types/node": "^22.7.4", | ||||
|         "@types/showdown": "^2.0.6", | ||||
|         "@types/react": "^18.3.13", | ||||
|         "@typescript-eslint/eslint-plugin": "^8.8.0", | ||||
|         "@typescript-eslint/parser": "^8.8.0", | ||||
|         "@wdio/browser-runner": "9.4", | ||||
|         "@wdio/cli": "9.4", | ||||
|         "@wdio/spec-reporter": "^9.1.2", | ||||
|         "chokidar": "^4.0.1", | ||||
|         "change-case": "^5.4.4", | ||||
|         "chromedriver": "^131.0.1", | ||||
|         "esbuild": "^0.25.0", | ||||
|         "eslint": "^9.11.1", | ||||
| @ -87,6 +91,13 @@ | ||||
|         "npm-run-all": "^4.1.5", | ||||
|         "prettier": "^3.3.3", | ||||
|         "pseudolocale": "^2.1.0", | ||||
|         "rehype-highlight": "^7.0.2", | ||||
|         "rehype-parse": "^9.0.1", | ||||
|         "rehype-stringify": "^10.0.1", | ||||
|         "remark-directive": "^4.0.0", | ||||
|         "remark-frontmatter": "^5.0.0", | ||||
|         "remark-gfm": "^4.0.1", | ||||
|         "remark-mdx-frontmatter": "^5.0.0", | ||||
|         "rollup-plugin-modify": "^3.0.0", | ||||
|         "rollup-plugin-postcss-lit": "^2.1.0", | ||||
|         "storybook": "^8.3.4", | ||||
| @ -118,7 +129,7 @@ | ||||
|         "build-locales:build": "wireit", | ||||
|         "build-proxy": "wireit", | ||||
|         "build:sfe": "wireit", | ||||
|         "esbuild:watch": "node build.mjs --watch", | ||||
|         "esbuild:watch": "node scripts/build-web.mjs --watch", | ||||
|         "extract-locales": "wireit", | ||||
|         "format": "wireit", | ||||
|         "lint": "wireit", | ||||
| @ -153,7 +164,7 @@ | ||||
|                 "instead of `npm run watch`. The former is more comprehensive, but ", | ||||
|                 "the latter is faster." | ||||
|             ], | ||||
|             "command": "${NODE_RUNNER} build.mjs", | ||||
|             "command": "${NODE_RUNNER} scripts/build-web.mjs", | ||||
|             "files": [ | ||||
|                 "src/**/*.{css,jpg,png,ts,js,json}", | ||||
|                 "!src/**/*.stories.ts", | ||||
| @ -173,6 +184,7 @@ | ||||
|                 "./dist/poly-*.js.map", | ||||
|                 "./dist/custom.css", | ||||
|                 "./dist/theme-dark.css", | ||||
|                 "./dist/one-dark.css", | ||||
|                 "./dist/patternfly.min.css" | ||||
|             ], | ||||
|             "dependencies": [ | ||||
| @ -195,7 +207,7 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "build-proxy": { | ||||
|             "command": "node build.mjs --proxy", | ||||
|             "command": "node scripts/build-web.mjs --proxy", | ||||
|             "dependencies": [ | ||||
|                 "build-locales" | ||||
|             ] | ||||
|  | ||||
| @ -3,15 +3,16 @@ import esbuild from "esbuild"; | ||||
| import findFreePorts from "find-free-ports"; | ||||
| import { copyFileSync, mkdirSync, readFileSync, statSync } from "fs"; | ||||
| import { globSync } from "glob"; | ||||
| import path from "path"; | ||||
| import * as path from "path"; | ||||
| import { cwd } from "process"; | ||||
| import process from "process"; | ||||
| import { fileURLToPath } from "url"; | ||||
| 
 | ||||
| import { buildObserverPlugin } from "./build-observer-plugin.mjs"; | ||||
| import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs"; | ||||
| import { buildObserverPlugin } from "./esbuild/build-observer-plugin.mjs"; | ||||
| 
 | ||||
| const __dirname = fileURLToPath(new URL(".", import.meta.url)); | ||||
| let authentikProjectRoot = __dirname + "../"; | ||||
| let authentikProjectRoot = path.join(__dirname, "..", ".."); | ||||
| 
 | ||||
| try { | ||||
|     // Use the package.json file in the root folder, as it has the current version information.
 | ||||
| @ -122,11 +123,10 @@ const BASE_ESBUILD_OPTIONS = { | ||||
|     loader: { | ||||
|         ".css": "text", | ||||
|         ".md": "text", | ||||
|         ".mdx": "text", | ||||
|     }, | ||||
|     define: definitions, | ||||
|     format: "esm", | ||||
|     plugins: [], | ||||
|     plugins: [mdxPlugin()], | ||||
|     logOverride: { | ||||
|         /** | ||||
|          * HACK: Silences issue originating in ESBuild. | ||||
| @ -161,7 +161,7 @@ function composeVersionID() { | ||||
|  * @throws {Error} on build failure | ||||
|  */ | ||||
| function createEntryPointOptions([source, dest], overrides = {}) { | ||||
|     const outdir = path.join(__dirname, "./dist", dest); | ||||
|     const outdir = path.join(__dirname, "..", "dist", dest); | ||||
| 
 | ||||
|     return { | ||||
|         ...BASE_ESBUILD_OPTIONS, | ||||
| @ -214,7 +214,7 @@ async function doWatch() { | ||||
|                         buildObserverPlugin({ | ||||
|                             serverURL, | ||||
|                             logPrefix: entryPoint[1], | ||||
|                             relativeRoot: __dirname, | ||||
|                             relativeRoot: path.join(__dirname, ".."), | ||||
|                         }), | ||||
|                     ], | ||||
|                     define: { | ||||
							
								
								
									
										299
									
								
								web/scripts/esbuild/build-mdx-plugin.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										299
									
								
								web/scripts/esbuild/build-mdx-plugin.mjs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,299 @@ | ||||
| /** | ||||
|  * @import {Options as HighlightOptions} from 'rehype-highlight' | ||||
|  * @import {CompileOptions} from '@mdx-js/mdx' | ||||
|  * @import {mdxmermaid} from 'mdx-mermaid' | ||||
|  * @import {Message, | ||||
|       OnLoadArgs, | ||||
|       OnLoadResult, | ||||
|       Plugin, | ||||
|       PluginBuild | ||||
|  * } from 'esbuild' | ||||
|  */ | ||||
| import { run as runMDX } from "@mdx-js/mdx"; | ||||
| import { createFormatAwareProcessors } from "@mdx-js/mdx/internal-create-format-aware-processors"; | ||||
| import { extnamesToRegex } from "@mdx-js/mdx/internal-extnames-to-regex"; | ||||
| import apacheGrammar from "highlight.js/lib/languages/apache"; | ||||
| import diffGrammar from "highlight.js/lib/languages/diff"; | ||||
| import confGrammar from "highlight.js/lib/languages/ini"; | ||||
| import nginxGrammar from "highlight.js/lib/languages/nginx"; | ||||
| import { common } from "lowlight"; | ||||
| import mdxMermaid from "mdx-mermaid"; | ||||
| import { Mermaid } from "mdx-mermaid/lib/Mermaid"; | ||||
| import assert from "node:assert"; | ||||
| import fs from "node:fs/promises"; | ||||
| import path from "node:path"; | ||||
| import React from "react"; | ||||
| import { renderToStaticMarkup } from "react-dom/server"; | ||||
| import * as runtime from "react/jsx-runtime"; | ||||
| import rehypeHighlight from "rehype-highlight"; | ||||
| import remarkDirective from "remark-directive"; | ||||
| import remarkFrontmatter from "remark-frontmatter"; | ||||
| import remarkGFM from "remark-gfm"; | ||||
| import remarkMdxFrontmatter from "remark-mdx-frontmatter"; | ||||
| import remarkParse from "remark-parse"; | ||||
| import { SourceMapGenerator } from "source-map"; | ||||
| import { VFile } from "vfile"; | ||||
| import { VFileMessage } from "vfile-message"; | ||||
|  | ||||
| import { remarkAdmonition } from "./remark/remark-admonition.mjs"; | ||||
| import { remarkHeadings } from "./remark/remark-headings.mjs"; | ||||
| import { remarkLinks } from "./remark/remark-links.mjs"; | ||||
| import { remarkLists } from "./remark/remark-lists.mjs"; | ||||
|  | ||||
| /** | ||||
|  * @typedef {Omit<OnLoadArgs, 'pluginData'> & LoadDataFields} LoadData | ||||
|  *   Data passed to `onload`. | ||||
|  * | ||||
|  * @typedef LoadDataFields | ||||
|  *   Extra fields given in `data` to `onload`. | ||||
|  * @property {PluginData | null | undefined} [pluginData] | ||||
|  *   Plugin data. | ||||
|  * | ||||
|  * @typedef {CompileOptions} Options | ||||
|  *   Configuration. | ||||
|  * | ||||
|  *   Options are the same as `compile` from `@mdx-js/mdx`. | ||||
|  * | ||||
|  * @typedef PluginData | ||||
|  *   Extra data passed. | ||||
|  * @property {Buffer | string | null | undefined} [contents] | ||||
|  *   File contents. | ||||
|  * | ||||
|  * @typedef State | ||||
|  *   Info passed around. | ||||
|  * @property {string} doc | ||||
|  *   File value. | ||||
|  * @property {string} name | ||||
|  *   Plugin name. | ||||
|  * @property {string} path | ||||
|  *   File path. | ||||
|  */ | ||||
|  | ||||
| const eol = /\r\n|\r|\n|\u2028|\u2029/g; | ||||
|  | ||||
| const name = "@mdx-js/esbuild"; | ||||
|  | ||||
| /** | ||||
|  * Compile MDX to HTML. | ||||
|  * * | ||||
|  * @param {Readonly<Options> | null | undefined} [mdxOptions] | ||||
|  *   Configuration (optional). | ||||
|  * @return {Plugin} | ||||
|  *   Plugin. | ||||
|  */ | ||||
| export function mdxPlugin(mdxOptions) { | ||||
|     /** @type {mdxmermaid.Config} */ | ||||
|     const mermaidConfig = { | ||||
|         output: "svg", | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * @type {HighlightOptions} | ||||
|      */ | ||||
|     const highlightThemeOptions = { | ||||
|         languages: { | ||||
|             ...common, | ||||
|             nginx: nginxGrammar, | ||||
|             apache: apacheGrammar, | ||||
|             conf: confGrammar, | ||||
|             diff: diffGrammar, | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
|     const { extnames, process } = createFormatAwareProcessors({ | ||||
|         ...mdxOptions, | ||||
|         SourceMapGenerator, | ||||
|         outputFormat: "function-body", | ||||
|  | ||||
|         remarkPlugins: [ | ||||
|             remarkParse, | ||||
|             remarkDirective, | ||||
|             remarkAdmonition, | ||||
|             remarkGFM, | ||||
|             remarkFrontmatter, | ||||
|             remarkMdxFrontmatter, | ||||
|             remarkHeadings, | ||||
|             remarkLinks, | ||||
|             remarkLists, | ||||
|             [mdxMermaid, mermaidConfig], | ||||
|         ], | ||||
|         rehypePlugins: [[rehypeHighlight, highlightThemeOptions]], | ||||
|     }); | ||||
|  | ||||
|     return { name, setup }; | ||||
|  | ||||
|     /** | ||||
|      * @param {PluginBuild} build | ||||
|      *   Build. | ||||
|      * @returns {undefined} | ||||
|      *   Nothing. | ||||
|      */ | ||||
|     function setup(build) { | ||||
|         build.onLoad({ filter: extnamesToRegex(extnames) }, onload); | ||||
|  | ||||
|         /** | ||||
|          * @param {LoadData} data | ||||
|          *   Data. | ||||
|          * @returns {Promise<OnLoadResult>} | ||||
|          *   Result. | ||||
|          */ | ||||
|         async function onload(data) { | ||||
|             const document = String( | ||||
|                 data.pluginData && | ||||
|                     data.pluginData.contents !== null && | ||||
|                     data.pluginData.contents !== undefined | ||||
|                     ? data.pluginData.contents | ||||
|                     : await fs.readFile(data.path), | ||||
|             ); | ||||
|  | ||||
|             /** @type {State} */ | ||||
|             const state = { | ||||
|                 doc: document, | ||||
|                 name, | ||||
|                 path: data.path, | ||||
|             }; | ||||
|  | ||||
|             let file = new VFile({ | ||||
|                 path: data.path, | ||||
|                 value: document, | ||||
|             }); | ||||
|  | ||||
|             /** @type {string | undefined} */ | ||||
|             let value; | ||||
|  | ||||
|             /** @type {Array<VFileMessage>} */ | ||||
|             let messages = []; | ||||
|  | ||||
|             /** @type {Array<Message>} */ | ||||
|             const errors = []; | ||||
|  | ||||
|             /** @type {Array<Message>} */ | ||||
|             const warnings = []; | ||||
|  | ||||
|             /** | ||||
|              * @type {React.ComponentType<{children: React.ReactNode, frontmatter: Record<string, string>}>} | ||||
|              */ | ||||
|             const wrapper = ({ children, frontmatter }) => { | ||||
|                 const title = frontmatter.title; | ||||
|                 const nextChildren = React.Children.toArray(children); | ||||
|  | ||||
|                 if (title) { | ||||
|                     nextChildren.unshift(React.createElement("h1", { key: "title" }, title)); | ||||
|                 } | ||||
|  | ||||
|                 return React.createElement(React.Fragment, null, nextChildren); | ||||
|             }; | ||||
|  | ||||
|             try { | ||||
|                 file = await process(file); | ||||
|                 const { default: Content, ...mdxExports } = await runMDX(file, { | ||||
|                     ...runtime, | ||||
|                     useMDXComponents: () => { | ||||
|                         return { | ||||
|                             mermaid: Mermaid, | ||||
|                             Mermaid, | ||||
|                         }; | ||||
|                     }, | ||||
|                     baseUrl: import.meta.url, | ||||
|                 }); | ||||
|  | ||||
|                 const { frontmatter = {} } = mdxExports; | ||||
|                 const result = renderToStaticMarkup( | ||||
|                     Content({ | ||||
|                         frontmatter, | ||||
|                         components: { | ||||
|                             wrapper, | ||||
|                         }, | ||||
|                     }), | ||||
|                 ); | ||||
|  | ||||
|                 value = result; | ||||
|  | ||||
|                 messages = file.messages; | ||||
|             } catch (error_) { | ||||
|                 const cause = /** @type {VFileMessage | Error} */ (error_); | ||||
|  | ||||
|                 console.error(cause); | ||||
|  | ||||
|                 const message = | ||||
|                     "reason" in cause | ||||
|                         ? cause | ||||
|                         : new VFileMessage("Cannot process MDX file with esbuild", { | ||||
|                               cause, | ||||
|                               ruleId: "process-error", | ||||
|                               source: "@mdx-js/esbuild", | ||||
|                           }); | ||||
|  | ||||
|                 message.fatal = true; | ||||
|                 messages.push(message); | ||||
|             } | ||||
|  | ||||
|             for (const message of messages) { | ||||
|                 const list = message.fatal ? errors : warnings; | ||||
|                 list.push(vfileMessageToEsbuild(state, message)); | ||||
|             } | ||||
|  | ||||
|             // Safety check: the file has a path, so there has to be a `dirname`. | ||||
|             assert(file.dirname, "expected `dirname` to be defined"); | ||||
|  | ||||
|             return { | ||||
|                 contents: value || "", | ||||
|                 loader: "text", | ||||
|                 errors, | ||||
|                 resolveDir: path.resolve(file.cwd, file.dirname), | ||||
|                 warnings, | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param {Readonly<State>} state | ||||
|  *   Info passed around. | ||||
|  * @param {Readonly<VFileMessage>} message | ||||
|  *   VFile message or error. | ||||
|  * @returns {Message} | ||||
|  *   ESBuild message. | ||||
|  */ | ||||
| function vfileMessageToEsbuild(state, message) { | ||||
|     const place = message.place; | ||||
|     const start = place ? ("start" in place ? place.start : place) : undefined; | ||||
|     const end = place && "end" in place ? place.end : undefined; | ||||
|     let length = 0; | ||||
|     let lineStart = 0; | ||||
|     let line = 0; | ||||
|     let column = 0; | ||||
|  | ||||
|     if (start && start.offset !== undefined) { | ||||
|         line = start.line; | ||||
|         column = start.column - 1; | ||||
|         lineStart = start.offset - column; | ||||
|         length = 1; | ||||
|  | ||||
|         if (end && end.offset !== undefined) { | ||||
|             length = end.offset - start.offset; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     eol.lastIndex = lineStart; | ||||
|  | ||||
|     const match = eol.exec(state.doc); | ||||
|     const lineEnd = match ? match.index : state.doc.length; | ||||
|  | ||||
|     return { | ||||
|         detail: message, | ||||
|         id: "", | ||||
|         location: { | ||||
|             column, | ||||
|             file: state.path, | ||||
|             length: Math.min(length, lineEnd), | ||||
|             line, | ||||
|             lineText: state.doc.slice(lineStart, lineEnd), | ||||
|             namespace: "file", | ||||
|             suggestion: "", | ||||
|         }, | ||||
|         notes: [], | ||||
|         pluginName: state.name, | ||||
|         text: message.reason, | ||||
|     }; | ||||
| } | ||||
| @ -8,7 +8,7 @@ import path from "path"; | ||||
|  * @returns {string} | ||||
|  */ | ||||
| export function serializeCustomEventToStream(event) { | ||||
|     // @ts-ignore
 | ||||
|     // @ts-expect-error - TS doesn't know about the detail property
 | ||||
|     const data = event.detail ?? {}; | ||||
| 
 | ||||
|     const eventContent = [`event: ${event.type}`, `data: ${JSON.stringify(data)}`]; | ||||
							
								
								
									
										46
									
								
								web/scripts/esbuild/remark/remark-admonition.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								web/scripts/esbuild/remark/remark-admonition.mjs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| /** | ||||
|  * @import {Plugin} from 'unified' | ||||
|  * @import {Directives} from 'mdast-util-directive' | ||||
|  * @import {} from 'mdast-util-to-hast' | ||||
|  * @import {Root} from 'mdast' | ||||
|  * @import {VFile} from 'vfile' | ||||
|  */ | ||||
| import { h } from "hastscript"; | ||||
| import { visit } from "unist-util-visit"; | ||||
|  | ||||
| const ADMONITION_TYPES = new Set(["info", "warning", "danger", "note"]); | ||||
|  | ||||
| /** | ||||
|  * Remark plugin to process links | ||||
|  * @type {Plugin<[unknown], Root, VFile>} | ||||
|  */ | ||||
| export function remarkAdmonition() { | ||||
|     return function transformer(tree) { | ||||
|         /** | ||||
|          * @param {Directives} node | ||||
|          */ | ||||
|         const visitor = (node) => { | ||||
|             if ( | ||||
|                 node.type === "containerDirective" || | ||||
|                 node.type === "leafDirective" || | ||||
|                 node.type === "textDirective" | ||||
|             ) { | ||||
|                 if (!ADMONITION_TYPES.has(node.name)) return; | ||||
|  | ||||
|                 const data = node.data || (node.data = {}); | ||||
|  | ||||
|                 const tagName = node.type === "textDirective" ? "span" : "ak-alert"; | ||||
|  | ||||
|                 data.hName = tagName; | ||||
|  | ||||
|                 const element = h(tagName, node.attributes || {}); | ||||
|  | ||||
|                 data.hProperties = element.properties || {}; | ||||
|                 data.hProperties.level = `pf-m-${node.name}`; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         // @ts-ignore - visit cannot infer the type of the visitor. | ||||
|         visit(tree, visitor); | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										33
									
								
								web/scripts/esbuild/remark/remark-headings.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								web/scripts/esbuild/remark/remark-headings.mjs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| /** | ||||
|  * @import {Plugin} from 'unified' | ||||
|  * @import {Root, Heading} from 'mdast' | ||||
|  * @import {VFile} from 'vfile' | ||||
|  */ | ||||
| import { kebabCase } from "change-case"; | ||||
| import { toString } from "mdast-util-to-string"; | ||||
| import { visit } from "unist-util-visit"; | ||||
|  | ||||
| /** | ||||
|  * Remark plugin to process links | ||||
|  * @type {Plugin<[unknown], Root, VFile>} | ||||
|  */ | ||||
| export const remarkHeadings = () => { | ||||
|     return function transformer(tree) { | ||||
|         /** | ||||
|          * @param {Heading} node | ||||
|          */ | ||||
|         const visitor = (node) => { | ||||
|             const textContent = toString(node); | ||||
|             const id = kebabCase(textContent); | ||||
|  | ||||
|             node.data = node.data || {}; | ||||
|             node.data.hProperties = { | ||||
|                 ...node.data.hProperties, | ||||
|                 id, | ||||
|             }; | ||||
|         }; | ||||
|  | ||||
|         // @ts-ignore - visit cannot infer the type of the visitor. | ||||
|         visit(tree, "heading", visitor); | ||||
|     }; | ||||
| }; | ||||
							
								
								
									
										59
									
								
								web/scripts/esbuild/remark/remark-links.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								web/scripts/esbuild/remark/remark-links.mjs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| /** | ||||
|  * @import {Plugin} from 'unified' | ||||
|  * @import {} from 'mdast-util-directive' | ||||
|  * @import {} from 'mdast-util-to-hast' | ||||
|  * @import {Root, Link} from 'mdast' | ||||
|  * @import {VFile} from 'vfile' | ||||
|  */ | ||||
| import * as path from "node:path"; | ||||
| import { visit } from "unist-util-visit"; | ||||
|  | ||||
| const DOCS_DOMAIN = "https://goauthentik.io"; | ||||
|  | ||||
| /** | ||||
|  * Remark plugin to process links | ||||
|  * @type {Plugin<[unknown], Root, VFile>} | ||||
|  */ | ||||
| export const remarkLinks = () => { | ||||
|     return function transformer(tree, file) { | ||||
|         const docsRoot = path.resolve(file.cwd, "..", "website"); | ||||
|  | ||||
|         /** | ||||
|          * @param {Link} node | ||||
|          */ | ||||
|         const visitor = (node) => { | ||||
|             node.data = node.data || {}; | ||||
|  | ||||
|             if (node.url.startsWith("#")) { | ||||
|                 node.data.hProperties = { | ||||
|                     className: "markdown-heading", | ||||
|                 }; | ||||
|  | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             node.data.hProperties = { | ||||
|                 ...node.data.hProperties, | ||||
|                 rel: "noopener noreferrer", | ||||
|                 target: "_blank", | ||||
|             }; | ||||
|  | ||||
|             if (node.url.startsWith(".") && file.dirname) { | ||||
|                 const nextPathname = path.resolve( | ||||
|                     "/", | ||||
|                     path.relative(docsRoot, file.dirname), | ||||
|                     node.url, | ||||
|                 ); | ||||
|                 const nextURL = new URL(nextPathname, DOCS_DOMAIN); | ||||
|  | ||||
|                 // Remove trailing .md and .mdx, and trailing "index". | ||||
|                 nextURL.pathname = nextURL.pathname.replace(/(index)?\.mdx?$/, ""); | ||||
|  | ||||
|                 node.data.hProperties.href = nextURL.toString(); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         // @ts-ignore - visit cannot infer the type of the visitor. | ||||
|         visit(tree, "link", visitor); | ||||
|     }; | ||||
| }; | ||||
							
								
								
									
										29
									
								
								web/scripts/esbuild/remark/remark-lists.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								web/scripts/esbuild/remark/remark-lists.mjs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| /** | ||||
|  * @import {Plugin} from 'unified' | ||||
|  * @import {Root, List} from 'mdast' | ||||
|  * @import {VFile} from 'vfile' | ||||
|  */ | ||||
| import { visit } from "unist-util-visit"; | ||||
|  | ||||
| /** | ||||
|  * Remark plugin to process links | ||||
|  * @type {Plugin<[unknown], Root, VFile>} | ||||
|  */ | ||||
| export const remarkLists = () => { | ||||
|     return function transformer(tree) { | ||||
|         /** | ||||
|          * @param {List} node | ||||
|          */ | ||||
|         const visitor = (node) => { | ||||
|             node.data = node.data || {}; | ||||
|  | ||||
|             node.data.hProperties = { | ||||
|                 ...node.data.hProperties, | ||||
|                 className: "pf-c-list", | ||||
|             }; | ||||
|         }; | ||||
|  | ||||
|         // @ts-ignore - visit cannot infer the type of the visitor. | ||||
|         visit(tree, "list", visitor); | ||||
|     }; | ||||
| }; | ||||
| @ -89,7 +89,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>) | ||||
|         return html`<div class="pf-c-sidebar__panel pf-m-width-25"> | ||||
|             <div class="pf-c-card"> | ||||
|                 <div class="pf-c-card__body"> | ||||
|                     <ak-markdown .md=${MDApplication} meta="applications/index.md"></ak-markdown> | ||||
|                     <ak-markdown .content=${MDApplication}></ak-markdown> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div>`; | ||||
|  | ||||
| @ -221,7 +221,7 @@ export class OAuth2ProviderViewPage extends AKElement { | ||||
|                                     > | ||||
|                                 </dt> | ||||
|                                 <dd class="pf-c-description-list__description"> | ||||
|                                     <div class="pf-c-description-list__text"> | ||||
|                                     <div class="pf-c-description-list__text pf-m-monospace"> | ||||
|                                         ${this.provider.clientId} | ||||
|                                     </div> | ||||
|                                 </dd> | ||||
| @ -236,7 +236,9 @@ export class OAuth2ProviderViewPage extends AKElement { | ||||
|                                     <div class="pf-c-description-list__text"> | ||||
|                                         <ul> | ||||
|                                             ${this.provider.redirectUris.map((ru) => { | ||||
|                                                 return html`<li>${ru.matchingMode}: ${ru.url}</li>`; | ||||
|                                                 return html`<li class="pf-m-monospace"> | ||||
|                                                     ${ru.matchingMode}: ${ru.url} | ||||
|                                                 </li>`; | ||||
|                                             })} | ||||
|                                         </ul> | ||||
|                                     </div> | ||||
| @ -356,6 +358,7 @@ export class OAuth2ProviderViewPage extends AKElement { | ||||
|                 > | ||||
|                     <div class="pf-c-card__body"> | ||||
|                         <ak-markdown | ||||
|                             .content=${MDProviderOAuth2} | ||||
|                             .replacers=${[ | ||||
|                                 (input: string) => { | ||||
|                                     if (!this.provider) { | ||||
| @ -367,8 +370,6 @@ export class OAuth2ProviderViewPage extends AKElement { | ||||
|                                     ); | ||||
|                                 }, | ||||
|                             ]} | ||||
|                             .md=${MDProviderOAuth2} | ||||
|                             meta="providers/oauth2/index.md" | ||||
|                         ></ak-markdown> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
| @ -196,8 +196,8 @@ export class ProxyProviderViewPage extends AKElement { | ||||
|                     class="pf-c-page__main-section pf-m-no-padding-mobile ak-markdown-section" | ||||
|                 > | ||||
|                     <ak-markdown | ||||
|                         .content=${server.md} | ||||
|                         .replacers=${replacers} | ||||
|                         .md=${server.md} | ||||
|                         meta=${server.meta} | ||||
|                     ></ak-markdown> | ||||
|                 </section>`; | ||||
| @ -266,7 +266,7 @@ export class ProxyProviderViewPage extends AKElement { | ||||
|             <div class="pf-c-card pf-l-grid__item pf-m-12-col"> | ||||
|                 <div class="pf-c-card__body"> | ||||
|                     <ak-markdown | ||||
|                         .md=${MDHeaderAuthentication} | ||||
|                         .content=${MDHeaderAuthentication} | ||||
|                         meta="proxy/header_authentication.md" | ||||
|                     ></ak-markdown> | ||||
|                 </div> | ||||
|  | ||||
| @ -244,7 +244,7 @@ export class SCIMProviderViewPage extends AKElement { | ||||
|                 <div class="pf-c-card pf-l-grid__item pf-m-5-col"> | ||||
|                     <div class="pf-c-card__body"> | ||||
|                         <ak-markdown | ||||
|                             .md=${MDSCIMProvider} | ||||
|                             .content=${MDSCIMProvider} | ||||
|                             meta="providers/scim/index.md" | ||||
|                         ></ak-markdown> | ||||
|                     </div> | ||||
|  | ||||
| @ -137,10 +137,13 @@ export class SSFProviderViewPage extends AKElement { | ||||
|                                 <dd class="pf-c-description-list__description"> | ||||
|                                     <div class="pf-c-description-list__text"> | ||||
|                                         <input | ||||
|                                             class="pf-c-form-control" | ||||
|                                             class="pf-c-form-control pf-m-monospace" | ||||
|                                             readonly | ||||
|                                             type="text" | ||||
|                                             value=${this.provider.ssfUrl || ""} | ||||
|                                             placeholder=${this.provider.ssfUrl | ||||
|                                                 ? msg("SSF URL") | ||||
|                                                 : msg("No assigned application")} | ||||
|                                         /> | ||||
|                                     </div> | ||||
|                                 </dd> | ||||
|  | ||||
| @ -187,7 +187,7 @@ export class KerberosSourceViewPage extends AKElement { | ||||
|                     <div class="pf-c-card pf-l-grid__item pf-m-12-col"> | ||||
|                         <div class="pf-c-card__body"> | ||||
|                             <ak-markdown | ||||
|                                 .md=${MDSourceKerberosBrowser} | ||||
|                                 .content=${MDSourceKerberosBrowser} | ||||
|                                 meta="users-sources/protocols/kerberos/browser.md" | ||||
|                                 ; | ||||
|                             ></ak-markdown> | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| /* #region Global */ | ||||
|  | ||||
| :root { | ||||
|     --ak-accent: #fd4b2d; | ||||
|  | ||||
| @ -45,7 +47,44 @@ html > form > input { | ||||
|     left: -2000px; | ||||
| } | ||||
|  | ||||
| /*#region Icons*/ | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Anchors */ | ||||
|  | ||||
| a { | ||||
|     --pf-global--link--Color: var(--pf-global--link--Color--light); | ||||
|     --pf-global--link--Color--hover: var(--pf-global--link--Color--light--hover); | ||||
|     --pf-global--link--Color--visited: var(--pf-global--link--Color); | ||||
| } | ||||
|  | ||||
| /* | ||||
|     Note that order of anchor pseudo-selectors must follow: | ||||
|  | ||||
|     1. link | ||||
|     2. visited | ||||
|     3. hover | ||||
|     4. active | ||||
| */ | ||||
|  | ||||
| a:link { | ||||
|     color: var(--pf-global--link--Color); | ||||
| } | ||||
|  | ||||
| a:visited { | ||||
|     color: var(--pf-global--link--Color--visited); | ||||
| } | ||||
|  | ||||
| a:hover { | ||||
|     color: var(--pf-global--link--Color--hover); | ||||
| } | ||||
|  | ||||
| a:active { | ||||
|     color: var(--pf-global--link--Color); | ||||
| } | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Icons */ | ||||
|  | ||||
| .pf-icon { | ||||
|     display: inline-block; | ||||
| @ -66,7 +105,7 @@ html > form > input { | ||||
|     ); | ||||
| } | ||||
|  | ||||
| /*#endregion*/ | ||||
| /* #endregion */ | ||||
|  | ||||
| .pf-c-page__header { | ||||
|     z-index: 0; | ||||
| @ -74,15 +113,15 @@ html > form > input { | ||||
|     box-shadow: var(--pf-global--BoxShadow--lg-bottom); | ||||
| } | ||||
|  | ||||
| /***************************** | ||||
| * Login adjustments | ||||
| *****************************/ | ||||
| /* #region Login adjustments */ | ||||
|  | ||||
| /* Ensure card is displayed on small screens */ | ||||
| .pf-c-login__main { | ||||
|     display: block; | ||||
|     position: relative; | ||||
|     width: 100%; | ||||
| } | ||||
|  | ||||
| .ak-login-container { | ||||
|     height: calc(100vh - var(--pf-global--spacer--lg) - var(--pf-global--spacer--lg)); | ||||
|     width: 35rem; | ||||
| @ -90,30 +129,34 @@ html > form > input { | ||||
|     flex-direction: column; | ||||
|     justify-content: space-between; | ||||
| } | ||||
|  | ||||
| .pf-c-login__header { | ||||
|     flex-grow: 1; | ||||
| } | ||||
|  | ||||
| .pf-c-login__footer { | ||||
|     flex-grow: 2; | ||||
|     display: flex; | ||||
|     justify-content: end; | ||||
|     flex-direction: column; | ||||
| } | ||||
|  | ||||
| .pf-c-login__footer ul.pf-c-list.pf-m-inline { | ||||
|     justify-content: center; | ||||
|     padding: 2rem 0; | ||||
| } | ||||
| /***************************** | ||||
| * End Login adjustments | ||||
| *****************************/ | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| .pf-c-content h1 { | ||||
|     display: flex; | ||||
|     align-items: flex-start; | ||||
| } | ||||
|  | ||||
| .pf-c-content h1 i { | ||||
|     font-style: normal; | ||||
| } | ||||
|  | ||||
| .pf-c-content h1 :first-child { | ||||
|     margin-right: var(--pf-global--spacer--sm); | ||||
| } | ||||
| @ -127,9 +170,11 @@ html > form > input { | ||||
| .pf-m-success { | ||||
|     color: var(--pf-global--success-color--100) !important; | ||||
| } | ||||
|  | ||||
| .pf-m-warning { | ||||
|     color: var(--pf-global--warning-color--100); | ||||
| } | ||||
|  | ||||
| .pf-m-danger { | ||||
|     color: var(--pf-global--danger-color--100); | ||||
| } | ||||
| @ -167,6 +212,7 @@ html > form > input { | ||||
|     justify-content: center; | ||||
|     width: 100%; | ||||
| } | ||||
|  | ||||
| .ak-brand img { | ||||
|     padding: 0 2rem; | ||||
|     max-height: inherit; | ||||
| @ -181,3 +227,48 @@ html > form > input { | ||||
| .pf-c-data-list { | ||||
|     padding-inline-start: 0; | ||||
| } | ||||
|  | ||||
| /* #region Mermaid */ | ||||
|  | ||||
| svg[id^="mermaid-svg-"] { | ||||
|     .rect { | ||||
|         fill: var( | ||||
|             --ak-mermaid-box-background-color, | ||||
|             var(--pf-global--BackgroundColor--light-300) | ||||
|         ) !important; | ||||
|     } | ||||
|  | ||||
|     .messageText { | ||||
|         stroke-width: 4; | ||||
|         fill: var(--ak-mermaid-message-text) !important; | ||||
|         paint-order: stroke; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Tables */ | ||||
|  | ||||
| table thead, | ||||
| table tr:nth-child(2n) { | ||||
|     background-color: var( | ||||
|         --ak-table-stripe-background, | ||||
|         var(--pf-global--BackgroundColor--light-200) | ||||
|     ); | ||||
| } | ||||
|  | ||||
| table td, | ||||
| table th { | ||||
|     border: var(--pf-table-border-width) solid var(--ifm-table-border-color); | ||||
|     padding: var(--pf-global--spacer--md); | ||||
| } | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Code blocks */ | ||||
|  | ||||
| pre:has(.hljs) { | ||||
|     padding: var(--pf-global--spacer--md); | ||||
| } | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
							
								
								
									
										98
									
								
								web/src/common/styles/one-dark.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								web/src/common/styles/one-dark.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,98 @@ | ||||
| /* | ||||
|  | ||||
| Atom One Dark by Daniel Gamage | ||||
| Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax | ||||
|  | ||||
| base:    #282c34 | ||||
| mono-1:  #abb2bf | ||||
| mono-2:  #818896 | ||||
| mono-3:  #5c6370 | ||||
| hue-1:   #56b6c2 | ||||
| hue-2:   #61aeee | ||||
| hue-3:   #c678dd | ||||
| hue-4:   #98c379 | ||||
| hue-5:   #e06c75 | ||||
| hue-5-2: #be5046 | ||||
| hue-6:   #d19a66 | ||||
| hue-6-2: #e6c07b | ||||
|  | ||||
| */ | ||||
|  | ||||
| .hljs { | ||||
|     color: #abb2bf; | ||||
|     background: #282c34; | ||||
| } | ||||
|  | ||||
| pre:has(.hljs) { | ||||
|     background: #282c34; | ||||
| } | ||||
|  | ||||
| .hljs-comment, | ||||
| .hljs-quote { | ||||
|     color: #5c6370; | ||||
|     font-style: italic; | ||||
| } | ||||
|  | ||||
| .hljs-doctag, | ||||
| .hljs-keyword, | ||||
| .hljs-formula { | ||||
|     color: #c678dd; | ||||
| } | ||||
|  | ||||
| .hljs-section, | ||||
| .hljs-name, | ||||
| .hljs-selector-tag, | ||||
| .hljs-deletion, | ||||
| .hljs-subst { | ||||
|     color: #e06c75; | ||||
| } | ||||
|  | ||||
| .hljs-literal { | ||||
|     color: #56b6c2; | ||||
| } | ||||
|  | ||||
| .hljs-string, | ||||
| .hljs-regexp, | ||||
| .hljs-addition, | ||||
| .hljs-attribute, | ||||
| .hljs-meta .hljs-string { | ||||
|     color: #98c379; | ||||
| } | ||||
|  | ||||
| .hljs-attr, | ||||
| .hljs-variable, | ||||
| .hljs-template-variable, | ||||
| .hljs-type, | ||||
| .hljs-selector-class, | ||||
| .hljs-selector-attr, | ||||
| .hljs-selector-pseudo, | ||||
| .hljs-number { | ||||
|     color: #d19a66; | ||||
| } | ||||
|  | ||||
| .hljs-symbol, | ||||
| .hljs-bullet, | ||||
| .hljs-link, | ||||
| .hljs-meta, | ||||
| .hljs-selector-id, | ||||
| .hljs-title { | ||||
|     color: #61aeee; | ||||
| } | ||||
|  | ||||
| .hljs-built_in, | ||||
| .hljs-title.class_, | ||||
| .hljs-class .hljs-title { | ||||
|     color: #e6c07b; | ||||
| } | ||||
|  | ||||
| .hljs-emphasis { | ||||
|     font-style: italic; | ||||
| } | ||||
|  | ||||
| .hljs-strong { | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| .hljs-link { | ||||
|     text-decoration: underline; | ||||
| } | ||||
| @ -1,59 +1,84 @@ | ||||
| /* #region Global */ | ||||
|  | ||||
| :root { | ||||
|     --pf-global--Color--100: var(--ak-dark-foreground) !important; | ||||
|     --ak-global--Color--100: var(--ak-dark-foreground) !important; | ||||
|     --pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker); | ||||
|     --pf-global--link--Color: var(--ak-dark-foreground-link) !important; | ||||
|     --pf-global--BorderColor--100: var(--ak-dark-background-lighter) !important; | ||||
|     --ak-mermaid-message-text: var(--ak-dark-foreground) !important; | ||||
|     --ak-mermaid-box-background-color: var(--ak-dark-background-lighter) !important; | ||||
|     --ak-table-stripe-background: var(--pf-global--BackgroundColor--dark-200); | ||||
| } | ||||
|  | ||||
| body { | ||||
|     background-color: var(--ak-dark-background) !important; | ||||
| } | ||||
|  | ||||
| .pf-c-radio { | ||||
|     --pf-c-radio__label--Color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| /* Global page background colour */ | ||||
| .pf-c-page { | ||||
|     --pf-c-page--BackgroundColor: var(--ak-dark-background); | ||||
| } | ||||
|  | ||||
| .pf-c-drawer__content { | ||||
|     --pf-c-drawer__content--BackgroundColor: var(--ak-dark-background); | ||||
| } | ||||
|  | ||||
| .pf-c-title { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-u-mb-xl { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* Header sections */ | ||||
|  | ||||
| .pf-c-page__main-section { | ||||
|     --pf-c-page__main-section--BackgroundColor: var(--ak-dark-background); | ||||
| } | ||||
|  | ||||
| .sidebar-trigger, | ||||
| .notification-trigger { | ||||
|     background-color: transparent !important; | ||||
| } | ||||
|  | ||||
| .pf-c-content { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
| /* Card */ | ||||
|  | ||||
| /* #region Card */ | ||||
|  | ||||
| .pf-c-card { | ||||
|     --pf-c-card--BackgroundColor: var(--ak-dark-background-light); | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-card.pf-m-non-selectable-raised { | ||||
|     --pf-c-card--BackgroundColor: var(--ak-dark-background-lighter); | ||||
| } | ||||
|  | ||||
| .pf-c-card__title, | ||||
| .pf-c-card__body { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| .pf-c-toolbar { | ||||
|     --pf-c-toolbar--BackgroundColor: var(--ak-dark-background-light); | ||||
| } | ||||
|  | ||||
| .pf-c-pagination.pf-m-bottom { | ||||
|     background-color: var(--ak-dark-background-light); | ||||
| } | ||||
| /* table */ | ||||
|  | ||||
| /* #region Tables */ | ||||
| .pf-c-table { | ||||
|     --pf-c-table--BackgroundColor: var(--ak-dark-background-light); | ||||
|     --pf-c-table--BorderColor: var(--ak-dark-background-lighter); | ||||
| @ -61,41 +86,58 @@ body { | ||||
|     --pf-c-table--tr--m-hoverable--hover--BackgroundColor: var(--ak-dark-background-light-ish); | ||||
|     --pf-c-table--tr--m-hoverable--active--BackgroundColor: var(--ak-dark-background-lighter); | ||||
| } | ||||
|  | ||||
| .pf-c-table__text { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-table__sort:not(.pf-m-selected) .pf-c-table__button .pf-c-table__text { | ||||
|     color: var(--ak-dark-foreground) !important; | ||||
| } | ||||
|  | ||||
| .pf-c-table__sort-indicator i { | ||||
|     color: var(--ak-dark-foreground) !important; | ||||
| } | ||||
|  | ||||
| .pf-c-table__expandable-row.pf-m-expanded { | ||||
|     --pf-c-table__expandable-row--m-expanded--BorderBottomColor: var(--ak-dark-background-lighter); | ||||
| } | ||||
| /* tabs */ | ||||
|  | ||||
| /* #endregion  */ | ||||
|  | ||||
| /* #region Tabs */ | ||||
|  | ||||
| .pf-c-tabs { | ||||
|     background-color: transparent; | ||||
| } | ||||
|  | ||||
| .pf-c-tabs.pf-m-box.pf-m-vertical .pf-c-tabs__list::before { | ||||
|     border-color: transparent; | ||||
| } | ||||
|  | ||||
| .pf-c-tabs.pf-m-box .pf-c-tabs__item.pf-m-current:first-child .pf-c-tabs__link::before { | ||||
|     border-color: transparent; | ||||
| } | ||||
|  | ||||
| .pf-c-tabs__link::before { | ||||
|     border-color: transparent; | ||||
| } | ||||
|  | ||||
| .pf-c-tabs__item.pf-m-current { | ||||
|     --pf-c-tabs__link--after--BorderColor: var(--ak-accent); | ||||
| } | ||||
|  | ||||
| .pf-c-tabs__link { | ||||
|     --pf-c-tabs__link--Color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-tabs.pf-m-vertical .pf-c-tabs__link { | ||||
|     background-color: transparent; | ||||
| } | ||||
| /* table, on mobile */ | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #Region Mobile Tables */ | ||||
| @media screen and (max-width: 1200px) { | ||||
|     .pf-m-grid-xl.pf-c-table tbody:first-of-type { | ||||
|         border-top-color: var(--ak-dark-background); | ||||
| @ -104,32 +146,42 @@ body { | ||||
|         border-bottom-color: var(--ak-dark-background); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* class for pagination text */ | ||||
| .pf-c-options-menu__toggle { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| /* table icon used for expanding rows */ | ||||
| .pf-c-table__toggle-icon { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| /* expandable elements */ | ||||
| .pf-c-expandable-section__toggle-text { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-expandable-section__toggle-icon { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-expandable-section.pf-m-display-lg { | ||||
|     background-color: var(--ak-dark-background-light-ish); | ||||
| } | ||||
|  | ||||
| /* header for form group */ | ||||
| .pf-c-form__field-group-header-title-text { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-form__field-group { | ||||
|     border-bottom: 0; | ||||
| } | ||||
| /* inputs */ | ||||
|  | ||||
| /* #region Inputs */ | ||||
| optgroup, | ||||
| option { | ||||
|     color: var(--ak-dark-foreground); | ||||
| @ -138,9 +190,11 @@ select[multiple] optgroup:checked, | ||||
| select[multiple] option:checked { | ||||
|     color: var(--ak-dark-background); | ||||
| } | ||||
|  | ||||
| .pf-c-input-group { | ||||
|     --pf-c-input-group--BackgroundColor: transparent; | ||||
| } | ||||
|  | ||||
| .pf-c-form-control { | ||||
|     --pf-c-form-control--BorderTopColor: transparent !important; | ||||
|     --pf-c-form-control--BorderRightColor: transparent !important; | ||||
| @ -149,12 +203,15 @@ select[multiple] option:checked { | ||||
|     --pf-c-form-control--BackgroundColor: var(--ak-dark-background-light); | ||||
|     --pf-c-form-control--Color: var(--ak-dark-foreground) !important; | ||||
| } | ||||
|  | ||||
| .pf-c-form-control:disabled { | ||||
|     background-color: var(--ak-dark-background-light); | ||||
| } | ||||
|  | ||||
| .pf-c-form-control[readonly] { | ||||
|     background-color: var(--ak-dark-background-light) !important; | ||||
| } | ||||
|  | ||||
| .pf-c-switch__input:checked ~ .pf-c-switch__label { | ||||
|     --pf-c-switch__input--checked__label--Color: var(--ak-dark-foreground); | ||||
| } | ||||
| @ -162,38 +219,47 @@ input[type="datetime-local"]::-webkit-calendar-picker-indicator, | ||||
| input[type="date"]::-webkit-calendar-picker-indicator { | ||||
|     filter: invert(1); | ||||
| } | ||||
|  | ||||
| /* select toggle */ | ||||
| .pf-c-select__toggle::before { | ||||
|     --pf-c-select__toggle--before--BorderTopColor: var(--ak-dark-background-lighter); | ||||
|     --pf-c-select__toggle--before--BorderRightColor: var(--ak-dark-background-lighter); | ||||
|     --pf-c-select__toggle--before--BorderLeftColor: var(--ak-dark-background-lighter); | ||||
| } | ||||
|  | ||||
| .pf-c-select__toggle.pf-m-typeahead { | ||||
|     --pf-c-select__toggle--BackgroundColor: var(--ak-dark-background-light); | ||||
| } | ||||
|  | ||||
| .pf-c-select__menu { | ||||
|     --pf-c-select__menu--BackgroundColor: var(--ak-dark-background-light-ish); | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-select__menu-item { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-select__menu-wrapper:hover, | ||||
| .pf-c-select__menu-item:hover { | ||||
|     --pf-c-select__menu-item--hover--BackgroundColor: var(--ak-dark-background-lighter); | ||||
| } | ||||
|  | ||||
| .pf-c-select__menu-wrapper:focus-within, | ||||
| .pf-c-select__menu-wrapper.pf-m-focus, | ||||
| .pf-c-select__menu-item:focus, | ||||
| .pf-c-select__menu-item.pf-m-focus { | ||||
|     --pf-c-select__menu-item--focus--BackgroundColor: var(--ak-dark-background-light-ish); | ||||
| } | ||||
|  | ||||
| .pf-c-button:disabled { | ||||
|     color: var(--ak-dark-background-lighter); | ||||
| } | ||||
|  | ||||
| .pf-c-button.pf-m-plain:hover { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-button.pf-m-control { | ||||
|     --pf-c-button--after--BorderColor: var(--ak-dark-background-lighter) | ||||
|         var(--ak-dark-background-lighter) var(--pf-c-button--m-control--after--BorderBottomColor) | ||||
| @ -201,92 +267,127 @@ input[type="date"]::-webkit-calendar-picker-indicator { | ||||
|     background-color: var(--ak-dark-background-light); | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-m-tertiary, | ||||
| .pf-c-button.pf-m-tertiary { | ||||
|     --pf-c-button--after--BorderColor: var(--ak-dark-foreground-darker); | ||||
|     color: var(--ak-dark-foreground-darker); | ||||
| } | ||||
|  | ||||
| .pf-m-tertiary:hover, | ||||
| .pf-c-button.pf-m-tertiary:hover { | ||||
|     --pf-c-button--after--BorderColor: var(--ak-dark-background-lighter); | ||||
| } | ||||
|  | ||||
| .pf-c-form__label-text { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-check__label { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-dropdown__toggle::before { | ||||
|     border-color: transparent; | ||||
| } | ||||
|  | ||||
| .pf-c-dropdown__menu { | ||||
|     --pf-c-dropdown__menu--BackgroundColor: var(--ak-dark-background); | ||||
| } | ||||
|  | ||||
| .pf-c-dropdown__menu-item { | ||||
|     --pf-c-dropdown__menu-item--BackgroundColor: var(--ak-dark-background); | ||||
|     --pf-c-dropdown__menu-item--Color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-dropdown__menu-item:hover, | ||||
| .pf-c-dropdown__menu-item:focus { | ||||
|     --pf-c-dropdown__menu-item--BackgroundColor: var(--ak-dark-background-light-ish); | ||||
|     --pf-c-dropdown__menu-item--Color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-toggle-group__button { | ||||
|     color: var(--ak-dark-foreground) !important; | ||||
| } | ||||
|  | ||||
| .pf-c-toggle-group__button:not(.pf-m-selected) { | ||||
|     background-color: var(--ak-dark-background-light) !important; | ||||
| } | ||||
|  | ||||
| .pf-c-toggle-group__button.pf-m-selected { | ||||
|     color: var(--ak-dark-foreground) !important; | ||||
|     background-color: var(--pf-global--primary-color--100) !important; | ||||
| } | ||||
|  | ||||
| /* inputs help text */ | ||||
| .pf-c-form__helper-text:not(.pf-m-error) { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
| /* modal */ | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Modal */ | ||||
|  | ||||
| .pf-c-modal-box, | ||||
| .pf-c-modal-box__header, | ||||
| .pf-c-modal-box__footer, | ||||
| .pf-c-modal-box__body { | ||||
|     background-color: var(--ak-dark-background); | ||||
| } | ||||
| /* sidebar */ | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Sidebar */ | ||||
|  | ||||
| .pf-c-nav { | ||||
|     background-color: var(--ak-dark-background-light); | ||||
| } | ||||
| /* flows */ | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Flows */ | ||||
|  | ||||
| .pf-c-login__main { | ||||
|     --pf-c-login__main--BackgroundColor: var(--ak-dark-background); | ||||
| } | ||||
|  | ||||
| .pf-c-login__main-body, | ||||
| .pf-c-login__main-header, | ||||
| .pf-c-login__main-header-desc { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-login__main-footer-links-item img, | ||||
| .pf-c-login__main-footer-links-item .fas { | ||||
|     filter: invert(1); | ||||
| } | ||||
|  | ||||
| .pf-c-login__main-footer-band { | ||||
|     --pf-c-login__main-footer-band--BackgroundColor: var(--ak-dark-background-lighter); | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .form-control-static { | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
| /* notifications */ | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Notifications */ | ||||
|  | ||||
| .pf-c-drawer__panel { | ||||
|     background-color: var(--ak-dark-background); | ||||
| } | ||||
|  | ||||
| .pf-c-notification-drawer { | ||||
|     --pf-c-notification-drawer--BackgroundColor: var(--ak-dark-background); | ||||
| } | ||||
|  | ||||
| .pf-c-notification-drawer__header { | ||||
|     background-color: var(--ak-dark-background-lighter); | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-notification-drawer__list-item { | ||||
|     background-color: var(--ak-dark-background-light-ish); | ||||
|     color: var(--ak-dark-foreground); | ||||
| @ -294,46 +395,84 @@ input[type="date"]::-webkit-calendar-picker-indicator { | ||||
|         --ak-dark-background-lighter | ||||
|     ) !important; | ||||
| } | ||||
| /* data list */ | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Data List */ | ||||
|  | ||||
| .pf-c-data-list { | ||||
|     padding-inline-start: 0; | ||||
|     border-top-color: var(--ak-dark-background-lighter); | ||||
| } | ||||
|  | ||||
| .pf-c-data-list__item { | ||||
|     --pf-c-data-list__item--BackgroundColor: transparent; | ||||
|     --pf-c-data-list__item--BorderBottomColor: var(--ak-dark-background-lighter); | ||||
|     color: var(--ak-dark-foreground); | ||||
| } | ||||
| /* wizards */ | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Wizards */ | ||||
|  | ||||
| .pf-c-wizard__nav { | ||||
|     --pf-c-wizard__nav--BackgroundColor: var(--ak-dark-background-lighter); | ||||
|     --pf-c-wizard__nav--lg--BorderRightColor: transparent; | ||||
| } | ||||
|  | ||||
| .pf-c-wizard__main { | ||||
|     background-color: var(--ak-dark-background-light-ish); | ||||
| } | ||||
|  | ||||
| .pf-c-wizard__footer { | ||||
|     --pf-c-wizard__footer--BackgroundColor: var(--ak-dark-background-light-ish); | ||||
| } | ||||
|  | ||||
| .pf-c-wizard__toggle-num, | ||||
| .pf-c-wizard__nav-link::before { | ||||
|     --pf-c-wizard__nav-link--before--BackgroundColor: transparent; | ||||
| } | ||||
| /* tree view */ | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Tree view */ | ||||
|  | ||||
| .pf-c-tree-view__node { | ||||
|     --pf-c-tree-view__node--Color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-tree-view__node-toggle { | ||||
|     --pf-c-tree-view__node-toggle--Color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| .pf-c-tree-view__node:focus { | ||||
|     --pf-c-tree-view__node--focus--BackgroundColor: var(--ak-dark-background-light-ish); | ||||
| } | ||||
|  | ||||
| .pf-c-tree-view__content:hover, | ||||
| .pf-c-tree-view__content:focus-within { | ||||
|     --pf-c-tree-view__node--hover--BackgroundColor: var(--ak-dark-background-light-ish); | ||||
| } | ||||
| /* stepper */ | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Stepper */ | ||||
| .pf-c-progress-stepper__step-title { | ||||
|     --pf-c-progress-stepper__step-title--Color: var(--ak-dark-foreground); | ||||
| } | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| /* #region Mermaid */ | ||||
|  | ||||
| svg[id^="mermaid-svg-"] { | ||||
|     line[class^="messageLine"] { | ||||
|         /* | ||||
|             Mermaid's support for dynamic palette changes leaves a lot to be desired. | ||||
|             This is a workaround to keep content readable while not breaking the rest of the theme. | ||||
|          */ | ||||
|         filter: invert(1) !important; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* #endregion */ | ||||
|  | ||||
| @ -8,6 +8,7 @@ import { localized } from "@lit/localize"; | ||||
| import { LitElement, ReactiveElement } from "lit"; | ||||
|  | ||||
| import AKGlobal from "@goauthentik/common/styles/authentik.css"; | ||||
| import OneDark from "@goauthentik/common/styles/one-dark.css"; | ||||
| import ThemeDark from "@goauthentik/common/styles/theme-dark.css"; | ||||
|  | ||||
| import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api"; | ||||
| @ -70,6 +71,7 @@ export class AKElement extends LitElement { | ||||
|         styleRoot.adoptedStyleSheets = adaptCSS([ | ||||
|             ...styleRoot.adoptedStyleSheets, | ||||
|             ensureCSSStyleSheet(AKGlobal), | ||||
|             ensureCSSStyleSheet(OneDark), | ||||
|         ]); | ||||
|         this._initTheme(styleRoot); | ||||
|         this._initCustomCSS(styleRoot); | ||||
|  | ||||
| @ -1,30 +1,19 @@ | ||||
| import { docLink } from "@goauthentik/common/global"; | ||||
| import "@goauthentik/elements/Alert"; | ||||
| import { Level } from "@goauthentik/elements/Alert"; | ||||
| import { AKElement } from "@goauthentik/elements/Base"; | ||||
| import { matter } from "md-front-matter"; | ||||
| import * as showdown from "showdown"; | ||||
|  | ||||
| import { CSSResult, PropertyValues, css, html, nothing } from "lit"; | ||||
| import { CSSResult, PropertyValues, css, nothing } from "lit"; | ||||
| import { customElement, property } from "lit/decorators.js"; | ||||
| import { unsafeHTML } from "lit/directives/unsafe-html.js"; | ||||
|  | ||||
| import PFContent from "@patternfly/patternfly/components/Content/content.css"; | ||||
| import PFList from "@patternfly/patternfly/components/List/list.css"; | ||||
|  | ||||
| export interface MarkdownDocument { | ||||
|     path: string; | ||||
| } | ||||
|  | ||||
| export type Replacer = (input: string, md: MarkdownDocument) => string; | ||||
|  | ||||
| const isRelativeLink = /href="(\.[^"]*)"/gm; | ||||
| const isFile = /[^/]+\.md/; | ||||
| export type Replacer = (input: string) => string; | ||||
|  | ||||
| @customElement("ak-markdown") | ||||
| export class Markdown extends AKElement { | ||||
|     @property() | ||||
|     md: string = ""; | ||||
|     content: string = ""; | ||||
|  | ||||
|     @property() | ||||
|     meta: string = ""; | ||||
| @ -32,14 +21,7 @@ export class Markdown extends AKElement { | ||||
|     @property({ attribute: false }) | ||||
|     replacers: Replacer[] = []; | ||||
|  | ||||
|     docHtml = ""; | ||||
|     docTitle = ""; | ||||
|  | ||||
|     defaultReplacers: Replacer[] = [ | ||||
|         this.replaceAdmonitions, | ||||
|         this.replaceList, | ||||
|         this.replaceRelativeLinks, | ||||
|     ]; | ||||
|     resolvedHTML = ""; | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return [ | ||||
| @ -53,55 +35,47 @@ export class Markdown extends AKElement { | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     converter = new showdown.Converter({ metadata: true, tables: true }); | ||||
|     protected firstUpdated(changedProperties: PropertyValues): void { | ||||
|         super.updated(changedProperties); | ||||
|  | ||||
|     replaceAdmonitions(input: string): string { | ||||
|         const admonitionStart = /:::(\w+)(<br\s*\/>|\s*$)/gm; | ||||
|         const admonitionEnd = /:::/gm; | ||||
|         return ( | ||||
|             input | ||||
|                 .replaceAll(admonitionStart, "<ak-alert level='pf-m-$1'>") | ||||
|                 .replaceAll(admonitionEnd, "</ak-alert>") | ||||
|                 // Workaround for admonitions using caution instead of warning | ||||
|                 .replaceAll("pf-m-caution", Level.Warning) | ||||
|         ); | ||||
|         const headingLinks = | ||||
|             this.shadowRoot?.querySelectorAll<HTMLAnchorElement>("a.markdown-heading") ?? []; | ||||
|  | ||||
|         for (const headingLink of headingLinks) { | ||||
|             headingLink.addEventListener("click", (ev) => { | ||||
|                 ev.preventDefault(); | ||||
|  | ||||
|                 const url = new URL(headingLink.href); | ||||
|                 const elementID = url.hash.slice(1); | ||||
|  | ||||
|                 const target = this.shadowRoot?.getElementById(elementID); | ||||
|  | ||||
|                 if (!target) { | ||||
|                     console.warn(`Element with ID ${elementID} not found`); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|     replaceList(input: string): string { | ||||
|         return input.replace("<ul>", "<ul class='pf-c-list'>"); | ||||
|     } | ||||
|  | ||||
|     replaceRelativeLinks(input: string, md: MarkdownDocument): string { | ||||
|         const baseName = md.path.replace(isFile, ""); | ||||
|         const baseUrl = docLink(""); | ||||
|         return input.replace(isRelativeLink, (_match, path) => { | ||||
|             const pathName = path.replace(".md", ""); | ||||
|             const link = `docs/${baseName}${pathName}`; | ||||
|             const url = new URL(link, baseUrl).toString(); | ||||
|             return `href="${url}" _target="blank" rel="noopener noreferrer"`; | ||||
|                 target.scrollIntoView({ | ||||
|                     behavior: "smooth", | ||||
|                     block: "center", | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     willUpdate(properties: PropertyValues<this>) { | ||||
|         if (properties.has("md") || properties.has("meta")) { | ||||
|             const parsedContent = matter(this.md); | ||||
|             const parsedHTML = this.converter.makeHtml(parsedContent.content); | ||||
|             const replacers = [...this.defaultReplacers, ...this.replacers]; | ||||
|             this.docTitle = parsedContent?.data?.title ?? ""; | ||||
|             this.docHtml = replacers.reduce( | ||||
|                 (html, replacer) => replacer(html, { path: this.meta }), | ||||
|                 parsedHTML, | ||||
|         if (properties.has("content")) { | ||||
|             this.resolvedHTML = this.replacers.reduce( | ||||
|                 (html, replacer) => replacer(html), | ||||
|                 this.content, | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     render() { | ||||
|         if (!this.md) { | ||||
|             return nothing; | ||||
|         } | ||||
|         if (!this.content) return nothing; | ||||
|  | ||||
|         return html`${this.docTitle ? html`<h2>${this.docTitle}</h2>` : nothing} | ||||
|         ${unsafeHTML(this.docHtml)}`; | ||||
|         return unsafeHTML(this.resolvedHTML); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										12
									
								
								web/src/global.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								web/src/global.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -1,15 +1,19 @@ | ||||
| declare module "*.css"; | ||||
|  | ||||
| declare module "*.md" { | ||||
|     /** | ||||
|      * The HTML content of the markdown file. | ||||
|      */ | ||||
|     const html: string; | ||||
|     const metadata: { [key: string]: string }; | ||||
|     const filename: string; | ||||
|     export default html; | ||||
| } | ||||
|  | ||||
| declare module "*.mdx" { | ||||
|     /** | ||||
|      * The HTML content of the markdown file. | ||||
|      */ | ||||
|     const html: string; | ||||
|     const metadata: { [key: string]: string }; | ||||
|     const filename: string; | ||||
|     export default html; | ||||
| } | ||||
|  | ||||
| declare namespace Intl { | ||||
|  | ||||
| @ -39,7 +39,7 @@ sequenceDiagram | ||||
|     user->>op: User authentication & authorization occurs | ||||
|     op->>rp: Redirect back to the RP with an authorization code | ||||
|  | ||||
|     rect rgb(255, 255, 191) | ||||
|     rect | ||||
|  | ||||
|         rp->>op: Exchange authorization code | ||||
|         op->>rp: RP receives Access token (optionally Refresh Token) | ||||
| @ -138,6 +138,7 @@ By default, every user that has access to an application can request any of the | ||||
|  | ||||
| ```python | ||||
| # There are additional fields set in the context, use `ak_logger.debug(request.context)` to see them. | ||||
|  | ||||
| if "my-admin-scope" in request.context["oauth_scopes"]: | ||||
|     return ak_is_group_member(request.user, name="my-admin-group") | ||||
| return True | ||||
|  | ||||
| @ -163,8 +163,10 @@ body { | ||||
| } | ||||
|  | ||||
| .badge--support-community { | ||||
|     --ifm-badge-background-color: var(--ifm-color-secondary); | ||||
|     --ifm-badge-border-color: var(--ifm-color-secondary-contrast-background); | ||||
|     --ifm-badge-background-color: var( | ||||
|         --ifm-color-secondary-contrast-foreground | ||||
|     ); | ||||
|     --ifm-badge-border-color: var(--ifm-color-secondary-dark); | ||||
|     --ifm-badge-color: var(--ifm-color-secondary-contrast-background); | ||||
| } | ||||
|  | ||||
| @ -176,15 +178,14 @@ body { | ||||
|  | ||||
| .badge--support-authentik { | ||||
|     --ifm-badge-background-color: var(--ifm-color-primary); | ||||
|     --ifm-badge-border-color: var(--ifm-color-primary-contrast-foreground); | ||||
|     --ifm-badge-color: var(--ifm-color-primary-contrast-foreground); | ||||
|     --ifm-badge-border-color: var(--ifm-color-secondary); | ||||
|     --ifm-badge-color: var(--ifm-color-secondary); | ||||
| } | ||||
|  | ||||
| .badge--version { | ||||
|     --ifm-badge-background-color: var(--ifm-color-primary-contrast-background); | ||||
|     --ifm-badge-border-color: var(--ifm-color-primary-contrast-foreground); | ||||
|     --ifm-badge-color: var(--ifm-color-primary-contrast-foreground); | ||||
|     cursor: help; | ||||
| } | ||||
|  | ||||
| .badge--preview { | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Teffen Ellis
					Teffen Ellis