aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--templates/org/home.tmpl2
-rw-r--r--templates/projects/list.tmpl4
-rw-r--r--templates/repo/editor/edit.tmpl4
-rw-r--r--templates/repo/issue/fields/markdown.tmpl2
-rw-r--r--templates/repo/issue/milestone_issues.tmpl2
-rw-r--r--templates/repo/issue/milestones.tmpl4
-rw-r--r--templates/repo/release/list.tmpl2
-rw-r--r--templates/repo/settings/lfs_file.tmpl4
-rw-r--r--templates/repo/view_file.tmpl2
-rw-r--r--templates/repo/wiki/view.tmpl8
-rw-r--r--templates/shared/combomarkdowneditor.tmpl2
-rw-r--r--templates/user/dashboard/feeds.tmpl2
-rw-r--r--templates/user/dashboard/milestones.tmpl6
-rw-r--r--templates/user/profile.tmpl2
-rw-r--r--web_src/css/editor/fileeditor.css9
-rw-r--r--web_src/css/markup/content.css2
-rw-r--r--web_src/css/shared/milestone.css2
-rw-r--r--web_src/js/features/repo-editor.ts4
-rw-r--r--web_src/js/features/repo-issue-edit.ts3
-rw-r--r--web_src/js/features/repo-wiki.ts4
-rw-r--r--web_src/js/index.ts3
-rw-r--r--web_src/js/markup/asciicast.ts18
-rw-r--r--web_src/js/markup/codecopy.ts17
-rw-r--r--web_src/js/markup/content.ts25
-rw-r--r--web_src/js/markup/math.ts42
-rw-r--r--web_src/js/markup/mermaid.ts126
-rw-r--r--web_src/js/markup/tasklist.ts124
-rw-r--r--web_src/js/modules/observer.ts3
-rw-r--r--web_src/js/render/pdf.ts10
29 files changed, 203 insertions, 235 deletions
diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl
index 826642db42..cffdfabfaa 100644
--- a/templates/org/home.tmpl
+++ b/templates/org/home.tmpl
@@ -6,7 +6,7 @@
<div class="ui mobile reversed stackable grid">
<div class="ui {{if .ShowMemberAndTeamTab}}eleven wide{{end}} column">
{{if .ProfileReadmeContent}}
- <div id="readme_profile" class="markup" data-profile-view-as-member="{{.IsViewingOrgAsMember}}">{{.ProfileReadmeContent}}</div>
+ <div id="readme_profile" class="render-content markup" data-profile-view-as-member="{{.IsViewingOrgAsMember}}">{{.ProfileReadmeContent}}</div>
{{end}}
{{template "shared/repo_search" .}}
{{template "explore/repo_list" .}}
diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl
index 5d40653dc6..48083811e7 100644
--- a/templates/projects/list.tmpl
+++ b/templates/projects/list.tmpl
@@ -74,9 +74,7 @@
{{end}}
</div>
{{if .Description}}
- <div class="content">
- {{.RenderedContent}}
- </div>
+ <div class="render-content markup">{{.RenderedContent}}</div>
{{end}}
</li>
{{end}}
diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl
index 577a2be9ad..ae8a60c20c 100644
--- a/templates/repo/editor/edit.tmpl
+++ b/templates/repo/editor/edit.tmpl
@@ -45,10 +45,10 @@
data-line-wrap-extensions="{{.LineWrapExtensions}}">{{.FileContent}}</textarea>
<div class="editor-loading is-loading"></div>
</div>
- <div class="ui tab markup tw-px-4 tw-py-3" data-tab="preview">
+ <div class="ui tab tw-px-4 tw-py-3" data-tab="preview">
{{ctx.Locale.Tr "loading"}}
</div>
- <div class="ui tab diff edit-diff" data-tab="diff">
+ <div class="ui tab" data-tab="diff">
<div class="tw-p-16"></div>
</div>
</div>
diff --git a/templates/repo/issue/fields/markdown.tmpl b/templates/repo/issue/fields/markdown.tmpl
index da8f5e6bdf..dbf4b71ba8 100644
--- a/templates/repo/issue/fields/markdown.tmpl
+++ b/templates/repo/issue/fields/markdown.tmpl
@@ -1,3 +1,3 @@
<div class="field {{if not .item.VisibleOnForm}}tw-hidden{{end}}">
- <div class="markup">{{ctx.RenderUtils.MarkdownToHtml .item.Attributes.value}}</div>
+ <div class="render-content markup">{{ctx.RenderUtils.MarkdownToHtml .item.Attributes.value}}</div>
</div>
diff --git a/templates/repo/issue/milestone_issues.tmpl b/templates/repo/issue/milestone_issues.tmpl
index abb4e3290d..ac5d7f16dd 100644
--- a/templates/repo/issue/milestone_issues.tmpl
+++ b/templates/repo/issue/milestone_issues.tmpl
@@ -22,7 +22,7 @@
{{end}}
</div>
{{if .Milestone.RenderedContent}}
- <div class="markup content tw-mb-4">
+ <div class="render-content markup tw-mb-4">
{{.Milestone.RenderedContent}}
</div>
{{end}}
diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl
index e7dfe08ee0..5701c1faa6 100644
--- a/templates/repo/issue/milestones.tmpl
+++ b/templates/repo/issue/milestones.tmpl
@@ -81,9 +81,7 @@
{{end}}
</div>
{{if .Content}}
- <div class="markup content">
- {{.RenderedContent}}
- </div>
+ <div class="render-content markup">{{.RenderedContent}}</div>
{{end}}
</li>
{{end}}
diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl
index 041890ca9c..88bd85ef4d 100644
--- a/templates/repo/release/list.tmpl
+++ b/templates/repo/release/list.tmpl
@@ -64,7 +64,7 @@
| <span class="ahead"><a href="{{$.RepoLink}}/compare/{{$release.TagName | PathEscapeSegments}}...{{$release.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" $release.NumCommitsBehind}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" $release.TargetBehind}}</span>
{{end}}
</p>
- <div class="markup desc">
+ <div class="render-content markup">
{{$release.RenderedNote}}
</div>
<div class="divider"></div>
diff --git a/templates/repo/settings/lfs_file.tmpl b/templates/repo/settings/lfs_file.tmpl
index f6fac05b69..9f72d764ae 100644
--- a/templates/repo/settings/lfs_file.tmpl
+++ b/templates/repo/settings/lfs_file.tmpl
@@ -13,7 +13,7 @@
</h4>
<div class="ui bottom attached table unstackable segment">
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}}
- <div class="file-view{{if .IsMarkup}} markup {{.MarkupType}}{{else if .IsPlainText}} plain-text{{else if .IsTextFile}} code-view{{end}}">
+ <div class="file-view {{if .IsPlainText}}plain-text{{else if .IsTextFile}}code-view{{end}}">
{{if .IsFileTooLarge}}
{{template "shared/filetoolarge" dict "RawFileLink" .RawFileLink}}
{{else if not .FileSize}}
@@ -31,7 +31,7 @@
<strong>{{ctx.Locale.Tr "repo.audio_not_supported_in_browser"}}</strong>
</audio>
{{else if .IsPDFFile}}
- <div class="pdf-content is-loading" data-src="{{$.RawFileLink}}" data-fallback-button-text="{{ctx.Locale.Tr "diff.view_file"}}"></div>
+ <div class="pdf-content is-loading" data-global-init="initPdfViewer" data-src="{{$.RawFileLink}}" data-fallback-button-text="{{ctx.Locale.Tr "diff.view_file"}}"></div>
{{else}}
<a href="{{$.RawFileLink}}" rel="nofollow" class="tw-p-4">{{ctx.Locale.Tr "repo.file_view_raw"}}</a>
{{end}}
diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl
index 4907d87301..9f1b2a5e8f 100644
--- a/templates/repo/view_file.tmpl
+++ b/templates/repo/view_file.tmpl
@@ -108,7 +108,7 @@
<strong>{{ctx.Locale.Tr "repo.audio_not_supported_in_browser"}}</strong>
</audio>
{{else if .IsPDFFile}}
- <div class="pdf-content is-loading" data-src="{{$.RawFileLink}}" data-fallback-button-text="{{ctx.Locale.Tr "repo.diff.view_file"}}"></div>
+ <div class="pdf-content is-loading" data-global-init="initPdfViewer" data-src="{{$.RawFileLink}}" data-fallback-button-text="{{ctx.Locale.Tr "repo.diff.view_file"}}"></div>
{{else}}
<a href="{{$.RawFileLink}}" rel="nofollow" class="tw-p-4">{{ctx.Locale.Tr "repo.file_view_raw"}}</a>
{{end}}
diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl
index 843a977e3e..efb614280a 100644
--- a/templates/repo/wiki/view.tmpl
+++ b/templates/repo/wiki/view.tmpl
@@ -63,18 +63,18 @@
<div class="wiki-content-parts">
{{if .sidebarTocContent}}
- <div class="markup wiki-content-sidebar wiki-content-toc">
+ <div class="render-content markup wiki-content-sidebar wiki-content-toc">
{{.sidebarTocContent | SafeHTML}}
</div>
{{end}}
- <div class="markup wiki-content-main {{if or .sidebarTocContent .sidebarPresent}}with-sidebar{{end}}">
+ <div class="render-content markup wiki-content-main {{if or .sidebarTocContent .sidebarPresent}}with-sidebar{{end}}">
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}}
{{.content | SafeHTML}}
</div>
{{if .sidebarPresent}}
- <div class="markup wiki-content-sidebar">
+ <div class="render-content markup wiki-content-sidebar">
{{if and .CanWriteWiki (not .Repository.IsMirror)}}
<a class="tw-float-right muted" href="{{.RepoLink}}/wiki/_Sidebar?action=_edit" aria-label="{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}">{{svg "octicon-pencil"}}</a>
{{end}}
@@ -86,7 +86,7 @@
<div class="tw-clear-both"></div>
{{if .footerPresent}}
- <div class="markup wiki-content-footer">
+ <div class="render-content markup wiki-content-footer">
{{if and .CanWriteWiki (not .Repository.IsMirror)}}
<a class="tw-float-right muted" href="{{.RepoLink}}/wiki/_Footer?action=_edit" aria-label="{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}">{{svg "octicon-pencil"}}</a>
{{end}}
diff --git a/templates/shared/combomarkdowneditor.tmpl b/templates/shared/combomarkdowneditor.tmpl
index b1c3b29cf3..fa3e6c6ade 100644
--- a/templates/shared/combomarkdowneditor.tmpl
+++ b/templates/shared/combomarkdowneditor.tmpl
@@ -81,7 +81,7 @@
}
</script>
</div>
- <div class="ui tab markup" data-tab-panel="markdown-previewer">
+ <div class="ui tab" data-tab-panel="markdown-previewer">
{{ctx.Locale.Tr "loading"}}
</div>
<div class="markdown-add-table-panel tippy-target">
diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl
index 739be586b8..47686dd442 100644
--- a/templates/user/dashboard/feeds.tmpl
+++ b/templates/user/dashboard/feeds.tmpl
@@ -110,7 +110,7 @@
<a href="{{.GetCommentLink ctx}}" class="text truncate issue title">{{(.GetIssueTitle ctx) | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
{{$comment := index .GetIssueInfos 1}}
{{if $comment}}
- <div class="markup tw-text-14">{{ctx.RenderUtils.MarkdownToHtml $comment}}</div>
+ <div class="render-content markup tw-text-14">{{ctx.RenderUtils.MarkdownToHtml $comment}}</div>
{{end}}
{{else if .GetOpType.InActions "merge_pull_request"}}
<div class="flex-item-body text black">{{index .GetIssueInfos 1}}</div>
diff --git a/templates/user/dashboard/milestones.tmpl b/templates/user/dashboard/milestones.tmpl
index 7c1a69a6f5..d0fe0abbc9 100644
--- a/templates/user/dashboard/milestones.tmpl
+++ b/templates/user/dashboard/milestones.tmpl
@@ -33,7 +33,7 @@
{{end}}
</div>
</div>
- <div class="flex-container-main content">
+ <div class="flex-container-main">
<div class="list-header">
<div class="small-menu-items ui compact tiny menu list-header-toggle">
<a class="item{{if not .IsShowClosed}} active{{end}}" href="?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open&q={{$.Keyword}}">
@@ -140,9 +140,7 @@
{{end}}
</div>
{{if .Content}}
- <div class="markup content">
- {{.RenderedContent}}
- </div>
+ <div class="render-content markup">{{.RenderedContent}}</div>
{{end}}
</li>
{{end}}
diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl
index 345872b00d..e5c3412ddd 100644
--- a/templates/user/profile.tmpl
+++ b/templates/user/profile.tmpl
@@ -26,7 +26,7 @@
{{else if eq .TabName "followers"}}
{{template "repo/user_cards" .}}
{{else if eq .TabName "overview"}}
- <div id="readme_profile" class="markup">{{.ProfileReadmeContent}}</div>
+ <div id="readme_profile" class="render-content markup">{{.ProfileReadmeContent}}</div>
{{else if eq .TabName "organizations"}}
{{template "repo/user_cards" .}}
{{else}}
diff --git a/web_src/css/editor/fileeditor.css b/web_src/css/editor/fileeditor.css
index 444ee8c7e7..698efffc99 100644
--- a/web_src/css/editor/fileeditor.css
+++ b/web_src/css/editor/fileeditor.css
@@ -74,12 +74,3 @@
padding: 1rem;
text-align: center;
}
-
-.edit-diff {
- padding: 0 !important;
-}
-
-.edit-diff > div > .ui.table {
- border-top: none !important;
- border-bottom: none !important;
-}
diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css
index d2dcf2ec6e..fabf5b3a8f 100644
--- a/web_src/css/markup/content.css
+++ b/web_src/css/markup/content.css
@@ -535,7 +535,7 @@
user-select: none;
}
-.markup-render {
+.markup-content-iframe {
display: block;
border: none;
width: 100%;
diff --git a/web_src/css/shared/milestone.css b/web_src/css/shared/milestone.css
index 91e6b5e387..47e822f8d3 100644
--- a/web_src/css/shared/milestone.css
+++ b/web_src/css/shared/milestone.css
@@ -12,7 +12,7 @@
border-top: 1px solid var(--color-secondary);
}
-.milestone-card .content {
+.milestone-card .render-content {
padding-top: 10px;
}
diff --git a/web_src/js/features/repo-editor.ts b/web_src/js/features/repo-editor.ts
index 0f3fb7bbcf..0f77508f70 100644
--- a/web_src/js/features/repo-editor.ts
+++ b/web_src/js/features/repo-editor.ts
@@ -1,7 +1,6 @@
import {htmlEscape} from 'escape-goat';
import {createCodeEditor} from './codeeditor.ts';
import {hideElem, queryElems, showElem, createElementFromHTML} from '../utils/dom.ts';
-import {initMarkupContent} from '../markup/content.ts';
import {attachRefIssueContextPopup} from './contextpopup.ts';
import {POST} from '../modules/fetch.ts';
import {initDropzone} from './dropzone.ts';
@@ -199,7 +198,6 @@ export function initRepoEditor() {
}
export function renderPreviewPanelContent(previewPanel: Element, content: string) {
- previewPanel.innerHTML = content;
- initMarkupContent();
+ previewPanel.innerHTML = `<div class="render-content markup">${content}</div>`;
attachRefIssueContextPopup(previewPanel.querySelectorAll('p .ref-issue'));
}
diff --git a/web_src/js/features/repo-issue-edit.ts b/web_src/js/features/repo-issue-edit.ts
index f3e8a0beba..b3de91c3bd 100644
--- a/web_src/js/features/repo-issue-edit.ts
+++ b/web_src/js/features/repo-issue-edit.ts
@@ -4,7 +4,6 @@ import {POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {hideElem, querySingleVisibleElem, showElem, type DOMEvent} from '../utils/dom.ts';
import {attachRefIssueContextPopup} from './contextpopup.ts';
-import {initCommentContent, initMarkupContent} from '../markup/content.ts';
import {triggerUploadStateChanged} from './comp/EditorUpload.ts';
import {convertHtmlToMarkdown} from '../markup/html2markdown.ts';
import {applyAreYouSure, reinitializeAreYouSure} from '../vendor/jquery.are-you-sure.ts';
@@ -74,8 +73,6 @@ async function tryOnEditContent(e: DOMEvent<MouseEvent>) {
content.querySelector('.dropzone-attachments').outerHTML = data.attachments;
}
comboMarkdownEditor.dropzoneSubmitReload();
- initMarkupContent();
- initCommentContent();
} catch (error) {
showErrorToast(`Failed to save the content: ${error}`);
console.error(error);
diff --git a/web_src/js/features/repo-wiki.ts b/web_src/js/features/repo-wiki.ts
index 9ffa8a3275..f94d3ef3d1 100644
--- a/web_src/js/features/repo-wiki.ts
+++ b/web_src/js/features/repo-wiki.ts
@@ -1,4 +1,3 @@
-import {initMarkupContent} from '../markup/content.ts';
import {validateTextareaNonEmpty, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
import {fomanticMobileScreen} from '../modules/fomantic.ts';
import {POST} from '../modules/fetch.ts';
@@ -31,8 +30,7 @@ async function initRepoWikiFormEditor() {
const response = await POST(editor.previewUrl, {data: formData});
const data = await response.text();
lastContent = newContent;
- previewTarget.innerHTML = `<div class="markup ui segment">${data}</div>`;
- initMarkupContent();
+ previewTarget.innerHTML = `<div class="render-content markup ui segment">${data}</div>`;
} catch (error) {
console.error('Error rendering preview:', error);
} finally {
diff --git a/web_src/js/index.ts b/web_src/js/index.ts
index f48074316e..c1ec78539d 100644
--- a/web_src/js/index.ts
+++ b/web_src/js/index.ts
@@ -18,7 +18,7 @@ import {initNotificationCount, initNotificationsTable} from './features/notifica
import {initRepoIssueContentHistory} from './features/repo-issue-content.ts';
import {initStopwatch} from './features/stopwatch.ts';
import {initFindFileInRepo} from './features/repo-findfile.ts';
-import {initCommentContent, initMarkupContent} from './markup/content.ts';
+import {initMarkupContent} from './markup/content.ts';
import {initPdfViewer} from './render/pdf.ts';
import {initUserAuthOauth2, initUserCheckAppUrl} from './features/user-auth.ts';
@@ -102,7 +102,6 @@ onDomReady(() => {
initHeadNavbarContentToggle,
initFootLanguageMenu,
- initCommentContent,
initContextPopups,
initHeatmap,
initImageDiff,
diff --git a/web_src/js/markup/asciicast.ts b/web_src/js/markup/asciicast.ts
index 9baae6ba85..22dbff2d46 100644
--- a/web_src/js/markup/asciicast.ts
+++ b/web_src/js/markup/asciicast.ts
@@ -1,6 +1,6 @@
-export async function renderAsciicast() {
- const els = document.querySelectorAll('.asciinema-player-container');
- if (!els.length) return;
+export async function initMarkupRenderAsciicast(elMarkup: HTMLElement): Promise<void> {
+ const el = elMarkup.querySelector('.asciinema-player-container');
+ if (!el) return;
const [player] = await Promise.all([
// @ts-expect-error: module exports no types
@@ -8,11 +8,9 @@ export async function renderAsciicast() {
import(/* webpackChunkName: "asciinema-player" */'asciinema-player/dist/bundle/asciinema-player.css'),
]);
- for (const el of els) {
- player.create(el.getAttribute('data-asciinema-player-src'), el, {
- // poster (a preview frame) to display until the playback is started.
- // Set it to 1 hour (also means the end if the video is shorter) to make the preview frame show more.
- poster: 'npt:1:0:0',
- });
- }
+ player.create(el.getAttribute('data-asciinema-player-src'), el, {
+ // poster (a preview frame) to display until the playback is started.
+ // Set it to 1 hour (also means the end if the video is shorter) to make the preview frame show more.
+ poster: 'npt:1:0:0',
+ });
}
diff --git a/web_src/js/markup/codecopy.ts b/web_src/js/markup/codecopy.ts
index f45b7a8e04..4430256848 100644
--- a/web_src/js/markup/codecopy.ts
+++ b/web_src/js/markup/codecopy.ts
@@ -7,15 +7,12 @@ export function makeCodeCopyButton(): HTMLButtonElement {
return button;
}
-export function renderCodeCopy(): void {
- const els = document.querySelectorAll('.markup .code-block code');
- if (!els.length) return;
+export function initMarkupCodeCopy(elMarkup: HTMLElement): void {
+ const el = elMarkup.querySelector('.code-block code'); // .markup .code-block code
+ if (!el || !el.textContent) return;
- for (const el of els) {
- if (!el.textContent) continue;
- const btn = makeCodeCopyButton();
- // remove final trailing newline introduced during HTML rendering
- btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, ''));
- el.after(btn);
- }
+ const btn = makeCodeCopyButton();
+ // remove final trailing newline introduced during HTML rendering
+ btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, ''));
+ el.after(btn);
}
diff --git a/web_src/js/markup/content.ts b/web_src/js/markup/content.ts
index b9190b15ce..55db4aa810 100644
--- a/web_src/js/markup/content.ts
+++ b/web_src/js/markup/content.ts
@@ -1,18 +1,17 @@
-import {renderMermaid} from './mermaid.ts';
-import {renderMath} from './math.ts';
-import {renderCodeCopy} from './codecopy.ts';
-import {renderAsciicast} from './asciicast.ts';
+import {initMarkupCodeMermaid} from './mermaid.ts';
+import {initMarkupCodeMath} from './math.ts';
+import {initMarkupCodeCopy} from './codecopy.ts';
+import {initMarkupRenderAsciicast} from './asciicast.ts';
import {initMarkupTasklist} from './tasklist.ts';
+import {registerGlobalSelectorFunc} from '../modules/observer.ts';
// code that runs for all markup content
export function initMarkupContent(): void {
- renderMermaid();
- renderMath();
- renderCodeCopy();
- renderAsciicast();
-}
-
-// code that only runs for comments
-export function initCommentContent(): void {
- initMarkupTasklist();
+ registerGlobalSelectorFunc('.markup', (el: HTMLElement) => {
+ initMarkupCodeCopy(el);
+ initMarkupTasklist(el);
+ initMarkupCodeMermaid(el);
+ initMarkupCodeMath(el);
+ initMarkupRenderAsciicast(el);
+ });
}
diff --git a/web_src/js/markup/math.ts b/web_src/js/markup/math.ts
index 4777805e3c..2a4468bf2e 100644
--- a/web_src/js/markup/math.ts
+++ b/web_src/js/markup/math.ts
@@ -11,9 +11,9 @@ function targetElement(el: Element): {target: Element, displayAsBlock: boolean}
};
}
-export async function renderMath(): Promise<void> {
- const els = document.querySelectorAll('.markup code.language-math');
- if (!els.length) return;
+export async function initMarkupCodeMath(elMarkup: HTMLElement): Promise<void> {
+ const el = elMarkup.querySelector('code.language-math'); // .markup code.language-math'
+ if (!el) return;
const [{default: katex}] = await Promise.all([
import(/* webpackChunkName: "katex" */'katex'),
@@ -24,25 +24,23 @@ export async function renderMath(): Promise<void> {
const MAX_SIZE = 25;
const MAX_EXPAND = 1000;
- for (const el of els) {
- const {target, displayAsBlock} = targetElement(el);
- if (target.hasAttribute('data-render-done')) continue;
- const source = el.textContent;
+ const {target, displayAsBlock} = targetElement(el);
+ if (target.hasAttribute('data-render-done')) return;
+ const source = el.textContent;
- if (source.length > MAX_CHARS) {
- displayError(target, new Error(`Math source of ${source.length} characters exceeds the maximum allowed length of ${MAX_CHARS}.`));
- continue;
- }
- try {
- const tempEl = document.createElement(displayAsBlock ? 'p' : 'span');
- katex.render(source, tempEl, {
- maxSize: MAX_SIZE,
- maxExpand: MAX_EXPAND,
- displayMode: displayAsBlock, // katex: true for display (block) mode, false for inline mode
- });
- target.replaceWith(tempEl);
- } catch (error) {
- displayError(target, error);
- }
+ if (source.length > MAX_CHARS) {
+ displayError(target, new Error(`Math source of ${source.length} characters exceeds the maximum allowed length of ${MAX_CHARS}.`));
+ return;
+ }
+ try {
+ const tempEl = document.createElement(displayAsBlock ? 'p' : 'span');
+ katex.render(source, tempEl, {
+ maxSize: MAX_SIZE,
+ maxExpand: MAX_EXPAND,
+ displayMode: displayAsBlock, // katex: true for display (block) mode, false for inline mode
+ });
+ target.replaceWith(tempEl);
+ } catch (error) {
+ displayError(target, error);
}
}
diff --git a/web_src/js/markup/mermaid.ts b/web_src/js/markup/mermaid.ts
index 2dbed280c2..b4bf3153ea 100644
--- a/web_src/js/markup/mermaid.ts
+++ b/web_src/js/markup/mermaid.ts
@@ -10,9 +10,9 @@ body {margin: 0; padding: 0; overflow: hidden}
#mermaid {display: block; margin: 0 auto}
blockquote, dd, dl, figure, h1, h2, h3, h4, h5, h6, hr, p, pre {margin: 0}`;
-export async function renderMermaid(): Promise<void> {
- const els = document.querySelectorAll('.markup code.language-mermaid');
- if (!els.length) return;
+export async function initMarkupCodeMermaid(elMarkup: HTMLElement): Promise<void> {
+ const el = elMarkup.querySelector('code.language-mermaid'); // .markup code.language-mermaid
+ if (!el) return;
const {default: mermaid} = await import(/* webpackChunkName: "mermaid" */'mermaid');
@@ -23,67 +23,65 @@ export async function renderMermaid(): Promise<void> {
suppressErrorRendering: true,
});
- for (const el of els) {
- const pre = el.closest('pre');
- if (pre.hasAttribute('data-render-done')) continue;
-
- const source = el.textContent;
- if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) {
- displayError(pre, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`));
- continue;
- }
-
- try {
- await mermaid.parse(source);
- } catch (err) {
- displayError(pre, err);
- continue;
- }
-
- try {
- // can't use bindFunctions here because we can't cross the iframe boundary. This
- // means js-based interactions won't work but they aren't intended to work either
- const {svg} = await mermaid.render('mermaid', source);
-
- const iframe = document.createElement('iframe');
- iframe.classList.add('markup-render', 'tw-invisible');
- iframe.srcdoc = `<html><head><style>${iframeCss}</style></head><body>${svg}</body></html>`;
-
- const mermaidBlock = document.createElement('div');
- mermaidBlock.classList.add('mermaid-block', 'is-loading', 'tw-hidden');
- mermaidBlock.append(iframe);
-
- const btn = makeCodeCopyButton();
- btn.setAttribute('data-clipboard-text', source);
- mermaidBlock.append(btn);
-
- const updateIframeHeight = () => {
- const body = iframe.contentWindow?.document?.body;
- if (body) {
- iframe.style.height = `${body.clientHeight}px`;
- }
- };
-
- iframe.addEventListener('load', () => {
- pre.replaceWith(mermaidBlock);
- mermaidBlock.classList.remove('tw-hidden');
+ const pre = el.closest('pre');
+ if (pre.hasAttribute('data-render-done')) return;
+
+ const source = el.textContent;
+ if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) {
+ displayError(pre, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`));
+ return;
+ }
+
+ try {
+ await mermaid.parse(source);
+ } catch (err) {
+ displayError(pre, err);
+ return;
+ }
+
+ try {
+ // can't use bindFunctions here because we can't cross the iframe boundary. This
+ // means js-based interactions won't work but they aren't intended to work either
+ const {svg} = await mermaid.render('mermaid', source);
+
+ const iframe = document.createElement('iframe');
+ iframe.classList.add('markup-content-iframe', 'tw-invisible');
+ iframe.srcdoc = `<html><head><style>${iframeCss}</style></head><body>${svg}</body></html>`;
+
+ const mermaidBlock = document.createElement('div');
+ mermaidBlock.classList.add('mermaid-block', 'is-loading', 'tw-hidden');
+ mermaidBlock.append(iframe);
+
+ const btn = makeCodeCopyButton();
+ btn.setAttribute('data-clipboard-text', source);
+ mermaidBlock.append(btn);
+
+ const updateIframeHeight = () => {
+ const body = iframe.contentWindow?.document?.body;
+ if (body) {
+ iframe.style.height = `${body.clientHeight}px`;
+ }
+ };
+
+ iframe.addEventListener('load', () => {
+ pre.replaceWith(mermaidBlock);
+ mermaidBlock.classList.remove('tw-hidden');
+ updateIframeHeight();
+ setTimeout(() => { // avoid flash of iframe background
+ mermaidBlock.classList.remove('is-loading');
+ iframe.classList.remove('tw-invisible');
+ }, 0);
+
+ // update height when element's visibility state changes, for example when the diagram is inside
+ // a <details> + <summary> block and the <details> block becomes visible upon user interaction, it
+ // would initially set a incorrect height and the correct height is set during this callback.
+ (new IntersectionObserver(() => {
updateIframeHeight();
- setTimeout(() => { // avoid flash of iframe background
- mermaidBlock.classList.remove('is-loading');
- iframe.classList.remove('tw-invisible');
- }, 0);
-
- // update height when element's visibility state changes, for example when the diagram is inside
- // a <details> + <summary> block and the <details> block becomes visible upon user interaction, it
- // would initially set a incorrect height and the correct height is set during this callback.
- (new IntersectionObserver(() => {
- updateIframeHeight();
- }, {root: document.documentElement})).observe(iframe);
- });
-
- document.body.append(mermaidBlock);
- } catch (err) {
- displayError(pre, err);
- }
+ }, {root: document.documentElement})).observe(iframe);
+ });
+
+ document.body.append(mermaidBlock);
+ } catch (err) {
+ displayError(pre, err);
}
}
diff --git a/web_src/js/markup/tasklist.ts b/web_src/js/markup/tasklist.ts
index 95db7fc845..dc4bbd9519 100644
--- a/web_src/js/markup/tasklist.ts
+++ b/web_src/js/markup/tasklist.ts
@@ -7,80 +7,80 @@ const preventListener = (e: Event) => e.preventDefault();
* Attaches `input` handlers to markdown rendered tasklist checkboxes in comments.
*
* When a checkbox value changes, the corresponding [ ] or [x] in the markdown string
- * is set accordingly and sent to the server. On success it updates the raw-content on
+ * is set accordingly and sent to the server. On success, it updates the raw-content on
* error it resets the checkbox to its original value.
*/
-export function initMarkupTasklist(): void {
- for (const el of document.querySelectorAll(`.markup[data-can-edit=true]`) || []) {
- const container = el.parentNode;
- const checkboxes = el.querySelectorAll<HTMLInputElement>(`.task-list-item input[type=checkbox]`);
+export function initMarkupTasklist(elMarkup: HTMLElement): void {
+ if (!elMarkup.matches('[data-can-edit=true]')) return;
- for (const checkbox of checkboxes) {
- if (checkbox.hasAttribute('data-editable')) {
- return;
- }
+ const container = elMarkup.parentNode;
+ const checkboxes = elMarkup.querySelectorAll<HTMLInputElement>(`.task-list-item input[type=checkbox]`);
- checkbox.setAttribute('data-editable', 'true');
- checkbox.addEventListener('input', async () => {
- const checkboxCharacter = checkbox.checked ? 'x' : ' ';
- const position = parseInt(checkbox.getAttribute('data-source-position')) + 1;
+ for (const checkbox of checkboxes) {
+ if (checkbox.hasAttribute('data-editable')) {
+ return;
+ }
- const rawContent = container.querySelector('.raw-content');
- const oldContent = rawContent.textContent;
+ checkbox.setAttribute('data-editable', 'true');
+ checkbox.addEventListener('input', async () => {
+ const checkboxCharacter = checkbox.checked ? 'x' : ' ';
+ const position = parseInt(checkbox.getAttribute('data-source-position')) + 1;
- const encoder = new TextEncoder();
- const buffer = encoder.encode(oldContent);
- // Indexes may fall off the ends and return undefined.
- if (buffer[position - 1] !== '['.codePointAt(0) ||
- buffer[position] !== ' '.codePointAt(0) && buffer[position] !== 'x'.codePointAt(0) ||
- buffer[position + 1] !== ']'.codePointAt(0)) {
- // Position is probably wrong. Revert and don't allow change.
- checkbox.checked = !checkbox.checked;
- throw new Error(`Expected position to be space or x and surrounded by brackets, but it's not: position=${position}`);
- }
- buffer.set(encoder.encode(checkboxCharacter), position);
- const newContent = new TextDecoder().decode(buffer);
+ const rawContent = container.querySelector('.raw-content');
+ const oldContent = rawContent.textContent;
- if (newContent === oldContent) {
- return;
- }
+ const encoder = new TextEncoder();
+ const buffer = encoder.encode(oldContent);
+ // Indexes may fall off the ends and return undefined.
+ if (buffer[position - 1] !== '['.codePointAt(0) ||
+ buffer[position] !== ' '.codePointAt(0) && buffer[position] !== 'x'.codePointAt(0) ||
+ buffer[position + 1] !== ']'.codePointAt(0)) {
+ // Position is probably wrong. Revert and don't allow change.
+ checkbox.checked = !checkbox.checked;
+ throw new Error(`Expected position to be space or x and surrounded by brackets, but it's not: position=${position}`);
+ }
+ buffer.set(encoder.encode(checkboxCharacter), position);
+ const newContent = new TextDecoder().decode(buffer);
- // Prevent further inputs until the request is done. This does not use the
- // `disabled` attribute because it causes the border to flash on click.
- for (const checkbox of checkboxes) {
- checkbox.addEventListener('click', preventListener);
- }
+ if (newContent === oldContent) {
+ return;
+ }
- try {
- const editContentZone = container.querySelector<HTMLDivElement>('.edit-content-zone');
- const updateUrl = editContentZone.getAttribute('data-update-url');
- const context = editContentZone.getAttribute('data-context');
- const contentVersion = editContentZone.getAttribute('data-content-version');
+ // Prevent further inputs until the request is done. This does not use the
+ // `disabled` attribute because it causes the border to flash on click.
+ for (const checkbox of checkboxes) {
+ checkbox.addEventListener('click', preventListener);
+ }
- const requestBody = new FormData();
- requestBody.append('ignore_attachments', 'true');
- requestBody.append('content', newContent);
- requestBody.append('context', context);
- requestBody.append('content_version', contentVersion);
- const response = await POST(updateUrl, {data: requestBody});
- const data = await response.json();
- if (response.status === 400) {
- showErrorToast(data.errorMessage);
- return;
- }
- editContentZone.setAttribute('data-content-version', data.contentVersion);
- rawContent.textContent = newContent;
- } catch (err) {
- checkbox.checked = !checkbox.checked;
- console.error(err);
- }
+ try {
+ const editContentZone = container.querySelector<HTMLDivElement>('.edit-content-zone');
+ const updateUrl = editContentZone.getAttribute('data-update-url');
+ const context = editContentZone.getAttribute('data-context');
+ const contentVersion = editContentZone.getAttribute('data-content-version');
- // Enable input on checkboxes again
- for (const checkbox of checkboxes) {
- checkbox.removeEventListener('click', preventListener);
+ const requestBody = new FormData();
+ requestBody.append('ignore_attachments', 'true');
+ requestBody.append('content', newContent);
+ requestBody.append('context', context);
+ requestBody.append('content_version', contentVersion);
+ const response = await POST(updateUrl, {data: requestBody});
+ const data = await response.json();
+ if (response.status === 400) {
+ showErrorToast(data.errorMessage);
+ return;
}
- });
- }
+ editContentZone.setAttribute('data-content-version', data.contentVersion);
+ rawContent.textContent = newContent;
+ } catch (err) {
+ checkbox.checked = !checkbox.checked;
+ console.error(err);
+ }
+
+ // Enable input on checkboxes again
+ for (const checkbox of checkboxes) {
+ checkbox.removeEventListener('click', preventListener);
+ }
+ });
// Enable the checkboxes as they are initially disabled by the markdown renderer
for (const checkbox of checkboxes) {
diff --git a/web_src/js/modules/observer.ts b/web_src/js/modules/observer.ts
index f60c033cf2..06208d0507 100644
--- a/web_src/js/modules/observer.ts
+++ b/web_src/js/modules/observer.ts
@@ -20,6 +20,9 @@ export function registerGlobalEventFunc<T extends HTMLElement, E extends Event>(
// It handles the global init functions by a selector, for example:
// > registerGlobalSelectorObserver('.ui.dropdown:not(.custom)', (el) => { initDropdown(el, ...) });
+// ATTENTION: For most cases, it's recommended to use registerGlobalInitFunc instead,
+// Because this selector-based approach is less efficient and less maintainable.
+// But if there are already a lot of elements on many pages, this selector-based approach is more convenient for exiting code.
export function registerGlobalSelectorFunc(selector: string, handler: (el: HTMLElement) => void) {
selectorHandlers.push({selector, handler});
// Then initAddedElementObserver will call this handler for all existing elements after all handlers are added.
diff --git a/web_src/js/render/pdf.ts b/web_src/js/render/pdf.ts
index f31f161e6e..283b4ed85c 100644
--- a/web_src/js/render/pdf.ts
+++ b/web_src/js/render/pdf.ts
@@ -1,12 +1,10 @@
import {htmlEscape} from 'escape-goat';
+import {registerGlobalInitFunc} from '../modules/observer.ts';
export async function initPdfViewer() {
- const els = document.querySelectorAll('.pdf-content');
- if (!els.length) return;
+ registerGlobalInitFunc('initPdfViewer', async (el: HTMLInputElement) => {
+ const pdfobject = await import(/* webpackChunkName: "pdfobject" */'pdfobject');
- const pdfobject = await import(/* webpackChunkName: "pdfobject" */'pdfobject');
-
- for (const el of els) {
const src = el.getAttribute('data-src');
const fallbackText = el.getAttribute('data-fallback-button-text');
pdfobject.embed(src, el, {
@@ -15,5 +13,5 @@ export async function initPdfViewer() {
`,
});
el.classList.remove('is-loading');
- }
+ });
}