mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 07:04:27 +00: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