${this.renderInlineMarkdown(text)}
`); + } + } + paragraphLines.length = 0; + }; + + const flushCodeBlock = () => { + if (!inCodeBlock) { + return; + } + const code = this.escapeHtml(codeLines.join('\n')); + htmlParts.push(`${code}`);
+ codeLines = [];
+ inCodeBlock = false;
+ };
+
+ for (let index = 0; index < lines.length; index += 1) {
+ const line = lines[index];
+
+ if (line.startsWith('```')) {
+ flushParagraph();
+ if (inCodeBlock) {
+ flushCodeBlock();
+ } else {
+ inCodeBlock = true;
+ codeLines = [];
+ }
+ continue;
+ }
+
+ if (inCodeBlock) {
+ codeLines.push(line);
+ continue;
+ }
+
+ const trimmed = line.trim();
+ if (!trimmed) {
+ flushParagraph();
+ continue;
+ }
+
+ const headingMatch = /^(#{1,6})\s+(.*)$/.exec(trimmed);
+ if (headingMatch) {
+ flushParagraph();
+
+ const level = headingMatch[1].length;
+ const text = headingMatch[2].trim();
+ if (!title && level === 1) {
+ title = this.decodePlainText(text);
+ continue;
+ }
+
+ htmlParts.push(`${code}`);
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label: string, href: string) => {
+ const safeHref = this.escapeHtml(href);
+ const safeLabel = this.escapeHtml(label);
+
+ if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('mailto:')) {
+ return `${safeLabel}`;
+ }
+
+ return `${safeLabel}`;
+ });
+ html = html.replace(/\*\*([^*]+)\*\*/g, '$1');
+ html = html.replace(/(^|[^\*])\*([^*]+)\*/g, '$1$2');
+
+ return html;
+ }
+
+ private decodePlainText(text: string): string {
+ const html = this.renderInlineMarkdown(text);
+ const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, html)?.replace(/<[^>]+>/g, '').trim() ?? text;
+ return this.decodeHtmlEntities(sanitized);
+ }
+
+ private escapeHtml(text: string): string {
+ return text
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ }
+
+ private decodeHtmlEntities(text: string): string {
+ if (typeof document === 'undefined') {
+ return text;
+ }
+
+ const textarea = document.createElement('textarea');
+ textarea.innerHTML = text;
+ return textarea.value;
+ }
+}
diff --git a/src/app/widget-modules/components/application-frame/navigation/link/link.component.html b/src/app/widget-modules/components/application-frame/navigation/link/link.component.html
index 57a3a89..4f04d92 100644
--- a/src/app/widget-modules/components/application-frame/navigation/link/link.component.html
+++ b/src/app/widget-modules/components/application-frame/navigation/link/link.component.html
@@ -1,4 +1,6 @@