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:
@ -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") ?? [];
|
||||
|
||||
replaceList(input: string): string {
|
||||
return input.replace("<ul>", "<ul class='pf-c-list'>");
|
||||
}
|
||||
for (const headingLink of headingLinks) {
|
||||
headingLink.addEventListener("click", (ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
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"`;
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user