import './publicpath.js'; import {htmlEscape} from 'escape-goat'; import 'jquery.are-you-sure'; import {initVueEnv} from './components/VueComponentLoader.js'; import {initRepoActivityTopAuthorsChart} from './components/RepoActivityTopAuthors.vue'; import {initDashboardRepoList} from './components/DashboardRepoList.js'; import {initRepoBranchTagDropdown} from './components/RepoBranchTagDropdown.js'; import attachTribute from './features/tribute.js'; import createColorPicker from './features/colorpicker.js'; import createDropzone from './features/dropzone.js'; import initClipboard from './features/clipboard.js'; import initContextPopups from './features/contextpopup.js'; import initGitGraph from './features/gitgraph.js'; import initHeatmap from './features/heatmap.js'; import initImageDiff from './features/imagediff.js'; import initMigration from './features/migration.js'; import initProject from './features/projects.js'; import initServiceWorker from './features/serviceworker.js'; import initTableSort from './features/tablesort.js'; import {initAdminUserListSearchForm} from './features/admin-users.js'; import {createCodeEditor, createMonaco} from './features/codeeditor.js'; import {initMarkupAnchors} from './markup/anchors.js'; import {initNotificationsTable, initNotificationCount} from './features/notification.js'; import {initLastCommitLoader} from './features/lastcommitloader.js'; import {initIssueContentHistory} from './features/issue-content-history.js'; import {initStopwatch} from './features/stopwatch.js'; import {showLineButton} from './code/linebutton.js'; import {initMarkupContent, initCommentContent} from './markup/content.js'; import {stripTags, mqBinarySearch} from './utils.js'; import {svg} from './svg.js'; const {AppSubUrl, csrf} = window.config; let previewFileModes; const commentMDEditors = {}; // Silence fomantic's error logging when tabs are used without a target content element $.fn.tab.settings.silent = true; initVueEnv(); function initCommentPreviewTab($form) { const $tabMenu = $form.find('.tabular.menu'); $tabMenu.find('.item').tab(); $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`).on('click', function () { const $this = $(this); $.post($this.data('url'), { _csrf: csrf, mode: 'comment', context: $this.data('context'), text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val() }, (data) => { const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`); $previewPanel.html(data); initMarkupContent(); }); }); buttonsClickOnEnter(); } function initEditPreviewTab($form) { const $tabMenu = $form.find('.tabular.menu'); $tabMenu.find('.item').tab(); const $previewTab = $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`); if ($previewTab.length) { previewFileModes = $previewTab.data('preview-file-modes').split(','); $previewTab.on('click', function () { const $this = $(this); let context = `${$this.data('context')}/`; const mode = $this.data('markdown-mode') || 'comment'; const treePathEl = $form.find('input#tree_path'); if (treePathEl.length > 0) { context += treePathEl.val(); } context = context.substring(0, context.lastIndexOf('/')); $.post($this.data('url'), { _csrf: csrf, mode, context, text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val() }, (data) => { const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`); $previewPanel.html(data); initMarkupContent(); }); }); } } function initEditDiffTab($form) { const $tabMenu = $form.find('.tabular.menu'); $tabMenu.find('.item').tab(); $tabMenu.find(`.item[data-tab="${$tabMenu.data('diff')}"]`).on('click', function () { const $this = $(this); $.post($this.data('url'), { _csrf: csrf, context: $this.data('context'), content: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val() }, (data) => { const $diffPreviewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('diff')}"]`); $diffPreviewPanel.html(data); }); }); } function initEditForm() { if ($('.edit.form').length === 0) { return; } initEditPreviewTab($('.edit.form')); initEditDiffTab($('.edit.form')); } function initBranchSelector() { const $selectBranch = $('.ui.select-branch'); const $branchMenu = $selectBranch.find('.reference-list-menu'); const $isNewIssue = $branchMenu.hasClass('new-issue'); $branchMenu.find('.item:not(.no-select)').click(function () { const selectedValue = $(this).data('id'); const editMode = $('#editing_mode').val(); $($(this).data('id-selector')).val(selectedValue); if ($isNewIssue) { $selectBranch.find('.ui .branch-name').text($(this).data('name')); return; } if (editMode === 'true') { const form = $('#update_issueref_form'); $.post(form.attr('action'), { _csrf: csrf, ref: selectedValue }, () => { window.location.reload(); }); } else if (editMode === '') { $selectBranch.find('.ui .branch-name').text(selectedValue); } }); $selectBranch.find('.reference.column').on('click', function () { $selectBranch.find('.scrolling.reference-list-menu').css('display', 'none'); $selectBranch.find('.reference .text').removeClass('black'); $($(this).data('target')).css('display', 'block'); $(this).find('.text').addClass('black'); return false; }); } function initLabelEdit() { // Create label const $newLabelPanel = $('.new-label.segment'); $('.new-label.button').on('click', () => { $newLabelPanel.show(); }); $('.new-label.segment .cancel').on('click', () => { $newLabelPanel.hide(); }); initColorPicker(); $('.edit-label-button').on('click', function () { $('.edit-label .color-picker').minicolors('value', $(this).data('color')); $('#label-modal-id').val($(this).data('id')); $('.edit-label .new-label-input').val($(this).data('title')); $('.edit-label .new-label-desc-input').val($(this).data('description')); $('.edit-label .color-picker').val($(this).data('color')); $('.edit-label .minicolors-swatch-color').css('background-color', $(this).data('color')); $('.edit-label.modal').modal({ onApprove() { $('.edit-label.form').trigger('submit'); } }).modal('show'); return false; }); } function initColorPicker() { createColorPicker($('.color-picker')); $('.precolors .color').on('click', function () { const color_hex = $(this).data('color-hex'); $('.color-picker').val(color_hex); $('.minicolors-swatch-color').css('background-color', color_hex); }); } function updateIssuesMeta(url, action, issueIds, elementId) { return new Promise(((resolve) => { $.ajax({ type: 'POST', url, data: { _csrf: csrf, action, issue_ids: issueIds, id: elementId, }, success: resolve }); })); } function initRepoStatusChecker() { const migrating = $('#repo_migrating'); $('#repo_migrating_failed').hide(); $('#repo_migrating_failed_image').hide(); $('#repo_migrating_progress_message').hide(); if (migrating) { const task = migrating.attr('task'); if (typeof task === 'undefined') { return; } $.ajax({ type: 'GET', url: `${AppSubUrl}/user/task/${task}`, data: { _csrf: csrf, }, complete(xhr) { if (xhr.status === 200 && xhr.responseJSON) { if (xhr.responseJSON.status === 4) { window.location.reload(); return; } else if (xhr.responseJSON.status === 3) { $('#repo_migrating_progress').hide(); $('#repo_migrating').hide(); $('#repo_migrating_failed').show(); $('#repo_migrating_failed_image').show(); $('#repo_migrating_failed_error').text(xhr.responseJSON.message); return; } if (xhr.responseJSON.message) { $('#repo_migrating_progress_message').show(); $('#repo_migrating_progress_message').text(xhr.responseJSON.message); } setTimeout(() => { initRepoStatusChecker(); }, 2000); return; } $('#repo_migrating_progress').hide(); $('#repo_migrating').hide(); $('#repo_migrating_failed').show(); $('#repo_migrating_failed_image').show(); } }); } } function initReactionSelector(parent) { let reactions = ''; if (!parent) { parent = $(document); reactions = '.reactions > '; } parent.find(`${reactions}a.label`).popup({position: 'bottom left', metadata: {content: 'title', title: 'none'}}); parent.find(`.select-reaction > .menu > .item, ${reactions}a.label`).on('click', function (e) { e.preventDefault(); if ($(this).hasClass('disabled')) return; const actionURL = $(this).hasClass('item') ? $(this).closest('.select-reaction').data('action-url') : $(this).data('action-url'); const url = `${actionURL}/${$(this).hasClass('blue') ? 'unreact' : 'react'}`; $.ajax({ type: 'POST', url, data: { _csrf: csrf, content: $(this).data('content') } }).done((resp) => { if (resp && (resp.html || resp.empty)) { const content = $(this).closest('.content'); let react = content.find('.segment.reactions'); if ((!resp.empty || resp.html === '') && react.length > 0) { react.remove(); } if (!resp.empty) { react = $('
'); const attachments = content.find('.segment.bottom:first'); if (attachments.length > 0) { react.insertBefore(attachments); } else { react.appendTo(content); } react.html(resp.html); react.find('.dropdown').dropdown(); initReactionSelector(react); } } }); }); } function insertAtCursor(field, value) { if (field.selectionStart || field.selectionStart === 0) { const startPos = field.selectionStart; const endPos = field.selectionEnd; field.value = field.value.substring(0, startPos) + value + field.value.substring(endPos, field.value.length); field.selectionStart = startPos + value.length; field.selectionEnd = startPos + value.length; } else { field.value += value; } } function replaceAndKeepCursor(field, oldval, newval) { if (field.selectionStart || field.selectionStart === 0) { const startPos = field.selectionStart; const endPos = field.selectionEnd; field.value = field.value.replace(oldval, newval); field.selectionStart = startPos + newval.length - oldval.length; field.selectionEnd = endPos + newval.length - oldval.length; } else { field.value = field.value.replace(oldval, newval); } } function getPastedImages(e) { if (!e.clipboardData) return []; const files = []; for (const item of e.clipboardData.items || []) { if (!item.type || !item.type.startsWith('image/')) continue; files.push(item.getAsFile()); } if (files.length) { e.preventDefault(); e.stopPropagation(); } return files; } async function uploadFile(file, uploadUrl) { const formData = new FormData(); formData.append('file', file, file.name); const res = await fetch(uploadUrl, { method: 'POST', headers: {'X-Csrf-Token': csrf}, body: formData, }); return await res.json(); } function reload() { window.location.reload(); } function initImagePaste(target) { target.each(function () { const dropzone = this.querySelector('.dropzone'); if (!dropzone) { return; } const uploadUrl = dropzone.dataset.uploadUrl; const dropzoneFiles = dropzone.querySelector('.files'); for (const textarea of this.querySelectorAll('textarea')) { textarea.addEventListener('paste', async (e) => { for (const img of getPastedImages(e)) { const name = img.name.substr(0, img.name.lastIndexOf('.')); insertAtCursor(textarea, `![${name}]()`); const data = await uploadFile(img, uploadUrl); replaceAndKeepCursor(textarea, `![${name}]()`, `![${name}](${AppSubUrl}/attachments/${data.uuid})`); const input = $(``).val(data.uuid); dropzoneFiles.appendChild(input[0]); } }, false); } }); } function initSimpleMDEImagePaste(simplemde, dropzone, files) { const uploadUrl = dropzone.dataset.uploadUrl; simplemde.codemirror.on('paste', async (_, e) => { for (const img of getPastedImages(e)) { const name = img.name.substr(0, img.name.lastIndexOf('.')); const data = await uploadFile(img, uploadUrl); const pos = simplemde.codemirror.getCursor(); simplemde.codemirror.replaceRange(`![${name}](${AppSubUrl}/attachments/${data.uuid})`, pos); const input = $(``).val(data.uuid); files.append(input); } }); } let autoSimpleMDE; function initCommentForm() { if ($('.comment.form').length === 0) { return; } autoSimpleMDE = setCommentSimpleMDE($('.comment.form textarea:not(.review-textarea)')); initBranchSelector(); initCommentPreviewTab($('.comment.form')); initImagePaste($('.comment.form')); // Listsubmit function initListSubmits(selector, outerSelector) { const $list = $(`.ui.${outerSelector}.list`); const $noSelect = $list.find('.no-select'); const $listMenu = $(`.${selector} .menu`); let hasUpdateAction = $listMenu.data('action') === 'update'; const items = {}; $(`.${selector}`).dropdown('setting', 'onHide', () => { hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var if (hasUpdateAction) { const promises = []; Object.keys(items).forEach((elementId) => { const item = items[elementId]; const promise = updateIssuesMeta( item['update-url'], item.action, item['issue-id'], elementId, ); promises.push(promise); }); Promise.all(promises).then(reload); } }); $listMenu.find('.item:not(.no-select)').on('click', function (e) { e.preventDefault(); if ($(this).hasClass('ban-change')) { return false; } hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var if ($(this).hasClass('checked')) { $(this).removeClass('checked'); $(this).find('.octicon-check').addClass('invisible'); if (hasUpdateAction) { if (!($(this).data('id') in items)) { items[$(this).data('id')] = { 'update-url': $listMenu.data('update-url'), action: 'detach', 'issue-id': $listMenu.data('issue-id'), }; } else { delete items[$(this).data('id')]; } } } else { $(this).addClass('checked'); $(this).find('.octicon-check').removeClass('invisible'); if (hasUpdateAction) { if (!($(this).data('id') in items)) { items[$(this).data('id')] = { 'update-url': $listMenu.data('update-url'), action: 'attach', 'issue-id': $listMenu.data('issue-id'), }; } else { delete items[$(this).data('id')]; } } } // TODO: Which thing should be done for choosing review requests // to make chosen items be shown on time here? if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { return false; } const listIds = []; $(this).parent().find('.item').each(function () { if ($(this).hasClass('checked')) { listIds.push($(this).data('id')); $($(this).data('id-selector')).removeClass('hide'); } else { $($(this).data('id-selector')).addClass('hide'); } }); if (listIds.length === 0) { $noSelect.removeClass('hide'); } else { $noSelect.addClass('hide'); } $($(this).parent().data('id')).val(listIds.join(',')); return false; }); $listMenu.find('.no-select.item').on('click', function (e) { e.preventDefault(); if (hasUpdateAction) { updateIssuesMeta( $listMenu.data('update-url'), 'clear', $listMenu.data('issue-id'), '', ).then(reload); } $(this).parent().find('.item').each(function () { $(this).removeClass('checked'); $(this).find('.octicon').addClass('invisible'); }); if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { return false; } $list.find('.item').each(function () { $(this).addClass('hide'); }); $noSelect.removeClass('hide'); $($(this).parent().data('id')).val(''); }); } // Init labels and assignees initListSubmits('select-label', 'labels'); initListSubmits('select-assignees', 'assignees'); initListSubmits('select-assignees-modify', 'assignees'); initListSubmits('select-reviewers-modify', 'assignees'); function selectItem(select_id, input_id) { const $menu = $(`${select_id} .menu`); const $list = $(`.ui${select_id}.list`); const hasUpdateAction = $menu.data('action') === 'update'; $menu.find('.item:not(.no-select)').on('click', function () { $(this).parent().find('.item').each(function () { $(this).removeClass('selected active'); }); $(this).addClass('selected active'); if (hasUpdateAction) { updateIssuesMeta( $menu.data('update-url'), '', $menu.data('issue-id'), $(this).data('id'), ).then(reload); } let icon = ''; if (input_id === '#milestone_id') { icon = svg('octicon-milestone', 18, 'mr-3'); } else if (input_id === '#project_id') { icon = svg('octicon-project', 18, 'mr-3'); } else if (input_id === '#assignee_id') { icon = `