aboutsummaryrefslogtreecommitdiffstats
path: root/web_src
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 /web_src
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>
Diffstat (limited to 'web_src')
-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
15 files changed, 183 insertions, 209 deletions
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');
- }
+ });
}