aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKerwin Bryant <kerwin612@qq.com>2025-03-04 03:49:15 +0800
committerGitHub <noreply@github.com>2025-03-03 11:49:15 -0800
commitf0f1737d4d533e06f482a63ff995ae79cc1c89dc (patch)
tree5b60a1b158cbbc50cb6ef9a88a2f932eaf17dee4
parent43c8d85f19e14e695eb129a8ff71bd106430b016 (diff)
downloadgitea-f0f1737d4d533e06f482a63ff995ae79cc1c89dc.tar.gz
gitea-f0f1737d4d533e06f482a63ff995ae79cc1c89dc.zip
Refactor markup and pdf-viewer to use new init framework (#33772)
1. Add some "render-content" classes to "markup" elements when the content is rendered 2. Use correct "markup" wrapper for "preview" (but not set that class on the tab) 3. Remove incorrect "markup" class from LFS file view, because there is no markup content * "edit-diff" is also removed because it does nothing 5. Use "initPdfViewer" for PDF viewer 6. Remove incorrect "content" class from milestone markup 7. Init all ".markup" elements by new init framework --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
-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');
- }
+ });
}