var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
var _, done = false;
for (var i = decorators.length - 1; i >= 0; i--) {
var context = {};
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
if (kind === "accessor") {
if (result === void 0) continue;
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
if (_ = accept(result.get)) descriptor.get = _;
if (_ = accept(result.set)) descriptor.set = _;
if (_ = accept(result.init)) initializers.unshift(_);
}
else if (_ = accept(result)) {
if (kind === "field") initializers.unshift(_);
else descriptor[key] = _;
}
}
if (target) Object.defineProperty(target, contextIn.name, descriptor);
done = true;
};
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
var useValue = arguments.length > 2;
for (var i = 0; i < initializers.length; i++) {
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
}
return useValue ? value : void 0;
};
import MarkdownIt from "markdown-it";
import { ContextAwareRendererComponent } from "../components.js";
import { MarkdownEvent, RendererEvent } from "../events.js";
import { Option } from "../../utils/index.js";
import { highlight, isLoadedLanguage, isSupportedLanguage } from "../../utils/highlighter.js";
import { assertNever, escapeHtml, i18n, JSX } from "#utils";
import { anchorIcon } from "./default/partials/anchor-icon.js";
import { Reflection, ReflectionKind, } from "../../models/index.js";
function getFriendlyFullName(target) {
if (target instanceof Reflection) {
return target.getFriendlyFullName();
}
if (target.parent) {
return target.name;
}
const parts = [target.name];
let current = target;
while (current.parent) {
parts.unshift(current.name);
current = current.parent;
}
return parts.join(".");
}
/**
* Implements markdown and relativeURL helpers for templates.
* @internal
*/
let MarkedPlugin = (() => {
let _classSuper = ContextAwareRendererComponent;
let _lightTheme_decorators;
let _lightTheme_initializers = [];
let _lightTheme_extraInitializers = [];
let _darkTheme_decorators;
let _darkTheme_initializers = [];
let _darkTheme_extraInitializers = [];
let _markdownItOptions_decorators;
let _markdownItOptions_initializers = [];
let _markdownItOptions_extraInitializers = [];
let _markdownLinkExternal_decorators;
let _markdownLinkExternal_initializers = [];
let _markdownLinkExternal_extraInitializers = [];
let _validation_decorators;
let _validation_initializers = [];
let _validation_extraInitializers = [];
return class MarkedPlugin extends _classSuper {
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
_lightTheme_decorators = [Option("lightHighlightTheme")];
_darkTheme_decorators = [Option("darkHighlightTheme")];
_markdownItOptions_decorators = [Option("markdownItOptions")];
_markdownLinkExternal_decorators = [Option("markdownLinkExternal")];
_validation_decorators = [Option("validation")];
__esDecorate(this, null, _lightTheme_decorators, { kind: "accessor", name: "lightTheme", static: false, private: false, access: { has: obj => "lightTheme" in obj, get: obj => obj.lightTheme, set: (obj, value) => { obj.lightTheme = value; } }, metadata: _metadata }, _lightTheme_initializers, _lightTheme_extraInitializers);
__esDecorate(this, null, _darkTheme_decorators, { kind: "accessor", name: "darkTheme", static: false, private: false, access: { has: obj => "darkTheme" in obj, get: obj => obj.darkTheme, set: (obj, value) => { obj.darkTheme = value; } }, metadata: _metadata }, _darkTheme_initializers, _darkTheme_extraInitializers);
__esDecorate(this, null, _markdownItOptions_decorators, { kind: "accessor", name: "markdownItOptions", static: false, private: false, access: { has: obj => "markdownItOptions" in obj, get: obj => obj.markdownItOptions, set: (obj, value) => { obj.markdownItOptions = value; } }, metadata: _metadata }, _markdownItOptions_initializers, _markdownItOptions_extraInitializers);
__esDecorate(this, null, _markdownLinkExternal_decorators, { kind: "accessor", name: "markdownLinkExternal", static: false, private: false, access: { has: obj => "markdownLinkExternal" in obj, get: obj => obj.markdownLinkExternal, set: (obj, value) => { obj.markdownLinkExternal = value; } }, metadata: _metadata }, _markdownLinkExternal_initializers, _markdownLinkExternal_extraInitializers);
__esDecorate(this, null, _validation_decorators, { kind: "accessor", name: "validation", static: false, private: false, access: { has: obj => "validation" in obj, get: obj => obj.validation, set: (obj, value) => { obj.validation = value; } }, metadata: _metadata }, _validation_initializers, _validation_extraInitializers);
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
}
#lightTheme_accessor_storage = __runInitializers(this, _lightTheme_initializers, void 0);
get lightTheme() { return this.#lightTheme_accessor_storage; }
set lightTheme(value) { this.#lightTheme_accessor_storage = value; }
#darkTheme_accessor_storage = (__runInitializers(this, _lightTheme_extraInitializers), __runInitializers(this, _darkTheme_initializers, void 0));
get darkTheme() { return this.#darkTheme_accessor_storage; }
set darkTheme(value) { this.#darkTheme_accessor_storage = value; }
#markdownItOptions_accessor_storage = (__runInitializers(this, _darkTheme_extraInitializers), __runInitializers(this, _markdownItOptions_initializers, void 0));
get markdownItOptions() { return this.#markdownItOptions_accessor_storage; }
set markdownItOptions(value) { this.#markdownItOptions_accessor_storage = value; }
#markdownLinkExternal_accessor_storage = (__runInitializers(this, _markdownItOptions_extraInitializers), __runInitializers(this, _markdownLinkExternal_initializers, void 0));
get markdownLinkExternal() { return this.#markdownLinkExternal_accessor_storage; }
set markdownLinkExternal(value) { this.#markdownLinkExternal_accessor_storage = value; }
#validation_accessor_storage = (__runInitializers(this, _markdownLinkExternal_extraInitializers), __runInitializers(this, _validation_initializers, void 0));
get validation() { return this.#validation_accessor_storage; }
set validation(value) { this.#validation_accessor_storage = value; }
parser = __runInitializers(this, _validation_extraInitializers);
renderedRelativeLinks = [];
/**
* This needing to be here really feels hacky... probably some nicer way to do this.
* Revisit in 0.28.
*/
renderContext = null;
lastHeaderSlug = "";
constructor(owner) {
super(owner);
this.owner.on(MarkdownEvent.PARSE, this.onParseMarkdown.bind(this));
this.owner.on(RendererEvent.END, this.onEnd.bind(this));
}
/**
* Highlight the syntax of the given text using Shiki.
*
* @param text The text that should be highlighted.
* @param lang The language that should be used to highlight the string.
* @return A html string with syntax highlighting.
*/
getHighlighted(text, lang) {
lang = lang || "typescript";
lang = lang.toLowerCase();
if (!isSupportedLanguage(lang)) {
this.application.logger.warn(i18n.unsupported_highlight_language_0_not_highlighted_in_comment_for_1(lang, getFriendlyFullName(this.page?.model || { name: "(unknown)" })));
return text;
}
if (!isLoadedLanguage(lang)) {
this.application.logger.warn(i18n.unloaded_language_0_not_highlighted_in_comment_for_1(lang, getFriendlyFullName(this.page?.model || { name: "(unknown)" })));
return text;
}
return highlight(text, lang);
}
/**
* Parse the given markdown string and return the resulting html.
*
* @param input The markdown string that should be parsed.
* @returns The resulting html string.
*/
parseMarkdown(input, page, context) {
let markdown = input;
if (typeof markdown !== "string") {
markdown = this.displayPartsToMarkdown(page, context, markdown);
}
this.renderContext = context;
const event = new MarkdownEvent(page, markdown, markdown);
this.owner.trigger(MarkdownEvent.PARSE, event);
this.renderContext = null;
return event.parsedText;
}
displayPartsToMarkdown(page, context, parts) {
const useHtml = !!this.markdownItOptions["html"];
const result = [];
for (const part of parts) {
switch (part.kind) {
case "text":
case "code":
result.push(part.text);
break;
case "inline-tag":
switch (part.tag) {
case "@label":
case "@inheritdoc": // Shouldn't happen
break; // Not rendered.
case "@link":
case "@linkcode":
case "@linkplain": {
if (part.target) {
let url;
let kindClass;
if (typeof part.target === "string") {
url = part.target === "#" ? undefined : part.target;
}
else if ("id" in part.target) {
// No point in trying to resolve a ReflectionSymbolId at this point, we've already
// tried and failed during the resolution step. Warnings related to those broken links
// have already been emitted.
kindClass = ReflectionKind.classString(part.target.kind);
if (context.router.hasUrl(part.target)) {
url = context.urlTo(part.target);
}
// If we don't have a URL the user probably linked to some deeply nested property
// which doesn't get an assigned URL. We'll walk upwards until we find a reflection
// which has a URL and link to that instead.
if (typeof url === "undefined") {
// Walk upwards to find something we can link to.
let target = part.target.parent;
while (!context.router.hasUrl(target)) {
target = target.parent;
}
// We know we'll always end up with a URL here eventually as the
// project always has a URL.
url = context.urlTo(target);
if (this.validation.rewrittenLink) {
this.application.logger.warn(i18n
.reflection_0_links_to_1_with_text_2_but_resolved_to_3(page.model.getFriendlyFullName(), part.target.getFriendlyFullName(), part.text, target.getFriendlyFullName()));
}
}
// If the url goes to this page, render as `#`
// to go to the top of the page.
if (url == "") {
url = "#";
}
}
if (useHtml) {
const text = part.tag === "@linkcode" ? `${part.text}` : part.text;
result.push(url
? `${text}`
: part.text);
}
else {
const text = part.tag === "@linkcode" ? "`" + part.text + "`" : part.text;
result.push(url ? `[${text}](${url})` : text);
}
}
else {
result.push(part.text);
}
break;
}
default:
// Hmm... probably want to be able to render these somehow, so custom inline tags can be given
// special rendering rules. Future capability. For now, just render their text.
result.push(`{${part.tag} ${part.text}}`);
break;
}
break;
case "relative-link":
switch (typeof part.target) {
case "number": {
const refl = page.project.files.resolve(part.target, page.model.project);
let url;
if (typeof refl === "object") {
url = context.urlTo(refl);
}
else {
const fileName = page.project.files.getName(part.target);
if (fileName) {
url = context.relativeURL(`media/${fileName}`);
}
}
if (typeof url !== "undefined") {
if (part.targetAnchor) {
url += "#" + part.targetAnchor;
if (typeof refl === "object") {
this.renderedRelativeLinks.push({
source: this.page.model,
target: refl,
link: part,
});
}
}
result.push(url);
break;
}
}
// fall through
case "undefined":
result.push(part.text);
break;
}
break;
default:
assertNever(part);
}
}
return result.join("");
}
onEnd() {
for (const { source, target, link } of this.renderedRelativeLinks) {
const slugger = this.owner.router.getSlugger(target);
if (!slugger.hasAnchor(link.targetAnchor)) {
this.application.logger.warn(i18n.reflection_0_links_to_1_but_anchor_does_not_exist_try_2(getFriendlyFullName(source), link.text, slugger
.getSimilarAnchors(link.targetAnchor)
.map((a) => link.text.replace(/#.*/, "#" + a))
.join("\n\t")));
}
}
// In case we're in watch mode
this.renderedRelativeLinks = [];
}
/**
* Triggered before the renderer starts rendering a project.
*
* @param event An event object describing the current render operation.
*/
onBeginRenderer(event) {
super.onBeginRenderer(event);
this.setupParser();
}
getSlugger() {
return this.owner.router.getSlugger(this.page.model);
}
/**
* Creates an object with options that are passed to the markdown parser.
*
* @returns The options object for the markdown parser.
*/
setupParser() {
this.parser = MarkdownIt({
...this.markdownItOptions,
highlight: (code, lang) => {
code = this.getHighlighted(code, lang || "ts");
code = code.replace(/\n$/, "") + "\n";
if (!lang) {
return `
${code}\n`;
}
return `${code}\n`;
},
});
githubAlertMarkdownPlugin(this.parser);
const loader = this.application.options.getValue("markdownItLoader");
loader(this.parser);
// Add anchor links for headings in readme, and add them to the "On this page" section
this.parser.renderer.rules["heading_open"] = (tokens, idx) => {
const token = tokens[idx];
const content = getTokenTextContent(tokens[idx + 1]);
const level = token.markup.length;
const slug = this.getSlugger().slug(content);
this.lastHeaderSlug = slug;
this.page.pageHeadings.push({
link: `#${slug}`,
text: content,
level,
});
return `<${token.tag} id="${slug}" class="tsd-anchor-link">`;
};
this.parser.renderer.rules["heading_close"] = (tokens, idx) => {
return `${JSX.renderElement(anchorIcon(this.renderContext, this.lastHeaderSlug))}${tokens[idx].tag}>`;
};
// Rewrite anchor links inline in a readme file to links targeting the `md:` prefixed anchors
// that TypeDoc creates.
this.parser.renderer.rules["link_open"] = (tokens, idx, options, _env, self) => {
const token = tokens[idx];
const href = token.attrGet("href");
if (href) {
// Note: This doesn't catch @link tags to reflections as those
// will be relative links. This will likely have to change with
// the introduction of support for customized routers whenever
// that becomes a real thing.
if (this.markdownLinkExternal &&
/^https?:\/\//i.test(href) &&
!(href + "/").startsWith(this.hostedBaseUrl)) {
token.attrSet("target", "_blank");
const classes = token.attrGet("class")?.split(" ") || [];
classes.push("external");
token.attrSet("class", classes.join(" "));
}
token.attrSet("href", href);
}
return self.renderToken(tokens, idx, options);
};
this.parser.renderer.rules["alert_open"] = (tokens, idx) => {
const icon = this.renderContext.icons[tokens[idx].attrGet("icon")];
const iconHtml = JSX.renderElement(icon());
return `