| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- import {html, htmlEscape} from '../utils/html.ts';
- import {createTippy, showTemporaryTooltip} from '../modules/tippy.ts';
- import {
- addDelegatedEventListener,
- createElementFromHTML,
- hideElem,
- queryElems,
- showElem,
- toggleElem,
- type DOMEvent,
- } from '../utils/dom.ts';
- import {setFileFolding} from './file-fold.ts';
- import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
- import {parseIssuePageInfo, toAbsoluteUrl} from '../utils.ts';
- import {GET, POST} from '../modules/fetch.ts';
- 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;
-
- export function initRepoIssueSidebarDependency() {
- const elDropdown = document.querySelector('#new-dependency-drop-list');
- if (!elDropdown) return;
-
- const issuePageInfo = parseIssuePageInfo();
- const crossRepoSearch = elDropdown.getAttribute('data-issue-cross-repo-search');
- let issueSearchUrl = `${issuePageInfo.repoLink}/issues/search?q={query}&type=${issuePageInfo.issueDependencySearchType}`;
- if (crossRepoSearch === 'true') {
- issueSearchUrl = `${appSubUrl}/issues/search?q={query}&priority_repo_id=${issuePageInfo.repoId}&type=${issuePageInfo.issueDependencySearchType}`;
- }
- fomanticQuery(elDropdown).dropdown({
- fullTextSearch: true,
- apiSettings: {
- cache: false,
- rawResponse: true,
- url: issueSearchUrl,
- onResponse(response: any) {
- const filteredResponse = {success: true, results: [] as Array<Record<string, any>>};
- const currIssueId = elDropdown.getAttribute('data-issue-id');
- // Parse the response from the api to work with our dropdown
- for (const issue of response) {
- // Don't list current issue in the dependency list.
- if (String(issue.id) === currIssueId) continue;
- filteredResponse.results.push({
- value: issue.id,
- 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;
- },
- },
- });
- }
-
- function initRepoIssueLabelFilter(elDropdown: HTMLElement) {
- const url = new URL(window.location.href);
- const showArchivedLabels = url.searchParams.get('archived_labels') === 'true';
- const queryLabels = url.searchParams.get('labels') || '';
- const selectedLabelIds = new Set<string>();
- for (const id of queryLabels ? queryLabels.split(',') : []) {
- selectedLabelIds.add(`${Math.abs(parseInt(id))}`); // "labels" contains negative ids, which are excluded
- }
-
- const excludeLabel = (e: MouseEvent | KeyboardEvent, item: Element) => {
- e.preventDefault();
- e.stopPropagation();
- const labelId = item.getAttribute('data-label-id');
- let labelIds: string[] = queryLabels ? queryLabels.split(',') : [];
- labelIds = labelIds.filter((id) => Math.abs(parseInt(id)) !== Math.abs(parseInt(labelId)));
- labelIds.push(`-${labelId}`);
- url.searchParams.set('labels', labelIds.join(','));
- window.location.assign(url);
- };
-
- // alt(or option) + click to exclude label
- queryElems(elDropdown, '.label-filter-query-item', (el) => {
- el.addEventListener('click', (e: MouseEvent) => {
- if (e.altKey) excludeLabel(e, el);
- });
- });
- // alt(or option) + enter to exclude selected label
- elDropdown.addEventListener('keydown', (e: KeyboardEvent) => {
- if (e.altKey && e.key === 'Enter') {
- const selectedItem = elDropdown.querySelector('.label-filter-query-item.selected');
- if (selectedItem) excludeLabel(e, selectedItem);
- }
- });
- // no "labels" query parameter means "all issues"
- elDropdown.querySelector('.label-filter-query-default').classList.toggle('selected', queryLabels === '');
- // "labels=0" query parameter means "issues without label"
- elDropdown.querySelector('.label-filter-query-not-set').classList.toggle('selected', queryLabels === '0');
-
- // prepare to process "archived" labels
- const elShowArchivedLabel = elDropdown.querySelector('.label-filter-archived-toggle');
- if (!elShowArchivedLabel) return;
- const elShowArchivedInput = elShowArchivedLabel.querySelector<HTMLInputElement>('input');
- elShowArchivedInput.checked = showArchivedLabels;
- const archivedLabels = elDropdown.querySelectorAll('.item[data-is-archived]');
- // if no archived labels, hide the toggle and return
- if (!archivedLabels.length) {
- hideElem(elShowArchivedLabel);
- return;
- }
-
- // show the archived labels if the toggle is checked or the label is selected
- for (const label of archivedLabels) {
- toggleElem(label, showArchivedLabels || selectedLabelIds.has(label.getAttribute('data-label-id')));
- }
- // update the url when the toggle is changed and reload
- elShowArchivedInput.addEventListener('input', () => {
- if (elShowArchivedInput.checked) {
- url.searchParams.set('archived_labels', 'true');
- } else {
- url.searchParams.delete('archived_labels');
- }
- window.location.assign(url);
- });
- }
-
- export function initRepoIssueFilterItemLabel() {
- // the "label-filter" is used in 2 templates: projects/view, issue/filter_list (issue list page including the milestone page)
- queryElems(document, '.ui.dropdown.label-filter', initRepoIssueLabelFilter);
- }
-
- export function initRepoIssueCommentDelete() {
- // Delete comment
- document.addEventListener('click', async (e: DOMEvent<MouseEvent>) => {
- if (!e.target.matches('.delete-comment')) return;
- e.preventDefault();
-
- const deleteButton = e.target;
- if (window.confirm(deleteButton.getAttribute('data-locale'))) {
- try {
- const response = await POST(deleteButton.getAttribute('data-url'));
- if (!response.ok) throw new Error('Failed to delete comment');
-
- const conversationHolder = deleteButton.closest('.conversation-holder');
- const parentTimelineItem = deleteButton.closest('.timeline-item');
- const parentTimelineGroup = deleteButton.closest('.timeline-item-group');
-
- // Check if this was a pending comment.
- if (conversationHolder?.querySelector('.pending-label')) {
- const counter = document.querySelector('#review-box .review-comments-counter');
- let num = parseInt(counter?.getAttribute('data-pending-comment-number')) - 1 || 0;
- num = Math.max(num, 0);
- counter.setAttribute('data-pending-comment-number', String(num));
- counter.textContent = String(num);
- }
-
- document.querySelector(`#${deleteButton.getAttribute('data-comment-id')}`)?.remove();
-
- if (conversationHolder && !conversationHolder.querySelector('.comment')) {
- const path = conversationHolder.getAttribute('data-path');
- const side = conversationHolder.getAttribute('data-side');
- const idx = conversationHolder.getAttribute('data-idx');
- const lineType = conversationHolder.closest('tr')?.getAttribute('data-line-type');
-
- // the conversation holder could appear either on the "Conversation" page, or the "Files Changed" page
- // on the Conversation page, there is no parent "tr", so no need to do anything for "add-code-comment"
- if (lineType) {
- if (lineType === 'same') {
- document.querySelector(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).classList.remove('tw-invisible');
- } else {
- document.querySelector(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).classList.remove('tw-invisible');
- }
- }
- conversationHolder.remove();
- }
-
- // Check if there is no review content, move the time avatar upward to avoid overlapping the content below.
- if (!parentTimelineGroup?.querySelector('.timeline-item.comment') && !parentTimelineItem?.querySelector('.conversation-holder')) {
- const timelineAvatar = parentTimelineGroup?.querySelector('.timeline-avatar');
- timelineAvatar?.classList.remove('timeline-avatar-offset');
- }
- } catch (error) {
- console.error(error);
- }
- }
- });
- }
-
- export function initRepoIssueCodeCommentCancel() {
- // Cancel inline code comment
- document.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
- if (!e.target.matches('.cancel-code-comment')) return;
-
- const form = e.target.closest('form');
- if (form?.classList.contains('comment-form')) {
- hideElem(form);
- showElem(form.closest('.comment-code-cloud')?.querySelectorAll('button.comment-form-reply'));
- } else {
- form.closest('.comment-code-cloud')?.remove();
- }
- });
- }
-
- export function initRepoPullRequestAllowMaintainerEdit() {
- const wrapper = document.querySelector('#allow-edits-from-maintainers');
- if (!wrapper) return;
- const checkbox = wrapper.querySelector<HTMLInputElement>('input[type="checkbox"]');
- checkbox.addEventListener('input', async () => {
- const url = `${wrapper.getAttribute('data-url')}/set_allow_maintainer_edit`;
- wrapper.classList.add('is-loading');
- try {
- const resp = await POST(url, {data: new URLSearchParams({
- allow_maintainer_edit: String(checkbox.checked),
- })});
- if (!resp.ok) {
- throw new Error('Failed to update maintainer edit permission');
- }
- const data = await resp.json();
- checkbox.checked = data.allow_maintainer_edit;
- } catch (error) {
- checkbox.checked = !checkbox.checked;
- console.error(error);
- showTemporaryTooltip(wrapper, wrapper.getAttribute('data-prompt-error'));
- } finally {
- wrapper.classList.remove('is-loading');
- }
- });
- }
-
- export function initRepoIssueComments() {
- if (!document.querySelector('.repository.view.issue .timeline')) return;
-
- document.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
- const urlTarget = document.querySelector(':target');
- if (!urlTarget) return;
-
- const urlTargetId = urlTarget.id;
- if (!urlTargetId) return;
-
- if (!/^(issue|pull)(comment)?-\d+$/.test(urlTargetId)) return;
-
- if (!e.target.closest(`#${urlTargetId}`)) {
- // if the user clicks outside the comment, remove the hash from the url
- // use empty hash and state to avoid scrolling
- window.location.hash = ' ';
- window.history.pushState(null, null, ' ');
- }
- });
- }
-
- export async function handleReply(el: HTMLElement) {
- const form = el.closest('.comment-code-cloud').querySelector('.comment-form');
- const textarea = form.querySelector('textarea');
-
- hideElem(el);
- showElem(form);
- const editor = getComboMarkdownEditor(textarea) ?? await initComboMarkdownEditor(form.querySelector('.combo-markdown-editor'));
- editor.focus();
- return editor;
- }
-
- export function initRepoPullRequestReview() {
- if (window.location.hash && window.location.hash.startsWith('#issuecomment-')) {
- const commentDiv = document.querySelector(window.location.hash);
- if (commentDiv) {
- // get the name of the parent id
- const groupID = commentDiv.closest('div[id^="code-comments-"]')?.getAttribute('id');
- if (groupID && groupID.startsWith('code-comments-')) {
- const id = groupID.slice(14);
- const ancestorDiffBox = commentDiv.closest<HTMLElement>('.diff-file-box');
-
- hideElem(`#show-outdated-${id}`);
- showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`);
- // if the comment box is folded, expand it
- if (ancestorDiffBox?.getAttribute('data-folded') === 'true') {
- setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file'), false);
- }
- }
- // set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
- if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
- // wait for a while because some elements (eg: image, editor, etc.) may change the viewport's height.
- setTimeout(() => commentDiv.scrollIntoView({block: 'start'}), 100);
- }
- }
-
- addDelegatedEventListener(document, 'click', '.show-outdated', (el, e) => {
- e.preventDefault();
- const id = el.getAttribute('data-comment');
- hideElem(el);
- showElem(`#code-comments-${id}`);
- showElem(`#code-preview-${id}`);
- showElem(`#hide-outdated-${id}`);
- });
-
- addDelegatedEventListener(document, 'click', '.hide-outdated', (el, e) => {
- e.preventDefault();
- const id = el.getAttribute('data-comment');
- hideElem(el);
- hideElem(`#code-comments-${id}`);
- hideElem(`#code-preview-${id}`);
- showElem(`#show-outdated-${id}`);
- });
-
- addDelegatedEventListener(document, 'click', 'button.comment-form-reply', (el, e) => {
- e.preventDefault();
- handleReply(el);
- });
-
- // The following part is only for diff views
- if (!document.querySelector('.repository.pull.diff')) return;
-
- const elReviewBtn = document.querySelector('.js-btn-review');
- const elReviewPanel = document.querySelector('.review-box-panel.tippy-target');
- if (elReviewBtn && elReviewPanel) {
- const tippy = createTippy(elReviewBtn, {
- content: elReviewPanel,
- theme: 'default',
- placement: 'bottom',
- trigger: 'click',
- maxWidth: 'none',
- interactive: true,
- hideOnClick: true,
- });
- elReviewPanel.querySelector('.close').addEventListener('click', () => tippy.hide());
- }
-
- addDelegatedEventListener(document, 'click', '.add-code-comment', async (el, e) => {
- e.preventDefault();
-
- const isSplit = el.closest('.code-diff')?.classList.contains('code-diff-split');
- const side = el.getAttribute('data-side');
- const idx = el.getAttribute('data-idx');
- const path = el.closest('[data-path]')?.getAttribute('data-path');
- const tr = el.closest('tr');
- const lineType = tr.getAttribute('data-line-type');
-
- let ntr = tr.nextElementSibling;
- if (!ntr?.classList.contains('add-comment')) {
- ntr = createElementFromHTML(`
- <tr class="add-comment" data-line-type="${lineType}">
- ${isSplit ? `
- <td class="add-comment-left" colspan="4"></td>
- <td class="add-comment-right" colspan="4"></td>
- ` : `
- <td class="add-comment-left add-comment-right" colspan="5"></td>
- `}
- </tr>`);
- tr.after(ntr);
- }
- const td = ntr.querySelector(`.add-comment-${side}`);
- const commentCloud = td.querySelector('.comment-code-cloud');
- if (!commentCloud && !ntr.querySelector('button[name="pending_review"]')) {
- const response = await GET(el.closest('[data-new-comment-url]')?.getAttribute('data-new-comment-url'));
- td.innerHTML = await response.text();
- td.querySelector<HTMLInputElement>("input[name='line']").value = idx;
- td.querySelector<HTMLInputElement>("input[name='side']").value = (side === 'left' ? 'previous' : 'proposed');
- td.querySelector<HTMLInputElement>("input[name='path']").value = path;
- const editor = await initComboMarkdownEditor(td.querySelector<HTMLElement>('.combo-markdown-editor'));
- editor.focus();
- }
- });
- }
-
- export function initRepoIssueReferenceIssue() {
- const elDropdown = document.querySelector('.issue_reference_repository_search');
- if (!elDropdown) return;
- const form = elDropdown.closest('form');
- fomanticQuery(elDropdown).dropdown({
- fullTextSearch: true,
- apiSettings: {
- cache: false,
- rawResponse: true,
- url: `${appSubUrl}/repo/search?q={query}&limit=20`,
- onResponse(response: any) {
- const filteredResponse = {success: true, results: [] as Array<Record<string, any>>};
- for (const repo of response.data) {
- filteredResponse.results.push({
- name: htmlEscape(repo.repository.full_name),
- value: repo.repository.full_name,
- });
- }
- return filteredResponse;
- },
- },
- onChange(_value: string, _text: string, _$choice: any) {
- form.setAttribute('action', `${appSubUrl}/${_text}/issues/new`);
- },
- });
-
- // Reference issue
- addDelegatedEventListener(document, 'click', '.reference-issue', (el, e) => {
- e.preventDefault();
- const target = el.getAttribute('data-target');
- const content = document.querySelector(`#${target}`)?.textContent ?? '';
- const poster = el.getAttribute('data-poster-username');
- const reference = toAbsoluteUrl(el.getAttribute('data-reference'));
- const modalSelector = el.getAttribute('data-modal');
- const modal = document.querySelector(modalSelector);
- const textarea = modal.querySelector<HTMLTextAreaElement>('textarea[name="content"]');
- textarea.value = `${content}\n\n_Originally posted by @${poster} in ${reference}_`;
- fomanticQuery(modal).modal('show');
- });
- }
-
- export function initRepoIssueWipNewTitle() {
- // Toggle WIP for new PR
- queryElems(document, '.title_wip_desc > a', (el) => el.addEventListener('click', (e) => {
- e.preventDefault();
- const wipPrefixes = JSON.parse(el.closest('.title_wip_desc').getAttribute('data-wip-prefixes'));
- const titleInput = document.querySelector<HTMLInputElement>('#issue_title');
- const titleValue = titleInput.value;
- for (const prefix of wipPrefixes) {
- if (titleValue.startsWith(prefix.toUpperCase())) {
- return;
- }
- }
- titleInput.value = `${wipPrefixes[0]} ${titleValue}`;
- }));
- }
-
- export function initRepoIssueWipToggle() {
- // Toggle WIP for existing PR
- registerGlobalInitFunc('initPullRequestWipToggle', (toggleWip) => toggleWip.addEventListener('click', async (e) => {
- e.preventDefault();
- const title = toggleWip.getAttribute('data-title');
- const wipPrefix = toggleWip.getAttribute('data-wip-prefix');
- const updateUrl = toggleWip.getAttribute('data-update-url');
-
- 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();
- }));
- }
-
- export function initRepoIssueTitleEdit() {
- const issueTitleDisplay = document.querySelector('#issue-title-display');
- const issueTitleEditor = document.querySelector<HTMLFormElement>('#issue-title-editor');
- if (!issueTitleEditor) return;
-
- const issueTitleInput = issueTitleEditor.querySelector('input');
- const oldTitle = issueTitleInput.getAttribute('data-old-title');
- issueTitleDisplay.querySelector('#issue-title-edit-show').addEventListener('click', () => {
- hideElem(issueTitleDisplay);
- hideElem('#pull-desc-display');
- showElem(issueTitleEditor);
- showElem('#pull-desc-editor');
- if (!issueTitleInput.value.trim()) {
- issueTitleInput.value = oldTitle;
- }
- issueTitleInput.focus();
- });
- issueTitleEditor.querySelector('.ui.cancel.button').addEventListener('click', () => {
- hideElem(issueTitleEditor);
- hideElem('#pull-desc-editor');
- showElem(issueTitleDisplay);
- showElem('#pull-desc-display');
- });
-
- const pullDescEditor = document.querySelector('#pull-desc-editor'); // it may not exist for a merged PR
- const prTargetUpdateUrl = pullDescEditor?.getAttribute('data-target-update-url');
-
- const editSaveButton = issueTitleEditor.querySelector('.ui.primary.button');
- issueTitleEditor.addEventListener('submit', async (e) => {
- e.preventDefault();
- const newTitle = issueTitleInput.value.trim();
- try {
- if (newTitle && newTitle !== oldTitle) {
- const resp = await POST(editSaveButton.getAttribute('data-update-url'), {data: new URLSearchParams({title: newTitle})});
- if (!resp.ok) {
- throw new Error(`Failed to update issue title: ${resp.statusText}`);
- }
- }
- if (prTargetUpdateUrl) {
- const newTargetBranch = document.querySelector('#pull-target-branch').getAttribute('data-branch');
- const oldTargetBranch = document.querySelector('#branch_target').textContent;
- if (newTargetBranch !== oldTargetBranch) {
- const resp = await POST(prTargetUpdateUrl, {data: new URLSearchParams({target_branch: newTargetBranch})});
- if (!resp.ok) {
- throw new Error(`Failed to update PR target branch: ${resp.statusText}`);
- }
- }
- }
- ignoreAreYouSure(issueTitleEditor);
- window.location.reload();
- } catch (error) {
- console.error(error);
- showErrorToast(error.message);
- }
- });
- }
-
- export function initRepoIssueBranchSelect() {
- document.querySelector<HTMLElement>('#branch-select')?.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
- const el = e.target.closest('.item[data-branch]');
- if (!el) return;
- const pullTargetBranch = document.querySelector('#pull-target-branch');
- const baseName = pullTargetBranch.getAttribute('data-basename');
- const branchNameNew = el.getAttribute('data-branch');
- const branchNameOld = pullTargetBranch.getAttribute('data-branch');
- pullTargetBranch.textContent = pullTargetBranch.textContent.replace(`${baseName}:${branchNameOld}`, `${baseName}:${branchNameNew}`);
- pullTargetBranch.setAttribute('data-branch', branchNameNew);
- });
- }
-
- async function initSingleCommentEditor(commentForm: HTMLFormElement) {
- // pages:
- // * normal new issue/pr page: no status-button, no comment-button (there is only a normal submit button which can submit empty content)
- // * issue/pr view page: with comment form, has status-button and comment-button
- const editor = await initComboMarkdownEditor(commentForm.querySelector('.combo-markdown-editor'));
- const statusButton = document.querySelector<HTMLButtonElement>('#status-button');
- const commentButton = document.querySelector<HTMLButtonElement>('#comment-button');
- const syncUiState = () => {
- const editorText = editor.value().trim(), isUploading = editor.isUploading();
- if (statusButton) {
- statusButton.textContent = statusButton.getAttribute(editorText ? 'data-status-and-comment' : 'data-status');
- statusButton.disabled = isUploading;
- }
- if (commentButton) {
- commentButton.disabled = !editorText || isUploading;
- }
- };
- editor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState);
- editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, syncUiState);
- syncUiState();
- }
-
- function initIssueTemplateCommentEditors(commentForm: HTMLFormElement) {
- // pages:
- // * new issue with issue template
- const comboFields = commentForm.querySelectorAll<HTMLElement>('.combo-editor-dropzone');
-
- const initCombo = async (elCombo: HTMLElement) => {
- const fieldTextarea = elCombo.querySelector<HTMLTextAreaElement>('.form-field-real');
- const dropzoneContainer = elCombo.querySelector<HTMLElement>('.form-field-dropzone');
- const markdownEditor = elCombo.querySelector<HTMLElement>('.combo-markdown-editor');
-
- const editor = await initComboMarkdownEditor(markdownEditor);
- editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => fieldTextarea.value = editor.value());
-
- fieldTextarea.addEventListener('focus', async () => {
- // deactivate all markdown editors
- showElem(commentForm.querySelectorAll('.combo-editor-dropzone .form-field-real'));
- hideElem(commentForm.querySelectorAll('.combo-editor-dropzone .combo-markdown-editor'));
- queryElems(commentForm, '.combo-editor-dropzone .form-field-dropzone', (dropzoneContainer) => {
- // if "form-field-dropzone" exists, then "dropzone" must also exist
- const dropzone = dropzoneContainer.querySelector<HTMLElement>('.dropzone').dropzone;
- const hasUploadedFiles = dropzone.files.length !== 0;
- toggleElem(dropzoneContainer, hasUploadedFiles);
- });
-
- // activate this markdown editor
- hideElem(fieldTextarea);
- showElem(markdownEditor);
- showElem(dropzoneContainer);
-
- await editor.switchToUserPreference();
- editor.focus();
- });
- };
-
- for (const el of comboFields) {
- initCombo(el);
- }
- }
-
- export function initRepoCommentFormAndSidebar() {
- const commentForm = document.querySelector<HTMLFormElement>('.comment.form');
- if (!commentForm) return;
-
- if (commentForm.querySelector('.field.combo-editor-dropzone')) {
- // at the moment, if a form has multiple combo-markdown-editors, it must be an issue template form
- initIssueTemplateCommentEditors(commentForm);
- } else if (commentForm.querySelector('.combo-markdown-editor')) {
- // it's quite unclear about the "comment form" elements, sometimes it's for issue comment, sometimes it's for file editor/uploader message
- initSingleCommentEditor(commentForm);
- }
-
- initRepoIssueSidebar();
- }
|