diff options
Diffstat (limited to 'web_src/js/features')
-rw-r--r-- | web_src/js/features/comp/ConfirmModal.ts | 24 | ||||
-rw-r--r-- | web_src/js/features/comp/EditorUpload.ts | 2 | ||||
-rw-r--r-- | web_src/js/features/comp/SearchUserBox.ts | 2 | ||||
-rw-r--r-- | web_src/js/features/dropzone.ts | 6 | ||||
-rw-r--r-- | web_src/js/features/emoji.ts | 6 | ||||
-rw-r--r-- | web_src/js/features/file-view.ts | 4 | ||||
-rw-r--r-- | web_src/js/features/repo-editor.ts | 13 | ||||
-rw-r--r-- | web_src/js/features/repo-issue-list.ts | 8 | ||||
-rw-r--r-- | web_src/js/features/repo-issue.ts | 27 | ||||
-rw-r--r-- | web_src/js/features/repo-new.ts | 2 | ||||
-rw-r--r-- | web_src/js/features/repo-wiki.ts | 3 | ||||
-rw-r--r-- | web_src/js/features/tribute.ts | 13 |
12 files changed, 54 insertions, 56 deletions
diff --git a/web_src/js/features/comp/ConfirmModal.ts b/web_src/js/features/comp/ConfirmModal.ts index 81ea09476b..97a73eace6 100644 --- a/web_src/js/features/comp/ConfirmModal.ts +++ b/web_src/js/features/comp/ConfirmModal.ts @@ -1,5 +1,5 @@ import {svg} from '../../svg.ts'; -import {htmlEscape} from 'escape-goat'; +import {html, htmlRaw} from '../../utils/html.ts'; import {createElementFromHTML} from '../../utils/dom.ts'; import {fomanticQuery} from '../../modules/fomantic/base.ts'; @@ -12,17 +12,17 @@ type ConfirmModalOptions = { } export function createConfirmModal({header = '', content = '', confirmButtonColor = 'primary'}:ConfirmModalOptions = {}): HTMLElement { - const headerHtml = header ? `<div class="header">${htmlEscape(header)}</div>` : ''; - return createElementFromHTML(` -<div class="ui g-modal-confirm modal"> - ${headerHtml} - <div class="content">${htmlEscape(content)}</div> - <div class="actions"> - <button class="ui cancel button">${svg('octicon-x')} ${htmlEscape(i18n.modal_cancel)}</button> - <button class="ui ${confirmButtonColor} ok button">${svg('octicon-check')} ${htmlEscape(i18n.modal_confirm)}</button> - </div> -</div> -`); + const headerHtml = header ? html`<div class="header">${header}</div>` : ''; + return createElementFromHTML(html` + <div class="ui g-modal-confirm modal"> + ${htmlRaw(headerHtml)} + <div class="content">${content}</div> + <div class="actions"> + <button class="ui cancel button">${htmlRaw(svg('octicon-x'))} ${i18n.modal_cancel}</button> + <button class="ui ${confirmButtonColor} ok button">${htmlRaw(svg('octicon-check'))} ${i18n.modal_confirm}</button> + </div> + </div> + `.trim()); } export function confirmModal(modal: HTMLElement | ConfirmModalOptions): Promise<boolean> { diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts index bf9ce9bfb1..bf78f58daf 100644 --- a/web_src/js/features/comp/EditorUpload.ts +++ b/web_src/js/features/comp/EditorUpload.ts @@ -114,7 +114,7 @@ async function handleUploadFiles(editor: CodeMirrorEditor | TextareaEditor, drop export function removeAttachmentLinksFromMarkdown(text: string, fileUuid: string) { text = text.replace(new RegExp(`!?\\[([^\\]]+)\\]\\(/?attachments/${fileUuid}\\)`, 'g'), ''); - text = text.replace(new RegExp(`<img[^>]+src="/?attachments/${fileUuid}"[^>]*>`, 'g'), ''); + text = text.replace(new RegExp(`[<]img[^>]+src="/?attachments/${fileUuid}"[^>]*>`, 'g'), ''); return text; } diff --git a/web_src/js/features/comp/SearchUserBox.ts b/web_src/js/features/comp/SearchUserBox.ts index 9fedb3ed24..4b13a2141f 100644 --- a/web_src/js/features/comp/SearchUserBox.ts +++ b/web_src/js/features/comp/SearchUserBox.ts @@ -1,4 +1,4 @@ -import {htmlEscape} from 'escape-goat'; +import {htmlEscape} from '../../utils/html.ts'; import {fomanticQuery} from '../../modules/fomantic/base.ts'; const {appSubUrl} = window.config; diff --git a/web_src/js/features/dropzone.ts b/web_src/js/features/dropzone.ts index b2ba7651c4..20f7ceb6c3 100644 --- a/web_src/js/features/dropzone.ts +++ b/web_src/js/features/dropzone.ts @@ -1,5 +1,5 @@ import {svg} from '../svg.ts'; -import {htmlEscape} from 'escape-goat'; +import {html} from '../utils/html.ts'; import {clippie} from 'clippie'; import {showTemporaryTooltip} from '../modules/tippy.ts'; import {GET, POST} from '../modules/fetch.ts'; @@ -33,14 +33,14 @@ export function generateMarkdownLinkForAttachment(file: Partial<CustomDropzoneFi // Scale down images from HiDPI monitors. This uses the <img> tag because it's the only // method to change image size in Markdown that is supported by all implementations. // Make the image link relative to the repo path, then the final URL is "/sub-path/owner/repo/attachments/{uuid}" - fileMarkdown = `<img width="${Math.round(width / dppx)}" alt="${htmlEscape(file.name)}" src="attachments/${htmlEscape(file.uuid)}">`; + fileMarkdown = html`<img width="${Math.round(width / dppx)}" alt="${file.name}" src="attachments/${file.uuid}">`; } else { // Markdown always renders the image with a relative path, so the final URL is "/sub-path/owner/repo/attachments/{uuid}" // TODO: it should also use relative path for consistency, because absolute is ambiguous for "/sub-path/attachments" or "/attachments" fileMarkdown = ``; } } else if (isVideoFile(file)) { - fileMarkdown = `<video src="attachments/${htmlEscape(file.uuid)}" title="${htmlEscape(file.name)}" controls></video>`; + fileMarkdown = html`<video src="attachments/${file.uuid}" title="${file.name}" controls></video>`; } return fileMarkdown; } diff --git a/web_src/js/features/emoji.ts b/web_src/js/features/emoji.ts index 135620e51e..69afe491e2 100644 --- a/web_src/js/features/emoji.ts +++ b/web_src/js/features/emoji.ts @@ -1,4 +1,5 @@ import emojis from '../../../assets/emoji.json' with {type: 'json'}; +import {html} from '../utils/html.ts'; const {assetUrlPrefix, customEmojis} = window.config; @@ -24,12 +25,11 @@ for (const key of emojiKeys) { export function emojiHTML(name: string) { let inner; if (Object.hasOwn(customEmojis, name)) { - inner = `<img alt=":${name}:" src="${assetUrlPrefix}/img/emoji/${name}.png">`; + inner = html`<img alt=":${name}:" src="${assetUrlPrefix}/img/emoji/${name}.png">`; } else { inner = emojiString(name); } - - return `<span class="emoji" title=":${name}:">${inner}</span>`; + return html`<span class="emoji" title=":${name}:">${inner}</span>`; } // retrieve string for given emoji name diff --git a/web_src/js/features/file-view.ts b/web_src/js/features/file-view.ts index 867f946297..d803f53c0d 100644 --- a/web_src/js/features/file-view.ts +++ b/web_src/js/features/file-view.ts @@ -3,7 +3,7 @@ import {newRenderPlugin3DViewer} from '../render/plugins/3d-viewer.ts'; import {newRenderPluginPdfViewer} from '../render/plugins/pdf-viewer.ts'; import {registerGlobalInitFunc} from '../modules/observer.ts'; import {createElementFromHTML, showElem, toggleClass} from '../utils/dom.ts'; -import {htmlEscape} from 'escape-goat'; +import {html} from '../utils/html.ts'; import {basename} from '../utils.ts'; const plugins: FileRenderPlugin[] = []; @@ -54,7 +54,7 @@ async function renderRawFileToContainer(container: HTMLElement, rawFileLink: str container.replaceChildren(elViewRawPrompt); if (errorMsg) { - const elErrorMessage = createElementFromHTML(htmlEscape`<div class="ui error message">${errorMsg}</div>`); + const elErrorMessage = createElementFromHTML(html`<div class="ui error message">${errorMsg}</div>`); elViewRawPrompt.insertAdjacentElement('afterbegin', elErrorMessage); } } diff --git a/web_src/js/features/repo-editor.ts b/web_src/js/features/repo-editor.ts index c6b5cccd54..f3ca13460c 100644 --- a/web_src/js/features/repo-editor.ts +++ b/web_src/js/features/repo-editor.ts @@ -1,4 +1,4 @@ -import {htmlEscape} from 'escape-goat'; +import {html, htmlRaw} from '../utils/html.ts'; import {createCodeEditor} from './codeeditor.ts'; import {hideElem, queryElems, showElem, createElementFromHTML} from '../utils/dom.ts'; import {attachRefIssueContextPopup} from './contextpopup.ts'; @@ -87,10 +87,10 @@ export function initRepoEditor() { if (i < parts.length - 1) { if (trimValue.length) { const linkElement = createElementFromHTML( - `<span class="section"><a href="#">${htmlEscape(value)}</a></span>`, + html`<span class="section"><a href="#">${value}</a></span>`, ); const dividerElement = createElementFromHTML( - `<div class="breadcrumb-divider">/</div>`, + html`<div class="breadcrumb-divider">/</div>`, ); links.push(linkElement); dividers.push(dividerElement); @@ -113,7 +113,7 @@ export function initRepoEditor() { if (!warningDiv) { warningDiv = document.createElement('div'); warningDiv.classList.add('ui', 'warning', 'message', 'flash-message', 'flash-warning', 'space-related'); - warningDiv.innerHTML = '<p>File path contains leading or trailing whitespace.</p>'; + warningDiv.innerHTML = html`<p>File path contains leading or trailing whitespace.</p>`; // Add display 'block' because display is set to 'none' in formantic\build\semantic.css warningDiv.style.display = 'block'; const inputContainer = document.querySelector('.repo-editor-header'); @@ -196,7 +196,8 @@ export function initRepoEditor() { })(); } -export function renderPreviewPanelContent(previewPanel: Element, content: string) { - previewPanel.innerHTML = `<div class="render-content markup">${content}</div>`; +export function renderPreviewPanelContent(previewPanel: Element, htmlContent: string) { + // the content is from the server, so it is safe to use innerHTML + previewPanel.innerHTML = html`<div class="render-content markup">${htmlRaw(htmlContent)}</div>`; attachRefIssueContextPopup(previewPanel.querySelectorAll('p .ref-issue')); } diff --git a/web_src/js/features/repo-issue-list.ts b/web_src/js/features/repo-issue-list.ts index 3ea5fb70c0..762fbf51bb 100644 --- a/web_src/js/features/repo-issue-list.ts +++ b/web_src/js/features/repo-issue-list.ts @@ -1,6 +1,6 @@ import {updateIssuesMeta} from './repo-common.ts'; import {toggleElem, queryElems, isElemVisible} from '../utils/dom.ts'; -import {htmlEscape} from 'escape-goat'; +import {html} from '../utils/html.ts'; import {confirmModal} from './comp/ConfirmModal.ts'; import {showErrorToast} from '../modules/toast.ts'; import {createSortable} from '../modules/sortable.ts'; @@ -138,10 +138,10 @@ function initDropdownUserRemoteSearch(el: Element) { // the content is provided by backend IssuePosters handler processedResults.length = 0; for (const item of resp.results) { - let html = `<img class="ui avatar tw-align-middle" src="${htmlEscape(item.avatar_link)}" aria-hidden="true" alt width="20" height="20"><span class="gt-ellipsis">${htmlEscape(item.username)}</span>`; - if (item.full_name) html += `<span class="search-fullname tw-ml-2">${htmlEscape(item.full_name)}</span>`; + let nameHtml = html`<img class="ui avatar tw-align-middle" src="${item.avatar_link}" aria-hidden="true" alt width="20" height="20"><span class="gt-ellipsis">${item.username}</span>`; + if (item.full_name) nameHtml += html`<span class="search-fullname tw-ml-2">${item.full_name}</span>`; if (selectedUsername.toLowerCase() === item.username.toLowerCase()) selectedUsername = item.username; - processedResults.push({value: item.username, name: html}); + processedResults.push({value: item.username, name: nameHtml}); } resp.results = processedResults; return resp; diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index bc7d4dee19..49e8fc40a2 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -1,4 +1,4 @@ -import {htmlEscape} from 'escape-goat'; +import {html, htmlEscape} from '../utils/html.ts'; import {createTippy, showTemporaryTooltip} from '../modules/tippy.ts'; import { addDelegatedEventListener, @@ -17,6 +17,7 @@ import {showErrorToast} from '../modules/toast.ts'; import {initRepoIssueSidebar} from './repo-issue-sidebar.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts'; +import {registerGlobalInitFunc} from '../modules/observer.ts'; const {appSubUrl} = window.config; @@ -45,8 +46,7 @@ export function initRepoIssueSidebarDependency() { if (String(issue.id) === currIssueId) continue; filteredResponse.results.push({ value: issue.id, - name: `<div class="gt-ellipsis">#${issue.number} ${htmlEscape(issue.title)}</div> -<div class="text small tw-break-anywhere">${htmlEscape(issue.repository.full_name)}</div>`, + name: html`<div class="gt-ellipsis">#${issue.number} ${issue.title}</div><div class="text small tw-break-anywhere">${issue.repository.full_name}</div>`, }); } return filteredResponse; @@ -416,25 +416,20 @@ export function initRepoIssueWipNewTitle() { export function initRepoIssueWipToggle() { // Toggle WIP for existing PR - queryElems(document, '.toggle-wip', (el) => el.addEventListener('click', async (e) => { + registerGlobalInitFunc('initPullRequestWipToggle', (toggleWip) => toggleWip.addEventListener('click', async (e) => { e.preventDefault(); - const toggleWip = el; const title = toggleWip.getAttribute('data-title'); const wipPrefix = toggleWip.getAttribute('data-wip-prefix'); const updateUrl = toggleWip.getAttribute('data-update-url'); - try { - const params = new URLSearchParams(); - params.append('title', title?.startsWith(wipPrefix) ? title.slice(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`); - - const response = await POST(updateUrl, {data: params}); - if (!response.ok) { - throw new Error('Failed to toggle WIP status'); - } - window.location.reload(); - } catch (error) { - console.error(error); + const params = new URLSearchParams(); + params.append('title', title?.startsWith(wipPrefix) ? title.slice(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`); + const response = await POST(updateUrl, {data: params}); + if (!response.ok) { + showErrorToast(`Failed to toggle 'work in progress' status`); + return; } + window.location.reload(); })); } diff --git a/web_src/js/features/repo-new.ts b/web_src/js/features/repo-new.ts index 0e4d78872d..e2aa13f490 100644 --- a/web_src/js/features/repo-new.ts +++ b/web_src/js/features/repo-new.ts @@ -1,5 +1,5 @@ import {hideElem, querySingleVisibleElem, showElem, toggleElem} from '../utils/dom.ts'; -import {htmlEscape} from 'escape-goat'; +import {htmlEscape} from '../utils/html.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; import {sanitizeRepoName} from './repo-common.ts'; diff --git a/web_src/js/features/repo-wiki.ts b/web_src/js/features/repo-wiki.ts index f94d3ef3d1..6ae0947077 100644 --- a/web_src/js/features/repo-wiki.ts +++ b/web_src/js/features/repo-wiki.ts @@ -2,6 +2,7 @@ import {validateTextareaNonEmpty, initComboMarkdownEditor} from './comp/ComboMar import {fomanticMobileScreen} from '../modules/fomantic.ts'; import {POST} from '../modules/fetch.ts'; import type {ComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts'; +import {html, htmlRaw} from '../utils/html.ts'; async function initRepoWikiFormEditor() { const editArea = document.querySelector<HTMLTextAreaElement>('.repository.wiki .combo-markdown-editor textarea'); @@ -30,7 +31,7 @@ async function initRepoWikiFormEditor() { const response = await POST(editor.previewUrl, {data: formData}); const data = await response.text(); lastContent = newContent; - previewTarget.innerHTML = `<div class="render-content markup ui segment">${data}</div>`; + previewTarget.innerHTML = html`<div class="render-content markup ui segment">${htmlRaw(data)}</div>`; } catch (error) { console.error('Error rendering preview:', error); } finally { diff --git a/web_src/js/features/tribute.ts b/web_src/js/features/tribute.ts index cf98377ae7..43c21ebe6d 100644 --- a/web_src/js/features/tribute.ts +++ b/web_src/js/features/tribute.ts @@ -1,5 +1,5 @@ import {emojiKeys, emojiHTML, emojiString} from './emoji.ts'; -import {htmlEscape} from 'escape-goat'; +import {html, htmlRaw} from '../utils/html.ts'; type TributeItem = Record<string, any>; @@ -26,17 +26,18 @@ export async function attachTribute(element: HTMLElement) { return emojiString(item.original); }, menuItemTemplate: (item: TributeItem) => { - return `<div class="tribute-item">${emojiHTML(item.original)}<span>${htmlEscape(item.original)}</span></div>`; + return html`<div class="tribute-item">${htmlRaw(emojiHTML(item.original))}<span>${item.original}</span></div>`; }, }, { // mentions values: window.config.mentionValues ?? [], requireLeadingSpace: true, menuItemTemplate: (item: TributeItem) => { - return ` + const fullNameHtml = item.original.fullname && item.original.fullname !== '' ? html`<span class="fullname">${item.original.fullname}</span>` : ''; + return html` <div class="tribute-item"> - <img alt src="${htmlEscape(item.original.avatar)}" width="21" height="21"/> - <span class="name">${htmlEscape(item.original.name)}</span> - ${item.original.fullname && item.original.fullname !== '' ? `<span class="fullname">${htmlEscape(item.original.fullname)}</span>` : ''} + <img alt src="${item.original.avatar}" width="21" height="21"/> + <span class="name">${item.original.name}</span> + ${htmlRaw(fullNameHtml)} </div> `; }, |