mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 19:06:18 +01:00 
			
		
		
		
	| @@ -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> { | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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')); | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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, | ||||
| @@ -46,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; | ||||
|   | ||||
| @@ -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'; | ||||
|  | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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> | ||||
|         `; | ||||
|       }, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user