diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2019-11-13 22:52:13 +0800 |
---|---|---|
committer | Antoine GIRARD <sapk@users.noreply.github.com> | 2019-11-13 15:52:13 +0100 |
commit | afe50873a5d6b52177b0cd6bc9d2657faf82f311 (patch) | |
tree | 0d9fb3ef4d4ba5739c52f2fcb38ad96263346f08 /web_src | |
parent | 71395dffd0db0c53353e4b0cf0d687ece0032597 (diff) | |
download | gitea-afe50873a5d6b52177b0cd6bc9d2657faf82f311.tar.gz gitea-afe50873a5d6b52177b0cd6bc9d2657faf82f311.zip |
Move index.js to web_src and use webpack to pack them (#8598)
* Move index.js to web_src and use webpack
* Fix initHeatMap
* update eslint and move webpack to devDependencies
* update index.js
* add eslint env node
Diffstat (limited to 'web_src')
-rw-r--r-- | web_src/js/index.js | 3424 |
1 files changed, 3424 insertions, 0 deletions
diff --git a/web_src/js/index.js b/web_src/js/index.js new file mode 100644 index 0000000000..900b5893a5 --- /dev/null +++ b/web_src/js/index.js @@ -0,0 +1,3424 @@ +/* globals wipPrefixes, issuesTribute, emojiTribute */ +/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap */ +/* exported toggleDeadlineForm, setDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */ +'use strict'; + +function htmlEncode(text) { + return jQuery('<div />').text(text).html() +} + +let csrf; +let suburl; +let previewFileModes; +let simpleMDEditor; +let codeMirrorEditor; + +// Disable Dropzone auto-discover because it's manually initialized +if (typeof(Dropzone) !== "undefined") { + Dropzone.autoDiscover = false; +} + +// Polyfill for IE9+ support (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) +if (!Array.from) { + Array.from = (function () { + const toStr = Object.prototype.toString; + const isCallable = function (fn) { + return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; + }; + const toInteger = function (value) { + const number = Number(value); + if (isNaN(number)) { return 0; } + if (number === 0 || !isFinite(number)) { return number; } + return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); + }; + const maxSafeInteger = Math.pow(2, 53) - 1; + const toLength = function (value) { + const len = toInteger(value); + return Math.min(Math.max(len, 0), maxSafeInteger); + }; + + // The length property of the from method is 1. + return function from(arrayLike/*, mapFn, thisArg */) { + // 1. Let C be the this value. + const C = this; + + // 2. Let items be ToObject(arrayLike). + const items = Object(arrayLike); + + // 3. ReturnIfAbrupt(items). + if (arrayLike == null) { + throw new TypeError("Array.from requires an array-like object - not null or undefined"); + } + + // 4. If mapfn is undefined, then let mapping be false. + const mapFn = arguments.length > 1 ? arguments[1] : void undefined; + let T; + if (typeof mapFn !== 'undefined') { + // 5. else + // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. + if (!isCallable(mapFn)) { + throw new TypeError('Array.from: when provided, the second argument must be a function'); + } + + // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. + if (arguments.length > 2) { + T = arguments[2]; + } + } + + // 10. Let lenValue be Get(items, "length"). + // 11. Let len be ToLength(lenValue). + const len = toLength(items.length); + + // 13. If IsConstructor(C) is true, then + // 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len. + // 14. a. Else, Let A be ArrayCreate(len). + const A = isCallable(C) ? Object(new C(len)) : new Array(len); + + // 16. Let k be 0. + let k = 0; + // 17. Repeat, while k < len… (also steps a - h) + let kValue; + while (k < len) { + kValue = items[k]; + if (mapFn) { + A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); + } else { + A[k] = kValue; + } + k += 1; + } + // 18. Let putStatus be Put(A, "length", len, true). + A.length = len; + // 20. Return A. + return A; + }; + }()); +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign +if (typeof Object.assign != 'function') { + // Must be writable: true, enumerable: false, configurable: true + Object.defineProperty(Object, "assign", { + value: function assign(target, _varArgs) { // .length of function is 2 + 'use strict'; + if (target == null) { // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + const to = Object(target); + + for (let index = 1; index < arguments.length; index++) { + const nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (const nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }, + writable: true, + configurable: true + }); +} + +function initCommentPreviewTab($form) { + const $tabMenu = $form.find('.tabular.menu'); + $tabMenu.find('.item').tab(); + $tabMenu.find('.item[data-tab="' + $tabMenu.data('preview') + '"]').click(function () { + const $this = $(this); + $.post($this.data('url'), { + "_csrf": csrf, + "mode": "gfm", + "context": $this.data('context'), + "text": $form.find('.tab.segment[data-tab="' + $tabMenu.data('write') + '"] textarea').val() + }, + function (data) { + const $previewPanel = $form.find('.tab.segment[data-tab="' + $tabMenu.data('preview') + '"]'); + $previewPanel.html(data); + emojify.run($previewPanel[0]); + $('pre code', $previewPanel[0]).each(function () { + hljs.highlightBlock(this); + }); + } + ); + }); + + 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.click(function () { + const $this = $(this); + $.post($this.data('url'), { + "_csrf": csrf, + "mode": "gfm", + "context": $this.data('context'), + "text": $form.find('.tab.segment[data-tab="' + $tabMenu.data('write') + '"] textarea').val() + }, + function (data) { + const $previewPanel = $form.find('.tab.segment[data-tab="' + $tabMenu.data('preview') + '"]'); + $previewPanel.html(data); + emojify.run($previewPanel[0]); + $('pre code', $previewPanel[0]).each(function () { + hljs.highlightBlock(this); + }); + } + ); + }); + } +} + +function initEditDiffTab($form) { + const $tabMenu = $form.find('.tabular.menu'); + $tabMenu.find('.item').tab(); + $tabMenu.find('.item[data-tab="' + $tabMenu.data('diff') + '"]').click(function () { + const $this = $(this); + $.post($this.data('url'), { + "_csrf": csrf, + "context": $this.data('context'), + "content": $form.find('.tab.segment[data-tab="' + $tabMenu.data('write') + '"] textarea').val() + }, + function (data) { + const $diffPreviewPanel = $form.find('.tab.segment[data-tab="' + $tabMenu.data('diff') + '"]'); + $diffPreviewPanel.html(data); + emojify.run($diffPreviewPanel[0]); + } + ); + }); +} + + +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'); + $branchMenu.find('.item:not(.no-select)').click(function () { + const selectedValue = $(this).data('id'); + $($(this).data('id-selector')).val(selectedValue); + $selectBranch.find('.ui .branch-name').text(selectedValue); + }); + $selectBranch.find('.reference.column').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 updateIssuesMeta(url, action, issueIds, elementId) { + return new Promise(function(resolve) { + $.ajax({ + type: "POST", + url: url, + data: { + "_csrf": csrf, + "action": action, + "issue_ids": issueIds, + "id": elementId + }, + success: resolve + }) + }) +} + +function initRepoStatusChecker() { + const migrating = $("#repo_migrating"); + $('#repo_migrating_failed').hide(); + if (migrating) { + const repo_name = migrating.attr('repo'); + if (typeof repo_name === 'undefined') { + return + } + $.ajax({ + type: "GET", + url: suburl +"/"+repo_name+"/status", + data: { + "_csrf": csrf, + }, + complete: function(xhr) { + if (xhr.status == 200) { + if (xhr.responseJSON) { + if (xhr.responseJSON["status"] == 0) { + location.reload(); + return + } + + setTimeout(function () { + initRepoStatusChecker() + }, 2000); + return + } + } + $('#repo_migrating_progress').hide(); + $('#repo_migrating_failed').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){ + const vm = this; + 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: url, + data: { + '_csrf': csrf, + 'content': $(this).data('content') + } + }).done(function(resp) { + if (resp && (resp.html || resp.empty)) { + const content = $(vm).closest('.content'); + let react = content.find('.segment.reactions'); + if (!resp.empty && react.length > 0) { + react.remove(); + } + if (!resp.empty) { + react = $('<div class="ui attached segment reactions"></div>'); + const attachments = content.find('.segment.bottom:first'); + if (attachments.length > 0) { + react.insertBefore(attachments); + } else { + react.appendTo(content); + } + react.html(resp.html); + const hasEmoji = react.find('.has-emoji'); + for (let i = 0; i < hasEmoji.length; i++) { + emojify.run(hasEmoji.get(i)); + } + 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 retrieveImageFromClipboardAsBlob(pasteEvent, callback){ + if (!pasteEvent.clipboardData) { + return; + } + + const items = pasteEvent.clipboardData.items; + if (typeof(items) === "undefined") { + return; + } + + for (let i = 0; i < items.length; i++) { + if (items[i].type.indexOf("image") === -1) continue; + const blob = items[i].getAsFile(); + + if (typeof(callback) === "function") { + pasteEvent.preventDefault(); + pasteEvent.stopPropagation(); + callback(blob); + } + } +} + +function uploadFile(file, callback) { + const xhr = new XMLHttpRequest(); + + xhr.onload = function() { + if (xhr.status == 200) { + callback(xhr.responseText); + } + }; + + xhr.open("post", suburl + "/attachments", true); + xhr.setRequestHeader("X-Csrf-Token", csrf); + const formData = new FormData(); + formData.append('file', file, file.name); + xhr.send(formData); +} + +function reload() { + window.location.reload(); +} + +function initImagePaste(target) { + target.each(function() { + const field = this; + field.addEventListener('paste', function(event){ + retrieveImageFromClipboardAsBlob(event, function(img) { + const name = img.name.substr(0, img.name.lastIndexOf('.')); + insertAtCursor(field, '![' + name + ']()'); + uploadFile(img, function(res) { + const data = JSON.parse(res); + replaceAndKeepCursor(field, '![' + name + ']()', '![' + name + '](' + suburl + '/attachments/' + data.uuid + ')'); + const input = $('<input id="' + data.uuid + '" name="files" type="hidden">').val(data.uuid); + $('.files').append(input); + }); + }); + }, false); + }); +} + +function initCommentForm() { + if ($('.comment.form').length == 0) { + return + } + + initBranchSelector(); + initCommentPreviewTab($('.comment.form')); + initImagePaste($('.comment.form textarea')); + + // Listsubmit + function initListSubmits(selector, outerSelector) { + const $list = $('.ui.' + outerSelector + '.list'); + const $noSelect = $list.find('.no-select'); + const $listMenu = $('.' + selector + ' .menu'); + let hasLabelUpdateAction = $listMenu.data('action') == 'update'; + const labels = {}; + + $('.' + selector).dropdown('setting', 'onHide', function(){ + hasLabelUpdateAction = $listMenu.data('action') == 'update'; // Update the var + if (hasLabelUpdateAction) { + const promises = []; + Object.keys(labels).forEach(function(elementId) { + const label = labels[elementId]; + const promise = updateIssuesMeta( + label["update-url"], + label["action"], + label["issue-id"], + elementId + ); + promises.push(promise); + }); + Promise.all(promises).then(reload); + } + }); + + $listMenu.find('.item:not(.no-select)').click(function () { + + // we don't need the action attribute when updating assignees + if (selector == 'select-assignees-modify') { + + // UI magic. We need to do this here, otherwise it would destroy the functionality of + // adding/removing labels + if ($(this).hasClass('checked')) { + $(this).removeClass('checked'); + $(this).find('.octicon').removeClass('octicon-check'); + } else { + $(this).addClass('checked'); + $(this).find('.octicon').addClass('octicon-check'); + } + + updateIssuesMeta( + $listMenu.data('update-url'), + "", + $listMenu.data('issue-id'), + $(this).data('id') + ); + $listMenu.data('action', 'update'); // Update to reload the page when we updated items + return false; + } + + if ($(this).hasClass('checked')) { + $(this).removeClass('checked'); + $(this).find('.octicon').removeClass('octicon-check'); + if (hasLabelUpdateAction) { + if (!($(this).data('id') in labels)) { + labels[$(this).data('id')] = { + "update-url": $listMenu.data('update-url'), + "action": "detach", + "issue-id": $listMenu.data('issue-id'), + }; + } else { + delete labels[$(this).data('id')]; + } + } + } else { + $(this).addClass('checked'); + $(this).find('.octicon').addClass('octicon-check'); + if (hasLabelUpdateAction) { + if (!($(this).data('id') in labels)) { + labels[$(this).data('id')] = { + "update-url": $listMenu.data('update-url'), + "action": "attach", + "issue-id": $listMenu.data('issue-id'), + }; + } else { + delete labels[$(this).data('id')]; + } + } + } + + 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').click(function () { + if (hasLabelUpdateAction || selector == 'select-assignees-modify') { + updateIssuesMeta( + $listMenu.data('update-url'), + "clear", + $listMenu.data('issue-id'), + "" + ).then(reload); + } + + $(this).parent().find('.item').each(function () { + $(this).removeClass('checked'); + $(this).find('.octicon').removeClass('octicon-check'); + }); + + $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'); + + 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)').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); + } + switch (input_id) { + case '#milestone_id': + $list.find('.selected').html('<a class="item" href=' + $(this).data('href') + '>' + + htmlEncode($(this).text()) + '</a>'); + break; + case '#assignee_id': + $list.find('.selected').html('<a class="item" href=' + $(this).data('href') + '>' + + '<img class="ui avatar image" src=' + $(this).data('avatar') + '>' + + htmlEncode($(this).text()) + '</a>'); + } + $('.ui' + select_id + '.list .no-select').addClass('hide'); + $(input_id).val($(this).data('id')); + }); + $menu.find('.no-select.item').click(function () { + $(this).parent().find('.item:not(.no-select)').each(function () { + $(this).removeClass('selected active') + }); + + if (hasUpdateAction) { + updateIssuesMeta( + $menu.data('update-url'), + "", + $menu.data('issue-id'), + $(this).data('id') + ).then(reload); + } + + $list.find('.selected').html(''); + $list.find('.no-select').removeClass('hide'); + $(input_id).val(''); + }); + } + + // Milestone and assignee + selectItem('.select-milestone', '#milestone_id'); + selectItem('.select-assignee', '#assignee_id'); +} + +function initInstall() { + if ($('.install').length == 0) { + return; + } + + if ($('#db_host').val()=="") { + $('#db_host').val("127.0.0.1:3306"); + $('#db_user').val("gitea"); + $('#db_name').val("gitea"); + } + + // Database type change detection. + $("#db_type").change(function () { + const sqliteDefault = 'data/gitea.db'; + const tidbDefault = 'data/gitea_tidb'; + + const dbType = $(this).val(); + if (dbType === "SQLite3") { + $('#sql_settings').hide(); + $('#pgsql_settings').hide(); + $('#mysql_settings').hide(); + $('#sqlite_settings').show(); + + if (dbType === "SQLite3" && $('#db_path').val() == tidbDefault) { + $('#db_path').val(sqliteDefault); + } + return; + } + + const dbDefaults = { + "MySQL": "127.0.0.1:3306", + "PostgreSQL": "127.0.0.1:5432", + "MSSQL": "127.0.0.1:1433" + }; + + $('#sqlite_settings').hide(); + $('#sql_settings').show(); + + $('#pgsql_settings').toggle(dbType === "PostgreSQL"); + $('#mysql_settings').toggle(dbType === "MySQL"); + $.each(dbDefaults, function(_type, defaultHost) { + if ($('#db_host').val() == defaultHost) { + $('#db_host').val(dbDefaults[dbType]); + return false; + } + }); + }); + + // TODO: better handling of exclusive relations. + $('#offline-mode input').change(function () { + if ($(this).is(':checked')) { + $('#disable-gravatar').checkbox('check'); + $('#federated-avatar-lookup').checkbox('uncheck'); + } + }); + $('#disable-gravatar input').change(function () { + if ($(this).is(':checked')) { + $('#federated-avatar-lookup').checkbox('uncheck'); + } else { + $('#offline-mode').checkbox('uncheck'); + } + }); + $('#federated-avatar-lookup input').change(function () { + if ($(this).is(':checked')) { + $('#disable-gravatar').checkbox('uncheck'); + $('#offline-mode').checkbox('uncheck'); + } + }); + $('#enable-openid-signin input').change(function () { + if ($(this).is(':checked')) { + if (!$('#disable-registration input').is(':checked')) { + $('#enable-openid-signup').checkbox('check'); + } + } else { + $('#enable-openid-signup').checkbox('uncheck'); + } + }); + $('#disable-registration input').change(function () { + if ($(this).is(':checked')) { + $('#enable-captcha').checkbox('uncheck'); + $('#enable-openid-signup').checkbox('uncheck'); + } else { + $('#enable-openid-signup').checkbox('check'); + } + }); + $('#enable-captcha input').change(function () { + if ($(this).is(':checked')) { + $('#disable-registration').checkbox('uncheck'); + } + }); +} + +function initRepository() { + if ($('.repository').length == 0) { + return; + } + + function initFilterSearchDropdown(selector) { + const $dropdown = $(selector); + $dropdown.dropdown({ + fullTextSearch: true, + selectOnKeydown: false, + onChange: function (_text, _value, $choice) { + if ($choice.data('url')) { + window.location.href = $choice.data('url'); + } + }, + message: {noResults: $dropdown.data('no-results')} + }); + } + + // File list and commits + if ($('.repository.file.list').length > 0 || + ('.repository.commits').length > 0) { + initFilterBranchTagDropdown('.choose.reference .dropdown'); + } + + // Wiki + if ($('.repository.wiki.view').length > 0) { + initFilterSearchDropdown('.choose.page .dropdown'); + } + + // Options + if ($('.repository.settings.options').length > 0) { + $('#repo_name').keyup(function () { + const $prompt = $('#repo-name-change-prompt'); + if ($(this).val().toString().toLowerCase() != $(this).data('name').toString().toLowerCase()) { + $prompt.show(); + } else { + $prompt.hide(); + } + }); + + // Enable or select internal/external wiki system and issue tracker. + $('.enable-system').change(function () { + if (this.checked) { + $($(this).data('target')).removeClass('disabled'); + if (!$(this).data('context')) $($(this).data('context')).addClass('disabled'); + } else { + $($(this).data('target')).addClass('disabled'); + if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled'); + } + }); + $('.enable-system-radio').change(function () { + if (this.value == 'false') { + $($(this).data('target')).addClass('disabled'); + if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).removeClass('disabled'); + } else if (this.value == 'true') { + $($(this).data('target')).removeClass('disabled'); + if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).addClass('disabled'); + } + }); + } + + // Labels + if ($('.repository.labels').length > 0) { + // Create label + const $newLabelPanel = $('.new-label.segment'); + $('.new-label.button').click(function () { + $newLabelPanel.show(); + }); + $('.new-label.segment .cancel').click(function () { + $newLabelPanel.hide(); + }); + + $('.color-picker').each(function () { + $(this).minicolors(); + }); + $('.precolors .color').click(function () { + const color_hex = $(this).data('color-hex'); + $('.color-picker').val(color_hex); + $('.minicolors-swatch-color').css("background-color", color_hex); + }); + $('.edit-label-button').click(function () { + $('#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')); + $('.minicolors-swatch-color').css("background-color", $(this).data('color')); + $('.edit-label.modal').modal({ + onApprove: function () { + $('.edit-label.form').submit(); + } + }).modal('show'); + return false; + }); + } + + // Milestones + if ($('.repository.new.milestone').length > 0) { + const $datepicker = $('.milestone.datepicker'); + $datepicker.datetimepicker({ + lang: $datepicker.data('lang'), + inline: true, + timepicker: false, + startDate: $datepicker.data('start-date'), + formatDate: 'Y-m-d', + onSelectDate: function (ct) { + $('#deadline').val(ct.dateFormat('Y-m-d')); + } + }); + $('#clear-date').click(function () { + $('#deadline').val(''); + return false; + }); + } + + // Issues + if ($('.repository.view.issue').length > 0) { + // Edit issue title + const $issueTitle = $('#issue-title'); + const $editInput = $('#edit-title-input input'); + const editTitleToggle = function () { + $issueTitle.toggle(); + $('.not-in-edit').toggle(); + $('#edit-title-input').toggle(); + $('.in-edit').toggle(); + $editInput.focus(); + return false; + }; + $('#edit-title').click(editTitleToggle); + $('#cancel-edit-title').click(editTitleToggle); + $('#save-edit-title').click(editTitleToggle).click(function () { + if ($editInput.val().length == 0 || + $editInput.val() == $issueTitle.text()) { + $editInput.val($issueTitle.text()); + return false; + } + + $.post($(this).data('update-url'), { + "_csrf": csrf, + "title": $editInput.val() + }, + function (data) { + $editInput.val(data.title); + $issueTitle.text(data.title); + reload(); + }); + return false; + }); + + // Edit issue or comment content + $('.edit-content').click(function () { + const $segment = $(this).parent().parent().parent().next(); + const $editContentZone = $segment.find('.edit-content-zone'); + const $renderContent = $segment.find('.render-content'); + const $rawContent = $segment.find('.raw-content'); + let $textarea; + + // Setup new form + if ($editContentZone.html().length == 0) { + $editContentZone.html($('#edit-content-form').html()); + $textarea = $editContentZone.find('textarea'); + issuesTribute.attach($textarea.get()); + emojiTribute.attach($textarea.get()); + + const $dropzone = $editContentZone.find('.dropzone'); + $dropzone.data("saved", false); + const $files = $editContentZone.find('.comment-files'); + if ($dropzone.length > 0) { + const filenameDict = {}; + $dropzone.dropzone({ + url: $dropzone.data('upload-url'), + headers: {"X-Csrf-Token": csrf}, + maxFiles: $dropzone.data('max-file'), + maxFilesize: $dropzone.data('max-size'), + acceptedFiles: ($dropzone.data('accepts') === '*/*') ? null : $dropzone.data('accepts'), + addRemoveLinks: true, + dictDefaultMessage: $dropzone.data('default-message'), + dictInvalidFileType: $dropzone.data('invalid-input-type'), + dictFileTooBig: $dropzone.data('file-too-big'), + dictRemoveFile: $dropzone.data('remove-file'), + init: function () { + this.on("success", function (file, data) { + filenameDict[file.name] = { + "uuid": data.uuid, + "submitted": false + } + const input = $('<input id="' + data.uuid + '" name="files" type="hidden">').val(data.uuid); + $files.append(input); + }); + this.on("removedfile", function (file) { + if (!(file.name in filenameDict)) { + return; + } + $('#' + filenameDict[file.name].uuid).remove(); + if ($dropzone.data('remove-url') && $dropzone.data('csrf') && !filenameDict[file.name].submitted) { + $.post($dropzone.data('remove-url'), { + file: filenameDict[file.name].uuid, + _csrf: $dropzone.data('csrf') + }); + } + }); + this.on("submit", function () { + $.each(filenameDict, function(name){ + filenameDict[name].submitted = true; + }); + }); + this.on("reload", function (){ + $.getJSON($editContentZone.data('attachment-url'), function(data){ + const drop = $dropzone.get(0).dropzone; + drop.removeAllFiles(true); + $files.empty(); + $.each(data, function(){ + const imgSrc = $dropzone.data('upload-url') + "/" + this.uuid; + drop.emit("addedfile", this); + drop.emit("thumbnail", this, imgSrc); + drop.emit("complete", this); + drop.files.push(this); + filenameDict[this.name] = { + "submitted": true, + "uuid": this.uuid + } + $dropzone.find("img[src='" + imgSrc + "']").css("max-width", "100%"); + const input = $('<input id="' + this.uuid + '" name="files" type="hidden">').val(this.uuid); + $files.append(input); + }); + }); + }); + } + }); + $dropzone.get(0).dropzone.emit("reload"); + } + // Give new write/preview data-tab name to distinguish from others + const $editContentForm = $editContentZone.find('.ui.comment.form'); + const $tabMenu = $editContentForm.find('.tabular.menu'); + $tabMenu.attr('data-write', $editContentZone.data('write')); + $tabMenu.attr('data-preview', $editContentZone.data('preview')); + $tabMenu.find('.write.item').attr('data-tab', $editContentZone.data('write')); + $tabMenu.find('.preview.item').attr('data-tab', $editContentZone.data('preview')); + $editContentForm.find('.write.segment').attr('data-tab', $editContentZone.data('write')); + $editContentForm.find('.preview.segment').attr('data-tab', $editContentZone.data('preview')); + + initCommentPreviewTab($editContentForm); + + $editContentZone.find('.cancel.button').click(function () { + $renderContent.show(); + $editContentZone.hide(); + $dropzone.get(0).dropzone.emit("reload"); + }); + $editContentZone.find('.save.button').click(function () { + $renderContent.show(); + $editContentZone.hide(); + const $attachments = $files.find("[name=files]").map(function(){ + return $(this).val(); + }).get(); + $.post($editContentZone.data('update-url'), { + "_csrf": csrf, + "content": $textarea.val(), + "context": $editContentZone.data('context'), + "files": $attachments + }, + function (data) { + if (data.length == 0) { + $renderContent.html($('#no-content').html()); + } else { + $renderContent.html(data.content); + emojify.run($renderContent[0]); + $('pre code', $renderContent[0]).each(function () { + hljs.highlightBlock(this); + }); + } + const $content = $segment.parent(); + if(!$content.find(".ui.small.images").length){ + if(data.attachments != ""){ + $content.append( + '<div class="ui bottom attached segment">' + + ' <div class="ui small images">' + + ' </div>' + + '</div>' + ); + $content.find(".ui.small.images").html(data.attachments); + } + } else if (data.attachments == "") { + $content.find(".ui.small.images").parent().remove(); + } else { + $content.find(".ui.small.images").html(data.attachments); + } + $dropzone.get(0).dropzone.emit("submit"); + $dropzone.get(0).dropzone.emit("reload"); + }); + }); + } else { + $textarea = $segment.find('textarea'); + } + + // Show write/preview tab and copy raw content as needed + $editContentZone.show(); + $renderContent.hide(); + if ($textarea.val().length == 0) { + $textarea.val($rawContent.text()); + } + $textarea.focus(); + return false; + }); + + // Delete comment + $('.delete-comment').click(function () { + const $this = $(this); + if (confirm($this.data('locale'))) { + $.post($this.data('url'), { + "_csrf": csrf + }).success(function () { + $('#' + $this.data('comment-id')).remove(); + }); + } + return false; + }); + + // Change status + const $statusButton = $('#status-button'); + $('#comment-form .edit_area').keyup(function () { + if ($(this).val().length == 0) { + $statusButton.text($statusButton.data('status')) + } else { + $statusButton.text($statusButton.data('status-and-comment')) + } + }); + $statusButton.click(function () { + $('#status').val($statusButton.data('status-val')); + $('#comment-form').submit(); + }); + + // Pull Request merge button + const $mergeButton = $('.merge-button > button'); + $mergeButton.on('click', function(e) { + e.preventDefault(); + $('.' + $(this).data('do') + '-fields').show(); + $(this).parent().hide(); + }); + $('.merge-button > .dropdown').dropdown({ + onChange: function (_text, _value, $choice) { + if ($choice.data('do')) { + $mergeButton.find('.button-text').text($choice.text()); + $mergeButton.data('do', $choice.data('do')); + } + } + }); + $('.merge-cancel').on('click', function(e) { + e.preventDefault(); + $(this).closest('.form').hide(); + $mergeButton.parent().show(); + }); + + initReactionSelector(); + } + + // Diff + if ($('.repository.diff').length > 0) { + $('.diff-counter').each(function () { + const $item = $(this); + const addLine = $item.find('span[data-line].add').data("line"); + const delLine = $item.find('span[data-line].del').data("line"); + const addPercent = parseFloat(addLine) / (parseFloat(addLine) + parseFloat(delLine)) * 100; + $item.find(".bar .add").css("width", addPercent + "%"); + }); + } + + // Quick start and repository home + $('#repo-clone-ssh').click(function () { + $('.clone-url').text($(this).data('link')); + $('#repo-clone-url').val($(this).data('link')); + $(this).addClass('blue'); + $('#repo-clone-https').removeClass('blue'); + localStorage.setItem('repo-clone-protocol', 'ssh'); + }); + $('#repo-clone-https').click(function () { + $('.clone-url').text($(this).data('link')); + $('#repo-clone-url').val($(this).data('link')); + $(this).addClass('blue'); + $('#repo-clone-ssh').removeClass('blue'); + localStorage.setItem('repo-clone-protocol', 'https'); + }); + $('#repo-clone-url').click(function () { + $(this).select(); + }); + + // Pull request + const $repoComparePull = $('.repository.compare.pull'); + if ($repoComparePull.length > 0) { + initFilterSearchDropdown('.choose.branch .dropdown'); + // show pull request form + $repoComparePull.find('button.show-form').on('click', function(e) { + e.preventDefault(); + $repoComparePull.find('.pullrequest-form').show(); + $(this).parent().hide(); + }); + } + + // Branches + if ($('.repository.settings.branches').length > 0) { + initFilterSearchDropdown('.protected-branches .dropdown'); + $('.enable-protection, .enable-whitelist').change(function () { + if (this.checked) { + $($(this).data('target')).removeClass('disabled'); + } else { + $($(this).data('target')).addClass('disabled'); + } + }); + } +} + +function initMigration() { + const toggleMigrations = function() { + const authUserName = $('#auth_username').val(); + const cloneAddr = $('#clone_addr').val(); + if (!$('#mirror').is(":checked") && (authUserName!=undefined && authUserName.length > 0) + && (cloneAddr!=undefined && (cloneAddr.startsWith("https://github.com") || cloneAddr.startsWith("http://github.com")))) { + $('#migrate_items').show(); + } else { + $('#migrate_items').hide(); + } + } + + toggleMigrations(); + + $('#clone_addr').on('input', toggleMigrations) + $('#auth_username').on('input', toggleMigrations) + $('#mirror').on('change', toggleMigrations) +} + +function initPullRequestReview() { + $('.show-outdated').on('click', function (e) { + e.preventDefault(); + const id = $(this).data('comment'); + $(this).addClass("hide"); + $("#code-comments-" + id).removeClass('hide'); + $("#code-preview-" + id).removeClass('hide'); + $("#hide-outdated-" + id).removeClass('hide'); + }); + + $('.hide-outdated').on('click', function (e) { + e.preventDefault(); + const id = $(this).data('comment'); + $(this).addClass("hide"); + $("#code-comments-" + id).addClass('hide'); + $("#code-preview-" + id).addClass('hide'); + $("#show-outdated-" + id).removeClass('hide'); + }); + + $('button.comment-form-reply').on('click', function (e) { + e.preventDefault(); + $(this).hide(); + const form = $(this).parent().find('.comment-form') + form.removeClass('hide'); + assingMenuAttributes(form.find('.menu')); + }); + // The following part is only for diff views + if ($('.repository.pull.diff').length == 0) { + return; + } + + $('.diff-detail-box.ui.sticky').sticky(); + + $('.btn-review').on('click', function(e) { + e.preventDefault(); + $(this).closest('.dropdown').find('.menu').toggle('visible'); + }).closest('.dropdown').find('.link.close').on('click', function(e) { + e.preventDefault(); + $(this).closest('.menu').toggle('visible'); + }); + + $('.code-view .lines-code,.code-view .lines-num') + .on('mouseenter', function() { + const parent = $(this).closest('td'); + $(this).closest('tr').addClass( + parent.hasClass('lines-num-old') || parent.hasClass('lines-code-old') + ? 'focus-lines-old' : 'focus-lines-new' + ); + }) + .on('mouseleave', function() { + $(this).closest('tr').removeClass('focus-lines-new focus-lines-old'); + }); + $('.add-code-comment').on('click', function(e) { + // https://github.com/go-gitea/gitea/issues/4745 + if ($(e.target).hasClass('btn-add-single')) { + return; + } + e.preventDefault(); + const isSplit = $(this).closest('.code-diff').hasClass('code-diff-split'); + const side = $(this).data('side'); + const idx = $(this).data('idx'); + const path = $(this).data('path'); + const form = $('#pull_review_add_comment').html(); + const tr = $(this).closest('tr'); + let ntr = tr.next(); + if (!ntr.hasClass('add-comment')) { + ntr = $('<tr class="add-comment">' + + (isSplit ? '<td class="lines-num"></td><td class="lines-type-marker"></td><td class="add-comment-left"></td><td class="lines-num"></td><td class="lines-type-marker"></td><td class="add-comment-right"></td>' + : '<td class="lines-num"></td><td class="lines-num"></td><td class="lines-type-marker"></td><td class="add-comment-left add-comment-right"></td>') + + '</tr>'); + tr.after(ntr); + } + const td = ntr.find('.add-comment-' + side); + let commentCloud = td.find('.comment-code-cloud'); + if (commentCloud.length === 0) { + td.html(form); + commentCloud = td.find('.comment-code-cloud'); + assingMenuAttributes(commentCloud.find('.menu')); + + td.find("input[name='line']").val(idx); + td.find("input[name='side']").val(side === "left" ? "previous":"proposed"); + td.find("input[name='path']").val(path); + } + commentCloud.find('textarea').focus(); + }); +} + +function assingMenuAttributes(menu) { + const id = Math.floor(Math.random() * Math.floor(1000000)); + menu.attr('data-write', menu.attr('data-write') + id); + menu.attr('data-preview', menu.attr('data-preview') + id); + menu.find('.item').each(function() { + const tab = $(this).attr('data-tab') + id; + $(this).attr('data-tab', tab); + }); + menu.parent().find("*[data-tab='write']").attr('data-tab', 'write' + id); + menu.parent().find("*[data-tab='preview']").attr('data-tab', 'preview' + id); + initCommentPreviewTab(menu.parent(".form")); + return id; +} + +function initRepositoryCollaboration() { + // Change collaborator access mode + $('.access-mode.menu .item').click(function () { + const $menu = $(this).parent(); + $.post($menu.data('url'), { + "_csrf": csrf, + "uid": $menu.data('uid'), + "mode": $(this).data('value') + }) + }); +} + +function initTeamSettings() { + // Change team access mode + $('.organization.new.team input[name=permission]').change(function () { + const val = $('input[name=permission]:checked', '.organization.new.team').val() + if (val === 'admin') { + $('.organization.new.team .team-units').hide(); + } else { + $('.organization.new.team .team-units').show(); + } + }); +} + +function initWikiForm() { + const $editArea = $('.repository.wiki textarea#edit_area'); + if ($editArea.length > 0) { + const simplemde = new SimpleMDE({ + autoDownloadFontAwesome: false, + element: $editArea[0], + forceSync: true, + previewRender: function (plainText, preview) { // Async method + setTimeout(function () { + // FIXME: still send render request when return back to edit mode + $.post($editArea.data('url'), { + "_csrf": csrf, + "mode": "gfm", + "context": $editArea.data('context'), + "text": plainText + }, + function (data) { + preview.innerHTML = '<div class="markdown ui segment">' + data + '</div>'; + emojify.run($('.editor-preview')[0]); + } + ); + }, 0); + + return "Loading..."; + }, + renderingConfig: { + singleLineBreaks: false + }, + indentWithTabs: false, + tabSize: 4, + spellChecker: false, + toolbar: ["bold", "italic", "strikethrough", "|", + "heading-1", "heading-2", "heading-3", "heading-bigger", "heading-smaller", "|", + { + name: "code-inline", + action: function(e){ + const cm = e.codemirror; + const selection = cm.getSelection(); + cm.replaceSelection("`" + selection + "`"); + if (!selection) { + const cursorPos = cm.getCursor(); + cm.setCursor(cursorPos.line, cursorPos.ch - 1); + } + cm.focus(); + }, + className: "fa fa-angle-right", + title: "Add Inline Code", + },"code", "quote", "|", { + name: "checkbox-empty", + action: function(e){ + const cm = e.codemirror; + cm.replaceSelection("\n- [ ] " + cm.getSelection()); + cm.focus(); + }, + className: "fa fa-square-o", + title: "Add Checkbox (empty)", + }, + { + name: "checkbox-checked", + action: function(e){ + const cm = e.codemirror; + cm.replaceSelection("\n- [x] " + cm.getSelection()); + cm.focus(); + }, + className: "fa fa-check-square-o", + title: "Add Checkbox (checked)", + }, "|", + "unordered-list", "ordered-list", "|", + "link", "image", "table", "horizontal-rule", "|", + "clean-block", "preview", "fullscreen"] + }) + $(simplemde.codemirror.getInputField()).addClass("js-quick-submit"); + } +} + +// For IE +String.prototype.endsWith = function (pattern) { + const d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; +}; + +// Adding function to get the cursor position in a text field to jQuery object. +$.fn.getCursorPosition = function () { + const el = $(this).get(0); + let pos = 0; + if ('selectionStart' in el) { + pos = el.selectionStart; + } else if ('selection' in document) { + el.focus(); + const Sel = document.selection.createRange(); + const SelLength = document.selection.createRange().text.length; + Sel.moveStart('character', -el.value.length); + pos = Sel.text.length - SelLength; + } + return pos; +} + +function setSimpleMDE($editArea) { + if (codeMirrorEditor) { + codeMirrorEditor.toTextArea(); + codeMirrorEditor = null; + } + + if (simpleMDEditor) { + return true; + } + + simpleMDEditor = new SimpleMDE({ + autoDownloadFontAwesome: false, + element: $editArea[0], + forceSync: true, + renderingConfig: { + singleLineBreaks: false + }, + indentWithTabs: false, + tabSize: 4, + spellChecker: false, + previewRender: function (plainText, preview) { // Async method + setTimeout(function () { + // FIXME: still send render request when return back to edit mode + $.post($editArea.data('url'), { + "_csrf": csrf, + "mode": "gfm", + "context": $editArea.data('context'), + "text": plainText + }, + function (data) { + preview.innerHTML = '<div class="markdown ui segment">' + data + '</div>'; + emojify.run($('.editor-preview')[0]); + } + ); + }, 0); + + return "Loading..."; + }, + toolbar: ["bold", "italic", "strikethrough", "|", + "heading-1", "heading-2", "heading-3", "heading-bigger", "heading-smaller", "|", + "code", "quote", "|", + "unordered-list", "ordered-list", "|", + "link", "image", "table", "horizontal-rule", "|", + "clean-block", "preview", "fullscreen", "side-by-side"] + }); + + return true; +} + +function setCodeMirror($editArea) { + if (simpleMDEditor) { + simpleMDEditor.toTextArea(); + simpleMDEditor = null; + } + + if (codeMirrorEditor) { + return true; + } + + codeMirrorEditor = CodeMirror.fromTextArea($editArea[0], { + lineNumbers: true + }); + codeMirrorEditor.on("change", function (cm, _change) { + $editArea.val(cm.getValue()); + }); + + return true; +} + +function initEditor() { + $('.js-quick-pull-choice-option').change(function () { + if ($(this).val() == 'commit-to-new-branch') { + $('.quick-pull-branch-name').show(); + $('.quick-pull-branch-name input').prop('required',true); + } else { + $('.quick-pull-branch-name').hide(); + $('.quick-pull-branch-name input').prop('required',false); + } + $('#commit-button').text($(this).attr('button_text')); + }); + + const $editFilename = $("#file-name"); + $editFilename.keyup(function (e) { + const $section = $('.breadcrumb span.section'); + const $divider = $('.breadcrumb div.divider'); + let value; + let parts; + + if (e.keyCode == 8) { + if ($(this).getCursorPosition() == 0) { + if ($section.length > 0) { + value = $section.last().find('a').text(); + $(this).val(value + $(this).val()); + $(this)[0].setSelectionRange(value.length, value.length); + $section.last().remove(); + $divider.last().remove(); + } + } + } + if (e.keyCode == 191) { + parts = $(this).val().split('/'); + for (let i = 0; i < parts.length; ++i) { + value = parts[i]; + if (i < parts.length - 1) { + if (value.length) { + $('<span class="section"><a href="#">' + value + '</a></span>').insertBefore($(this)); + $('<div class="divider"> / </div>').insertBefore($(this)); + } + } + else { + $(this).val(value); + } + $(this)[0].setSelectionRange(0, 0); + } + } + parts = []; + $('.breadcrumb span.section').each(function () { + const element = $(this); + if (element.find('a').length) { + parts.push(element.find('a').text()); + } else { + parts.push(element.text()); + } + }); + if ($(this).val()) + parts.push($(this).val()); + $('#tree_path').val(parts.join('/')); + }).trigger('keyup'); + + const $editArea = $('.repository.editor textarea#edit_area'); + if (!$editArea.length) + return; + + const markdownFileExts = $editArea.data("markdown-file-exts").split(","); + const lineWrapExtensions = $editArea.data("line-wrap-extensions").split(","); + + $editFilename.on("keyup", function () { + const val = $editFilename.val(); + let mode, spec, extension, extWithDot, dataUrl, apiCall; + + extension = extWithDot = ""; + const m = /.+\.([^.]+)$/.exec(val); + if (m) { + extension = m[1]; + extWithDot = "." + extension; + } + + const info = CodeMirror.findModeByExtension(extension); + const previewLink = $('a[data-tab=preview]'); + if (info) { + mode = info.mode; + spec = info.mime; + apiCall = mode; + } + else { + apiCall = extension + } + + if (previewLink.length && apiCall && previewFileModes && previewFileModes.length && previewFileModes.indexOf(apiCall) >= 0) { + dataUrl = previewLink.data('url'); + previewLink.data('url', dataUrl.replace(/(.*)\/.*/i, '$1/' + mode)); + previewLink.show(); + } + else { + previewLink.hide(); + } + + // If this file is a Markdown extensions, we will load that editor and return + if (markdownFileExts.indexOf(extWithDot) >= 0) { + if (setSimpleMDE($editArea)) { + return; + } + } + + // Else we are going to use CodeMirror + if (!codeMirrorEditor && !setCodeMirror($editArea)) { + return; + } + + if (mode) { + codeMirrorEditor.setOption("mode", spec); + CodeMirror.autoLoadMode(codeMirrorEditor, mode); + } + + if (lineWrapExtensions.indexOf(extWithDot) >= 0) { + codeMirrorEditor.setOption("lineWrapping", true); + } + else { + codeMirrorEditor.setOption("lineWrapping", false); + } + + // get the filename without any folder + let value = $editFilename.val(); + if (value.length === 0) { + return; + } + value = value.split('/'); + value = value[value.length - 1]; + + $.getJSON($editFilename.data('ec-url-prefix')+value, function(editorconfig) { + if (editorconfig.indent_style === 'tab') { + codeMirrorEditor.setOption("indentWithTabs", true); + codeMirrorEditor.setOption('extraKeys', {}); + } else { + codeMirrorEditor.setOption("indentWithTabs", false); + // required because CodeMirror doesn't seems to use spaces correctly for {"indentWithTabs": false}: + // - https://github.com/codemirror/CodeMirror/issues/988 + // - https://codemirror.net/doc/manual.html#keymaps + codeMirrorEditor.setOption('extraKeys', { + Tab: function(cm) { + const spaces = Array(parseInt(cm.getOption("indentUnit")) + 1).join(" "); + cm.replaceSelection(spaces); + } + }); + } + codeMirrorEditor.setOption("indentUnit", editorconfig.indent_size || 4); + codeMirrorEditor.setOption("tabSize", editorconfig.tab_width || 4); + }); + }).trigger('keyup'); + + // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage + // to enable or disable the commit button + const $commitButton = $('#commit-button'); + const $editForm = $('.ui.edit.form'); + const dirtyFileClass = 'dirty-file'; + + // Disabling the button at the start + $commitButton.prop('disabled', true); + + // Registering a custom listener for the file path and the file content + $editForm.areYouSure({ + silent: true, + dirtyClass: dirtyFileClass, + fieldSelector: ':input:not(.commit-form-wrapper :input)', + change: function () { + const dirty = $(this).hasClass(dirtyFileClass); + $commitButton.prop('disabled', !dirty); + } + }); + + $commitButton.click(function (event) { + // A modal which asks if an empty file should be committed + if ($editArea.val().length === 0) { + $('#edit-empty-content-modal').modal({ + onApprove: function () { + $('.edit.form').submit(); + } + }).modal('show'); + event.preventDefault(); + } + }); +} + +function initOrganization() { + if ($('.organization').length == 0) { + return; + } + + // Options + if ($('.organization.settings.options').length > 0) { + $('#org_name').keyup(function () { + const $prompt = $('#org-name-change-prompt'); + if ($(this).val().toString().toLowerCase() != $(this).data('org-name').toString().toLowerCase()) { + $prompt.show(); + } else { + $prompt.hide(); + } + }); + } +} + +function initUserSettings() { + // Options + if ($('.user.settings.profile').length > 0) { + $('#username').keyup(function () { + const $prompt = $('#name-change-prompt'); + if ($(this).val().toString().toLowerCase() != $(this).data('name').toString().toLowerCase()) { + $prompt.show(); + } else { + $prompt.hide(); + } + }); + } +} + +function initWebhook() { + if ($('.new.webhook').length == 0) { + return; + } + + $('.events.checkbox input').change(function () { + if ($(this).is(':checked')) { + $('.events.fields').show(); + } + }); + $('.non-events.checkbox input').change(function () { + if ($(this).is(':checked')) { + $('.events.fields').hide(); + } + }); + + const updateContentType = function () { + const visible = $('#http_method').val() === 'POST'; + $('#content_type').parent().parent()[visible ? 'show' : 'hide'](); + }; + updateContentType(); + $('#http_method').change(function () { + updateContentType(); + }); + + // Test delivery + $('#test-delivery').click(function () { + const $this = $(this); + $this.addClass('loading disabled'); + $.post($this.data('link'), { + "_csrf": csrf + }).done( + setTimeout(function () { + window.location.href = $this.data('redirect'); + }, 5000) + ) + }); +} + +function initAdmin() { + if ($('.admin').length == 0) { + return; + } + + // New user + if ($('.admin.new.user').length > 0 || + $('.admin.edit.user').length > 0) { + $('#login_type').change(function () { + if ($(this).val().substring(0, 1) == '0') { + $('#login_name').removeAttr('required'); + $('.non-local').hide(); + $('.local').show(); + $('#user_name').focus(); + + if ($(this).data('password') == "required") { + $('#password').attr('required', 'required'); + } + + } else { + $('#login_name').attr('required', 'required'); + $('.non-local').show(); + $('.local').hide(); + $('#login_name').focus(); + + $('#password').removeAttr('required'); + } + }); + } + + function onSecurityProtocolChange() { + if ($('#security_protocol').val() > 0) { + $('.has-tls').show(); + } else { + $('.has-tls').hide(); + } + } + + function onUsePagedSearchChange() { + if ($('#use_paged_search').prop('checked')) { + $('.search-page-size').show() + .find('input').attr('required', 'required'); + } else { + $('.search-page-size').hide() + .find('input').removeAttr('required'); + } + } + + function onOAuth2Change() { + $('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url').hide(); + $('.open_id_connect_auto_discovery_url input[required]').removeAttr('required'); + + const provider = $('#oauth2_provider').val(); + switch (provider) { + case 'github': + case 'gitlab': + case 'gitea': + $('.oauth2_use_custom_url').show(); + break; + case 'openidConnect': + $('.open_id_connect_auto_discovery_url input').attr('required', 'required'); + $('.open_id_connect_auto_discovery_url').show(); + break; + } + onOAuth2UseCustomURLChange(); + } + + function onOAuth2UseCustomURLChange() { + const provider = $('#oauth2_provider').val(); + $('.oauth2_use_custom_url_field').hide(); + $('.oauth2_use_custom_url_field input[required]').removeAttr('required'); + + if ($('#oauth2_use_custom_url').is(':checked')) { + if (!$('#oauth2_token_url').val()) { + $('#oauth2_token_url').val($('#' + provider + '_token_url').val()); + } + if (!$('#oauth2_auth_url').val()) { + $('#oauth2_auth_url').val($('#' + provider + '_auth_url').val()); + } + if (!$('#oauth2_profile_url').val()) { + $('#oauth2_profile_url').val($('#' + provider + '_profile_url').val()); + } + if (!$('#oauth2_email_url').val()) { + $('#oauth2_email_url').val($('#' + provider + '_email_url').val()); + } + switch (provider) { + case 'github': + $('.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input, .oauth2_email_url input').attr('required', 'required'); + $('.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url, .oauth2_email_url').show(); + break; + case 'gitea': + case 'gitlab': + $('.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input').attr('required', 'required'); + $('.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url').show(); + $('#oauth2_email_url').val(''); + break; + } + } + } + + // New authentication + if ($('.admin.new.authentication').length > 0) { + $('#auth_type').change(function () { + $('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls .search-page-size').hide(); + + $('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required]').removeAttr('required'); + $('.binddnrequired').removeClass("required"); + + const authType = $(this).val(); + switch (authType) { + case '2': // LDAP + $('.ldap').show(); + $('.binddnrequired input, .ldap div.required:not(.dldap) input').attr('required', 'required'); + $('.binddnrequired').addClass("required"); + break; + case '3': // SMTP + $('.smtp').show(); + $('.has-tls').show(); + $('.smtp div.required input, .has-tls').attr('required', 'required'); + break; + case '4': // PAM + $('.pam').show(); + $('.pam input').attr('required', 'required'); + break; + case '5': // LDAP + $('.dldap').show(); + $('.dldap div.required:not(.ldap) input').attr('required', 'required'); + break; + case '6': // OAuth2 + $('.oauth2').show(); + $('.oauth2 div.required:not(.oauth2_use_custom_url,.oauth2_use_custom_url_field,.open_id_connect_auto_discovery_url) input').attr('required', 'required'); + onOAuth2Change(); + break; + } + if (authType == '2' || authType == '5') { + onSecurityProtocolChange() + } + if (authType == '2') { + onUsePagedSearchChange(); + } + }); + $('#auth_type').change(); + $('#security_protocol').change(onSecurityProtocolChange); + $('#use_paged_search').change(onUsePagedSearchChange); + $('#oauth2_provider').change(onOAuth2Change); + $('#oauth2_use_custom_url').change(onOAuth2UseCustomURLChange); + } + // Edit authentication + if ($('.admin.edit.authentication').length > 0) { + const authType = $('#auth_type').val(); + if (authType == '2' || authType == '5') { + $('#security_protocol').change(onSecurityProtocolChange); + if (authType == '2') { + $('#use_paged_search').change(onUsePagedSearchChange); + } + } else if (authType == '6') { + $('#oauth2_provider').change(onOAuth2Change); + $('#oauth2_use_custom_url').change(onOAuth2UseCustomURLChange); + onOAuth2Change(); + } + } + + // Notice + if ($('.admin.notice')) { + const $detailModal = $('#detail-modal'); + + // Attach view detail modals + $('.view-detail').click(function () { + $detailModal.find('.content p').text($(this).data('content')); + $detailModal.modal('show'); + return false; + }); + + // Select actions + const $checkboxes = $('.select.table .ui.checkbox'); + $('.select.action').click(function () { + switch ($(this).data('action')) { + case 'select-all': + $checkboxes.checkbox('check'); + break; + case 'deselect-all': + $checkboxes.checkbox('uncheck'); + break; + case 'inverse': + $checkboxes.checkbox('toggle'); + break; + } + }); + $('#delete-selection').click(function () { + const $this = $(this); + $this.addClass("loading disabled"); + const ids = []; + $checkboxes.each(function () { + if ($(this).checkbox('is checked')) { + ids.push($(this).data('id')); + } + }); + $.post($this.data('link'), { + "_csrf": csrf, + "ids": ids + }).done(function () { + window.location.href = $this.data('redirect'); + }); + }); + } +} + +function buttonsClickOnEnter() { + $('.ui.button').keypress(function (e) { + if (e.keyCode == 13 || e.keyCode == 32) // enter key or space bar + $(this).click(); + }); +} + +function searchUsers() { + const $searchUserBox = $('#search-user-box'); + $searchUserBox.search({ + minCharacters: 2, + apiSettings: { + url: suburl + '/api/v1/users/search?q={query}', + onResponse: function(response) { + const items = []; + $.each(response.data, function (_i, item) { + let title = item.login; + if (item.full_name && item.full_name.length > 0) { + title += ' (' + htmlEncode(item.full_name) + ')'; + } + items.push({ + title: title, + image: item.avatar_url + }) + }); + + return { results: items } + } + }, + searchFields: ['login', 'full_name'], + showNoResults: false + }); +} + +function searchTeams() { + const $searchTeamBox = $('#search-team-box'); + $searchTeamBox.search({ + minCharacters: 2, + apiSettings: { + url: suburl + '/api/v1/orgs/' + $searchTeamBox.data('org') + '/teams/search?q={query}', + headers: {"X-Csrf-Token": csrf}, + onResponse: function(response) { + const items = []; + $.each(response.data, function (_i, item) { + const title = item.name + ' (' + item.permission + ' access)'; + items.push({ + title: title, + }) + }); + + return { results: items } + } + }, + searchFields: ['name', 'description'], + showNoResults: false + }); +} + +function searchRepositories() { + const $searchRepoBox = $('#search-repo-box'); + $searchRepoBox.search({ + minCharacters: 2, + apiSettings: { + url: suburl + '/api/v1/repos/search?q={query}&uid=' + $searchRepoBox.data('uid'), + onResponse: function(response) { + const items = []; + $.each(response.data, function (_i, item) { + items.push({ + title: item.full_name.split("/")[1], + description: item.full_name + }) + }); + + return { results: items } + } + }, + searchFields: ['full_name'], + showNoResults: false + }); +} + +function initCodeView() { + if ($('.code-view .linenums').length > 0) { + $(document).on('click', '.lines-num span', function (e) { + const $select = $(this); + const $list = $select.parent().siblings('.lines-code').find('ol.linenums > li'); + selectRange($list, $list.filter('[rel=' + $select.attr('id') + ']'), (e.shiftKey ? $list.filter('.active').eq(0) : null)); + deSelect(); + }); + + $(window).on('hashchange', function () { + let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/); + const $list = $('.code-view ol.linenums > li'); + let $first; + if (m) { + $first = $list.filter('.' + m[1]); + selectRange($list, $first, $list.filter('.' + m[2])); + $("html, body").scrollTop($first.offset().top - 200); + return; + } + m = window.location.hash.match(/^#(L|n)(\d+)$/); + if (m) { + $first = $list.filter('.L' + m[2]); + selectRange($list, $first); + $("html, body").scrollTop($first.offset().top - 200); + } + }).trigger('hashchange'); + } +} + +function initU2FAuth() { + if($('#wait-for-key').length === 0) { + return + } + u2fApi.ensureSupport() + .then(function () { + $.getJSON(suburl + '/user/u2f/challenge').success(function(req) { + u2fApi.sign(req.appId, req.challenge, req.registeredKeys, 30) + .then(u2fSigned) + .catch(function (err) { + if(err === undefined) { + u2fError(1); + return + } + u2fError(err.metaData.code); + }); + }); + }).catch(function () { + // Fallback in case browser do not support U2F + window.location.href = suburl + "/user/two_factor" + }) +} +function u2fSigned(resp) { + $.ajax({ + url: suburl + '/user/u2f/sign', + type: "POST", + headers: {"X-Csrf-Token": csrf}, + data: JSON.stringify(resp), + contentType: "application/json; charset=utf-8", + }).done(function(res){ + window.location.replace(res); + }).fail(function () { + u2fError(1); + }); +} + +function u2fRegistered(resp) { + if (checkError(resp)) { + return; + } + $.ajax({ + url: suburl + '/user/settings/security/u2f/register', + type: "POST", + headers: {"X-Csrf-Token": csrf}, + data: JSON.stringify(resp), + contentType: "application/json; charset=utf-8", + success: function(){ + reload(); + }, + fail: function () { + u2fError(1); + } + }); +} + +function checkError(resp) { + if (!('errorCode' in resp)) { + return false; + } + if (resp.errorCode === 0) { + return false; + } + u2fError(resp.errorCode); + return true; +} + + +function u2fError(errorType) { + const u2fErrors = { + 'browser': $('#unsupported-browser'), + 1: $('#u2f-error-1'), + 2: $('#u2f-error-2'), + 3: $('#u2f-error-3'), + 4: $('#u2f-error-4'), + 5: $('.u2f-error-5') + }; + u2fErrors[errorType].removeClass('hide'); + for(const type in u2fErrors){ + if(type != errorType){ + u2fErrors[type].addClass('hide'); + } + } + $('#u2f-error').modal('show'); +} + +function initU2FRegister() { + $('#register-device').modal({allowMultiple: false}); + $('#u2f-error').modal({allowMultiple: false}); + $('#register-security-key').on('click', function(e) { + e.preventDefault(); + u2fApi.ensureSupport() + .then(u2fRegisterRequest) + .catch(function() { + u2fError('browser'); + }) + }) +} + +function u2fRegisterRequest() { + $.post(suburl + "/user/settings/security/u2f/request_register", { + "_csrf": csrf, + "name": $('#nickname').val() + }).success(function(req) { + $("#nickname").closest("div.field").removeClass("error"); + $('#register-device').modal('show'); + if(req.registeredKeys === null) { + req.registeredKeys = [] + } + u2fApi.register(req.appId, req.registerRequests, req.registeredKeys, 30) + .then(u2fRegistered) + .catch(function (reason) { + if(reason === undefined) { + u2fError(1); + return + } + u2fError(reason.metaData.code); + }); + }).fail(function(xhr) { + if(xhr.status === 409) { + $("#nickname").closest("div.field").addClass("error"); + } + }); +} + +function initWipTitle() { + $(".title_wip_desc > a").click(function (e) { + e.preventDefault(); + + const $issueTitle = $("#issue_title"); + $issueTitle.focus(); + const value = $issueTitle.val().trim().toUpperCase(); + + for (const i in wipPrefixes) { + if (value.startsWith(wipPrefixes[i].toUpperCase())) { + return; + } + } + + $issueTitle.val(wipPrefixes[0] + " " + $issueTitle.val()); + }); +} + +function initTemplateSearch() { + const $repoTemplate = $("#repo_template"); + const checkTemplate = function() { + const $templateUnits = $("#template_units"); + const $nonTemplate = $("#non_template"); + if ($repoTemplate.val() !== "") { + $templateUnits.show(); + $nonTemplate.hide(); + } else { + $templateUnits.hide(); + $nonTemplate.show(); + } + }; + $repoTemplate.change(checkTemplate); + checkTemplate(); + + const changeOwner = function() { + $("#repo_template_search") + .dropdown({ + apiSettings: { + url: suburl + '/api/v1/repos/search?q={query}&template=true&priority_owner_id=' + $("#uid").val(), + onResponse: function(response) { + const filteredResponse = {'success': true, 'results': []}; + filteredResponse.results.push({ + 'name': '', + 'value': '' + }); + // Parse the response from the api to work with our dropdown + $.each(response.data, function(_r, repo) { + filteredResponse.results.push({ + 'name' : htmlEncode(repo.full_name) , + 'value' : repo.id + }); + }); + return filteredResponse; + }, + cache: false, + }, + + fullTextSearch: true + }); + }; + $("#uid").change(changeOwner); + changeOwner(); +} + +$(document).ready(function () { + csrf = $('meta[name=_csrf]').attr("content"); + suburl = $('meta[name=_suburl]').attr("content"); + + // Show exact time + $('.time-since').each(function () { + $(this).addClass('poping up').attr('data-content', $(this).attr('title')).attr('data-variation', 'inverted tiny').attr('title', ''); + }); + + // Semantic UI modules. + $('.dropdown:not(.custom)').dropdown(); + $('.jump.dropdown').dropdown({ + action: 'hide', + onShow: function () { + $('.poping.up').popup('hide'); + } + }); + $('.slide.up.dropdown').dropdown({ + transition: 'slide up' + }); + $('.upward.dropdown').dropdown({ + direction: 'upward' + }); + $('.ui.accordion').accordion(); + $('.ui.checkbox').checkbox(); + $('.ui.progress').progress({ + showActivity: false + }); + $('.poping.up').popup(); + $('.top.menu .poping.up').popup({ + onShow: function () { + if ($('.top.menu .menu.transition').hasClass('visible')) { + return false; + } + } + }); + $('.tabular.menu .item').tab(); + $('.tabable.menu .item').tab(); + + $('.toggle.button').click(function () { + $($(this).data('target')).slideToggle(100); + }); + + // make table <tr> element clickable like a link + $('tr[data-href]').click(function() { + window.location = $(this).data('href'); + }); + + // Highlight JS + if (typeof hljs != 'undefined') { + const nodes = [].slice.call(document.querySelectorAll('pre code') || []); + for (let i = 0; i < nodes.length; i++) { + hljs.highlightBlock(nodes[i]); + } + } + + // Dropzone + const $dropzone = $('#dropzone'); + if ($dropzone.length > 0) { + const filenameDict = {}; + + new Dropzone("#dropzone", { + url: $dropzone.data('upload-url'), + headers: {"X-Csrf-Token": csrf}, + maxFiles: $dropzone.data('max-file'), + maxFilesize: $dropzone.data('max-size'), + acceptedFiles: ($dropzone.data('accepts') === '*/*') ? null : $dropzone.data('accepts'), + addRemoveLinks: true, + dictDefaultMessage: $dropzone.data('default-message'), + dictInvalidFileType: $dropzone.data('invalid-input-type'), + dictFileTooBig: $dropzone.data('file-too-big'), + dictRemoveFile: $dropzone.data('remove-file'), + init: function () { + this.on("success", function (file, data) { + filenameDict[file.name] = data.uuid; + const input = $('<input id="' + data.uuid + '" name="files" type="hidden">').val(data.uuid); + $('.files').append(input); + }); + this.on("removedfile", function (file) { + if (file.name in filenameDict) { + $('#' + filenameDict[file.name]).remove(); + } + if ($dropzone.data('remove-url') && $dropzone.data('csrf')) { + $.post($dropzone.data('remove-url'), { + file: filenameDict[file.name], + _csrf: $dropzone.data('csrf') + }); + } + }) + }, + }); + } + + // Emojify + emojify.setConfig({ + img_dir: suburl + '/vendor/plugins/emojify/images', + ignore_emoticons: true + }); + const hasEmoji = document.getElementsByClassName('has-emoji'); + for (let i = 0; i < hasEmoji.length; i++) { + emojify.run(hasEmoji[i]); + for (let j = 0; j < hasEmoji[i].childNodes.length; j++) { + if (hasEmoji[i].childNodes[j].nodeName === "A") { + emojify.run(hasEmoji[i].childNodes[j]) + } + } + } + + // Clipboard JS + const clipboard = new Clipboard('.clipboard'); + clipboard.on('success', function (e) { + e.clearSelection(); + + $('#' + e.trigger.getAttribute('id')).popup('destroy'); + e.trigger.setAttribute('data-content', e.trigger.getAttribute('data-success')) + $('#' + e.trigger.getAttribute('id')).popup('show'); + e.trigger.setAttribute('data-content', e.trigger.getAttribute('data-original')) + }); + + clipboard.on('error', function (e) { + $('#' + e.trigger.getAttribute('id')).popup('destroy'); + e.trigger.setAttribute('data-content', e.trigger.getAttribute('data-error')) + $('#' + e.trigger.getAttribute('id')).popup('show'); + e.trigger.setAttribute('data-content', e.trigger.getAttribute('data-original')) + }); + + // Helpers. + $('.delete-button').click(showDeletePopup); + $('.add-all-button').click(showAddAllPopup); + + $('.delete-branch-button').click(showDeletePopup); + + $('.undo-button').click(function() { + const $this = $(this); + $.post($this.data('url'), { + "_csrf": csrf, + "id": $this.data("id") + }).done(function(data) { + window.location.href = data.redirect; + }); + }); + $('.show-panel.button').click(function () { + $($(this).data('panel')).show(); + }); + $('.show-modal.button').click(function () { + $($(this).data('modal')).modal('show'); + }); + $('.delete-post.button').click(function () { + const $this = $(this); + $.post($this.data('request-url'), { + "_csrf": csrf + }).done(function () { + window.location.href = $this.data('done-url'); + }); + }); + + // Set anchor. + $('.markdown').each(function () { + const headers = {}; + $(this).find('h1, h2, h3, h4, h5, h6').each(function () { + let node = $(this); + const val = encodeURIComponent(node.text().toLowerCase().replace(/[^\u00C0-\u1FFF\u2C00-\uD7FF\w\- ]/g, '').replace(/[ ]/g, '-')); + let name = val; + if (headers[val] > 0) { + name = val + '-' + headers[val]; + } + if (headers[val] == undefined) { + headers[val] = 1; + } else { + headers[val] += 1; + } + node = node.wrap('<div id="' + name + '" class="anchor-wrap" ></div>'); + node.append('<a class="anchor" href="#' + name + '"><span class="octicon octicon-link"></span></a>'); + }); + }); + + $('.issue-checkbox').click(function() { + const numChecked = $('.issue-checkbox').children('input:checked').length; + if (numChecked > 0) { + $('#issue-filters').addClass("hide"); + $('#issue-actions').removeClass("hide"); + } else { + $('#issue-filters').removeClass("hide"); + $('#issue-actions').addClass("hide"); + } + }); + + $('.issue-action').click(function () { + let action = this.dataset.action; + let elementId = this.dataset.elementId; + const issueIDs = $('.issue-checkbox').children('input:checked').map(function() { + return this.dataset.issueId; + }).get().join(); + const url = this.dataset.url; + if (elementId === '0' && url.substr(-9) === '/assignee'){ + elementId = ''; + action = 'clear'; + } + updateIssuesMeta(url, action, issueIDs, elementId).then(function() { + // NOTICE: This reset of checkbox state targets Firefox caching behaviour, as the checkboxes stay checked after reload + if (action === "close" || action === "open" ){ + //uncheck all checkboxes + $('.issue-checkbox input[type="checkbox"]').each(function(_,e){ e.checked = false; }); + } + reload(); + }); + }); + + // NOTICE: This event trigger targets Firefox caching behaviour, as the checkboxes stay checked after reload + // trigger ckecked event, if checkboxes are checked on load + $('.issue-checkbox input[type="checkbox"]:checked').first().each(function(_,e) { + e.checked = false; + $(e).click(); + }); + + buttonsClickOnEnter(); + searchUsers(); + searchTeams(); + searchRepositories(); + + initCommentForm(); + initInstall(); + initRepository(); + initMigration(); + initWikiForm(); + initEditForm(); + initEditor(); + initOrganization(); + initWebhook(); + initAdmin(); + initCodeView(); + initVueApp(); + initTeamSettings(); + initCtrlEnterSubmit(); + initNavbarContentToggle(); + initTopicbar(); + initU2FAuth(); + initU2FRegister(); + initIssueList(); + initWipTitle(); + initPullRequestReview(); + initRepoStatusChecker(); + initTemplateSearch(); + + // Repo clone url. + if ($('#repo-clone-url').length > 0) { + switch (localStorage.getItem('repo-clone-protocol')) { + case 'ssh': + if ($('#repo-clone-ssh').click().length === 0) { + $('#repo-clone-https').click(); + } + break; + default: + $('#repo-clone-https').click(); + break; + } + } + + const routes = { + 'div.user.settings': initUserSettings, + 'div.repository.settings.collaboration': initRepositoryCollaboration + }; + + let selector; + for (selector in routes) { + if ($(selector).length > 0) { + routes[selector](); + break; + } + } + + const $cloneAddr = $('#clone_addr'); + $cloneAddr.change(function() { + const $repoName = $('#repo_name'); + if ($cloneAddr.val().length > 0 && $repoName.val().length === 0) { // Only modify if repo_name input is blank + $repoName.val($cloneAddr.val().match(/^(.*\/)?((.+?)(\.git)?)$/)[3]); + } + }); +}); + +function changeHash(hash) { + if (history.pushState) { + history.pushState(null, null, hash); + } + else { + location.hash = hash; + } +} + +function deSelect() { + if (window.getSelection) { + window.getSelection().removeAllRanges(); + } else { + document.selection.empty(); + } +} + +function selectRange($list, $select, $from) { + $list.removeClass('active'); + if ($from) { + let a = parseInt($select.attr('rel').substr(1)); + let b = parseInt($from.attr('rel').substr(1)); + let c; + if (a != b) { + if (a > b) { + c = a; + a = b; + b = c; + } + const classes = []; + for (let i = a; i <= b; i++) { + classes.push('.L' + i); + } + $list.filter(classes.join(',')).addClass('active'); + changeHash('#L' + a + '-' + 'L' + b); + return + } + } + $select.addClass('active'); + changeHash('#' + $select.attr('rel')); +} + +$(function () { + // Warn users that try to leave a page after entering data into a form. + // Except on sign-in pages, and for forms marked as 'ignore-dirty'. + if ($('.user.signin').length === 0) { + $('form:not(.ignore-dirty)').areYouSure(); + } + + // Parse SSH Key + $("#ssh-key-content").on('change paste keyup',function(){ + const arrays = $(this).val().split(" "); + const $title = $("#ssh-key-title") + if ($title.val() === "" && arrays.length === 3 && arrays[2] !== "") { + $title.val(arrays[2]); + } + }); +}); + +function showDeletePopup() { + const $this = $(this); + let filter = ""; + if ($this.attr("id")) { + filter += "#" + $this.attr("id") + } + + const dialog = $('.delete.modal' + filter); + dialog.find('.name').text($this.data('name')); + + dialog.modal({ + closable: false, + onApprove: function() { + if ($this.data('type') == "form") { + $($this.data('form')).submit(); + return; + } + + $.post($this.data('url'), { + "_csrf": csrf, + "id": $this.data("id") + }).done(function(data) { + window.location.href = data.redirect; + }); + } + }).modal('show'); + return false; +} + +function showAddAllPopup() { + const $this = $(this); + let filter = ""; + if ($this.attr("id")) { + filter += "#" + $this.attr("id") + } + + const dialog = $('.addall.modal' + filter); + dialog.find('.name').text($this.data('name')); + + dialog.modal({ + closable: false, + onApprove: function() { + if ($this.data('type') == "form") { + $($this.data('form')).submit(); + return; + } + + $.post($this.data('url'), { + "_csrf": csrf, + "id": $this.data("id") + }).done(function(data) { + window.location.href = data.redirect; + }); + } + }).modal('show'); + return false; +} + +function initVueComponents(){ + const vueDelimeters = ['${', '}']; + + Vue.component('repo-search', { + delimiters: vueDelimeters, + + props: { + searchLimit: { + type: Number, + default: 10 + }, + suburl: { + type: String, + required: true + }, + uid: { + type: Number, + required: true + }, + organizations: { + type: Array, + default: [] + }, + isOrganization: { + type: Boolean, + default: true + }, + canCreateOrganization: { + type: Boolean, + default: false + }, + organizationsTotalCount: { + type: Number, + default: 0 + }, + moreReposLink: { + type: String, + default: '' + } + }, + + data: function() { + return { + tab: 'repos', + repos: [], + reposTotalCount: 0, + reposFilter: 'all', + searchQuery: '', + isLoading: false, + repoTypes: { + 'all': { + count: 0, + searchMode: '', + }, + 'forks': { + count: 0, + searchMode: 'fork', + }, + 'mirrors': { + count: 0, + searchMode: 'mirror', + }, + 'sources': { + count: 0, + searchMode: 'source', + }, + 'collaborative': { + count: 0, + searchMode: 'collaborative', + }, + } + } + }, + + computed: { + showMoreReposLink: function() { + return this.repos.length > 0 && this.repos.length < this.repoTypes[this.reposFilter].count; + }, + searchURL: function() { + return this.suburl + '/api/v1/repos/search?sort=updated&order=desc&uid=' + this.uid + '&q=' + this.searchQuery + + '&limit=' + this.searchLimit + '&mode=' + this.repoTypes[this.reposFilter].searchMode + + (this.reposFilter !== 'all' ? '&exclusive=1' : ''); + }, + repoTypeCount: function() { + return this.repoTypes[this.reposFilter].count; + } + }, + + mounted: function() { + this.searchRepos(this.reposFilter); + + const self = this; + Vue.nextTick(function() { + self.$refs.search.focus(); + }); + }, + + methods: { + changeTab: function(t) { + this.tab = t; + }, + + changeReposFilter: function(filter) { + this.reposFilter = filter; + this.repos = []; + this.repoTypes[filter].count = 0; + this.searchRepos(filter); + }, + + showRepo: function(repo, filter) { + switch (filter) { + case 'sources': + return repo.owner.id == this.uid && !repo.mirror && !repo.fork; + case 'forks': + return repo.owner.id == this.uid && !repo.mirror && repo.fork; + case 'mirrors': + return repo.mirror; + case 'collaborative': + return repo.owner.id != this.uid && !repo.mirror; + default: + return true; + } + }, + + searchRepos: function(reposFilter) { + const self = this; + + this.isLoading = true; + + const searchedMode = this.repoTypes[reposFilter].searchMode; + const searchedURL = this.searchURL; + const searchedQuery = this.searchQuery; + + $.getJSON(searchedURL, function(result, _textStatus, request) { + if (searchedURL == self.searchURL) { + self.repos = result.data; + const count = request.getResponseHeader('X-Total-Count'); + if (searchedQuery === '' && searchedMode === '') { + self.reposTotalCount = count; + } + self.repoTypes[reposFilter].count = count; + } + }).always(function() { + if (searchedURL == self.searchURL) { + self.isLoading = false; + } + }); + }, + + repoClass: function(repo) { + if (repo.fork) { + return 'octicon octicon-repo-forked'; + } else if (repo.mirror) { + return 'octicon octicon-repo-clone'; + } else if (repo.private) { + return 'octicon octicon-lock'; + } else { + return 'octicon octicon-repo'; + } + } + } + }) +} + +function initCtrlEnterSubmit() { + $(".js-quick-submit").keydown(function(e) { + if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.keyCode == 13 || e.keyCode == 10)) { + $(this).closest("form").submit(); + } + }); +} + +function initVueApp() { + const el = document.getElementById('app'); + if (!el) { + return; + } + + initVueComponents(); + + new Vue({ + delimiters: ['${', '}'], + el: el, + + data: { + searchLimit: document.querySelector('meta[name=_search_limit]').content, + suburl: document.querySelector('meta[name=_suburl]').content, + uid: document.querySelector('meta[name=_context_uid]').content, + }, + }); +} + +window.timeAddManual = function() { + $('.mini.modal') + .modal({ + duration: 200, + onApprove: function() { + $('#add_time_manual_form').submit(); + } + }).modal('show') + ; +} + +window.toggleStopwatch = function() { + $("#toggle_stopwatch_form").submit(); +} +window.cancelStopwatch = function() { + $("#cancel_stopwatch_form").submit(); +} + +window.initHeatmap = function(appElementId, heatmapUser, locale) { + const el = document.getElementById(appElementId); + if (!el) { + return; + } + + locale = locale || {}; + + locale.contributions = locale.contributions || 'contributions'; + locale.no_contributions = locale.no_contributions || 'No contributions'; + + const vueDelimeters = ['${', '}']; + + Vue.component('activity-heatmap', { + delimiters: vueDelimeters, + + props: { + user: { + type: String, + required: true + }, + suburl: { + type: String, + required: true + }, + locale: { + type: Object, + required: true + } + }, + + data: function () { + return { + isLoading: true, + colorRange: [], + endDate: null, + values: [], + totalContributions: 0, + }; + }, + + mounted: function() { + this.colorRange = [ + this.getColor(0), + this.getColor(1), + this.getColor(2), + this.getColor(3), + this.getColor(4), + this.getColor(5) + ]; + this.endDate = new Date(); + this.loadHeatmap(this.user); + }, + + methods: { + loadHeatmap: function(userName) { + const self = this; + $.get(this.suburl + '/api/v1/users/' + userName + '/heatmap', function(chartRawData) { + const chartData = []; + for (let i = 0; i < chartRawData.length; i++) { + self.totalContributions += chartRawData[i].contributions; + chartData[i] = { date: new Date(chartRawData[i].timestamp * 1000), count: chartRawData[i].contributions }; + } + self.values = chartData; + self.isLoading = false; + }); + }, + + getColor: function(idx) { + const el = document.createElement('div'); + el.className = 'heatmap-color-' + idx; + document.body.appendChild(el); + + const color = getComputedStyle(el).backgroundColor; + + document.body.removeChild(el); + + return color; + } + }, + + template: '<div><div v-show="isLoading"><slot name="loading"></slot></div><h4 class="total-contributions" v-if="!isLoading"><span v-html="totalContributions"></span> total contributions in the last 12 months</h4><calendar-heatmap v-show="!isLoading" :locale="locale" :no-data-text="locale.no_contributions" :tooltip-unit="locale.contributions" :end-date="endDate" :values="values" :range-color="colorRange" />' + }); + + new Vue({ + delimiters: vueDelimeters, + el: el, + + data: { + suburl: document.querySelector('meta[name=_suburl]').content, + heatmapUser: heatmapUser, + locale: locale + }, + }); +} + +function initFilterBranchTagDropdown(selector) { + $(selector).each(function() { + const $dropdown = $(this); + const $data = $dropdown.find('.data'); + const data = { + items: [], + mode: $data.data('mode'), + searchTerm: '', + noResults: '', + canCreateBranch: false, + menuVisible: false, + active: 0 + }; + $data.find('.item').each(function() { + data.items.push({ + name: $(this).text(), + url: $(this).data('url'), + branch: $(this).hasClass('branch'), + tag: $(this).hasClass('tag'), + selected: $(this).hasClass('selected') + }); + }); + $data.remove(); + new Vue({ + delimiters: ['${', '}'], + el: this, + data: data, + + beforeMount: function () { + const vm = this; + + this.noResults = vm.$el.getAttribute('data-no-results'); + this.canCreateBranch = vm.$el.getAttribute('data-can-create-branch') === 'true'; + + document.body.addEventListener('click', function(event) { + if (vm.$el.contains(event.target)) { + return; + } + if (vm.menuVisible) { + Vue.set(vm, 'menuVisible', false); + } + }); + }, + + watch: { + menuVisible: function(visible) { + if (visible) { + this.focusSearchField(); + } + } + }, + + computed: { + filteredItems: function() { + const vm = this; + + const items = vm.items.filter(function (item) { + return ((vm.mode === 'branches' && item.branch) + || (vm.mode === 'tags' && item.tag)) + && (!vm.searchTerm + || item.name.toLowerCase().indexOf(vm.searchTerm.toLowerCase()) >= 0); + }); + + vm.active = (items.length === 0 && vm.showCreateNewBranch ? 0 : -1); + + return items; + }, + showNoResults: function() { + return this.filteredItems.length === 0 + && !this.showCreateNewBranch; + }, + showCreateNewBranch: function() { + const vm = this; + if (!this.canCreateBranch || !vm.searchTerm || vm.mode === 'tags') { + return false; + } + + return vm.items.filter(function (item) { + return item.name.toLowerCase() === vm.searchTerm.toLowerCase() + }).length === 0; + } + }, + + methods: { + selectItem: function(item) { + const prev = this.getSelected(); + if (prev !== null) { + prev.selected = false; + } + item.selected = true; + window.location.href = item.url; + }, + createNewBranch: function() { + if (!this.showCreateNewBranch) { + return; + } + this.$refs.newBranchForm.submit(); + }, + focusSearchField: function() { + const vm = this; + Vue.nextTick(function() { + vm.$refs.searchField.focus(); + }); + }, + getSelected: function() { + for (let i = 0, j = this.items.length; i < j; ++i) { + if (this.items[i].selected) + return this.items[i]; + } + return null; + }, + getSelectedIndexInFiltered: function() { + for (let i = 0, j = this.filteredItems.length; i < j; ++i) { + if (this.filteredItems[i].selected) + return i; + } + return -1; + }, + scrollToActive: function() { + let el = this.$refs['listItem' + this.active]; + if (!el || el.length === 0) { + return; + } + if (Array.isArray(el)) { + el = el[0]; + } + + const cont = this.$refs.scrollContainer; + + if (el.offsetTop < cont.scrollTop) { + cont.scrollTop = el.offsetTop; + } + else if (el.offsetTop + el.clientHeight > cont.scrollTop + cont.clientHeight) { + cont.scrollTop = el.offsetTop + el.clientHeight - cont.clientHeight; + } + }, + keydown: function(event) { + const vm = this; + if (event.keyCode === 40) { + // arrow down + event.preventDefault(); + + if (vm.active === -1) { + vm.active = vm.getSelectedIndexInFiltered(); + } + + if (vm.active + (vm.showCreateNewBranch ? 0 : 1) >= vm.filteredItems.length) { + return; + } + vm.active++; + vm.scrollToActive(); + } + if (event.keyCode === 38) { + // arrow up + event.preventDefault(); + + if (vm.active === -1) { + vm.active = vm.getSelectedIndexInFiltered(); + } + + if (vm.active <= 0) { + return; + } + vm.active--; + vm.scrollToActive(); + } + if (event.keyCode == 13) { + // enter + event.preventDefault(); + + if (vm.active >= vm.filteredItems.length) { + vm.createNewBranch(); + } else if (vm.active >= 0) { + vm.selectItem(vm.filteredItems[vm.active]); + } + } + if (event.keyCode == 27) { + // escape + event.preventDefault(); + vm.menuVisible = false; + } + } + } + }); + }); +} + +$(".commit-button").click(function(e) { + e.preventDefault(); + $(this).parent().find('.commit-body').toggle(); +}); + +function initNavbarContentToggle() { + const content = $('#navbar'); + const toggle = $('#navbar-expand-toggle'); + let isExpanded = false; + toggle.click(function() { + isExpanded = !isExpanded; + if (isExpanded) { + content.addClass('shown'); + toggle.addClass('active'); + } + else { + content.removeClass('shown'); + toggle.removeClass('active'); + } + }); +} + +function initTopicbar() { + const mgrBtn = $("#manage_topic"); + const editDiv = $("#topic_edit"); + const viewDiv = $("#repo-topics"); + const saveBtn = $("#save_topic"); + const topicDropdown = $('#topic_edit .dropdown'); + const topicForm = $('#topic_edit.ui.form'); + const topicPrompts = getPrompts(); + + mgrBtn.click(function() { + viewDiv.hide(); + editDiv.css('display', ''); // show Semantic UI Grid + }); + + function getPrompts() { + const hidePrompt = $("div.hide#validate_prompt"), + prompts = { + countPrompt: hidePrompt.children('#count_prompt').text(), + formatPrompt: hidePrompt.children('#format_prompt').text() + }; + hidePrompt.remove(); + return prompts; + } + + saveBtn.click(function() { + const topics = $("input[name=topics]").val(); + + $.post(saveBtn.data('link'), { + "_csrf": csrf, + "topics": topics + }, function(_data, _textStatus, xhr){ + if (xhr.responseJSON.status === 'ok') { + viewDiv.children(".topic").remove(); + if (topics.length) { + const topicArray = topics.split(","); + + const last = viewDiv.children("a").last(); + for (let i=0; i < topicArray.length; i++) { + $('<div class="ui small label topic" style="cursor:pointer;">'+topicArray[i]+'</div>').insertBefore(last) + } + } + editDiv.css('display', 'none'); + viewDiv.show(); + } + }).fail(function(xhr){ + if (xhr.status === 422) { + if (xhr.responseJSON.invalidTopics.length > 0) { + topicPrompts.formatPrompt = xhr.responseJSON.message; + + const invalidTopics = xhr.responseJSON.invalidTopics, + topicLables = topicDropdown.children('a.ui.label'); + + topics.split(',').forEach(function(value, index) { + for (let i=0; i < invalidTopics.length; i++) { + if (invalidTopics[i] === value) { + topicLables.eq(index).removeClass("green").addClass("red"); + } + } + }); + } else { + topicPrompts.countPrompt = xhr.responseJSON.message; + } + } + }).always(function() { + topicForm.form('validate form'); + }); + }); + + topicDropdown.dropdown({ + allowAdditions: true, + forceSelection: false, + fields: { name: "description", value: "data-value" }, + saveRemoteData: false, + label: { + transition : 'horizontal flip', + duration : 200, + variation : false, + blue : true, + basic: true, + }, + className: { + label: 'ui small label' + }, + apiSettings: { + url: suburl + '/api/v1/topics/search?q={query}', + throttle: 500, + cache: false, + onResponse: function(res) { + const formattedResponse = { + success: false, + results: [], + }; + const stripTags = function (text) { + return text.replace(/<[^>]*>?/gm, ""); + }; + + const query = stripTags(this.urlData.query.trim()); + let found_query = false; + const current_topics = []; + topicDropdown.find('div.label.visible.topic,a.label.visible').each(function(_,e){ current_topics.push(e.dataset.value); }); + + if (res.topics) { + let found = false; + for (let i=0;i < res.topics.length;i++) { + // skip currently added tags + if (current_topics.indexOf(res.topics[i].topic_name) != -1){ + continue; + } + + if (res.topics[i].topic_name.toLowerCase() === query.toLowerCase()){ + found_query = true; + } + formattedResponse.results.push({"description": res.topics[i].topic_name, "data-value": res.topics[i].topic_name}); + found = true; + } + formattedResponse.success = found; + } + + if (query.length > 0 && !found_query){ + formattedResponse.success = true; + formattedResponse.results.unshift({"description": query, "data-value": query}); + } else if (query.length > 0 && found_query) { + formattedResponse.results.sort(function(a, b){ + if (a.description.toLowerCase() === query.toLowerCase()) return -1; + if (b.description.toLowerCase() === query.toLowerCase()) return 1; + if (a.description > b.description) return -1; + if (a.description < b.description) return 1; + return 0; + }); + } + + + return formattedResponse; + }, + }, + onLabelCreate: function(value) { + value = value.toLowerCase().trim(); + this.attr("data-value", value).contents().first().replaceWith(value); + return $(this); + }, + onAdd: function(addedValue, _addedText, $addedChoice) { + addedValue = addedValue.toLowerCase().trim(); + $($addedChoice).attr('data-value', addedValue); + $($addedChoice).attr('data-text', addedValue); + } + }); + + $.fn.form.settings.rules.validateTopic = function(_values, regExp) { + const topics = topicDropdown.children('a.ui.label'), + status = topics.length === 0 || topics.last().attr("data-value").match(regExp); + if (!status) { + topics.last().removeClass("green").addClass("red"); + } + return status && topicDropdown.children('a.ui.label.red').length === 0; + }; + + topicForm.form({ + on: 'change', + inline : true, + fields: { + topics: { + identifier: 'topics', + rules: [ + { + type: 'validateTopic', + value: /^[a-z0-9][a-z0-9-]{1,35}$/, + prompt: topicPrompts.formatPrompt + }, + { + type: 'maxCount[25]', + prompt: topicPrompts.countPrompt + } + ] + }, + } + }); +} +window.toggleDeadlineForm = function() { + $('#deadlineForm').fadeToggle(150); +} + +window.setDeadline = function() { + const deadline = $('#deadlineDate').val(); + updateDeadline(deadline); +} + +function updateDeadline(deadlineString) { + $('#deadline-err-invalid-date').hide(); + $('#deadline-loader').addClass('loading'); + + let realDeadline = null; + if (deadlineString !== '') { + + const newDate = Date.parse(deadlineString) + + if (isNaN(newDate)) { + $('#deadline-loader').removeClass('loading'); + $('#deadline-err-invalid-date').show(); + return false; + } + realDeadline = new Date(newDate); + } + + $.ajax($('#update-issue-deadline-form').attr('action') + '/deadline', { + data: JSON.stringify({ + 'due_date': realDeadline, + }), + headers: { + 'X-Csrf-Token': csrf, + 'X-Remote': true, + }, + contentType: 'application/json', + type: 'POST', + success: function () { + reload(); + }, + error: function () { + $('#deadline-loader').removeClass('loading'); + $('#deadline-err-invalid-date').show(); + } + }); +} + +window.deleteDependencyModal = function(id, type) { + $('.remove-dependency') + .modal({ + closable: false, + duration: 200, + onApprove: function () { + $('#removeDependencyID').val(id); + $('#dependencyType').val(type); + $('#removeDependencyForm').submit(); + } + }).modal('show') + ; +} + +function initIssueList() { + const repolink = $('#repolink').val(); + const repoId = $('#repoId').val(); + const crossRepoSearch = $('#crossRepoSearch').val(); + let issueSearchUrl = suburl + '/api/v1/repos/' + repolink + '/issues?q={query}'; + if (crossRepoSearch === 'true') { + issueSearchUrl = suburl + '/api/v1/repos/issues/search?q={query}&priority_repo_id=' + repoId; + } + $('#new-dependency-drop-list') + .dropdown({ + apiSettings: { + url: issueSearchUrl, + onResponse: function(response) { + const filteredResponse = {'success': true, 'results': []}; + const currIssueId = $('#new-dependency-drop-list').data('issue-id'); + // Parse the response from the api to work with our dropdown + $.each(response, function(_i, issue) { + // Don't list current issue in the dependency list. + if(issue.id === currIssueId) { + return; + } + filteredResponse.results.push({ + 'name' : '#' + issue.number + ' ' + htmlEncode(issue.title) + + '<div class="text small dont-break-out">' + htmlEncode(issue.repository.full_name) + '</div>', + 'value' : issue.id + }); + }); + return filteredResponse; + }, + cache: false, + }, + + fullTextSearch: true + }); + + $(".menu a.label-filter-item").each(function() { + $(this).click(function(e) { + if (e.altKey) { + e.preventDefault(); + + const href = $(this).attr("href"); + const id = $(this).data("label-id"); + + const regStr = "labels=(-?[0-9]+%2c)*(" + id + ")(%2c-?[0-9]+)*&"; + const newStr = "labels=$1-$2$3&"; + + window.location = href.replace(new RegExp(regStr), newStr); + } + }); + }); + + $(".menu .ui.dropdown.label-filter").keydown(function(e) { + if (e.altKey && e.keyCode == 13) { + const selectedItems = $(".menu .ui.dropdown.label-filter .menu .item.selected"); + + if (selectedItems.length > 0) { + const item = $(selectedItems[0]); + + const href = item.attr("href"); + const id = item.data("label-id"); + + const regStr = "labels=(-?[0-9]+%2c)*(" + id + ")(%2c-?[0-9]+)*&"; + const newStr = "labels=$1-$2$3&"; + + window.location = href.replace(new RegExp(regStr), newStr); + } + } + }); +} +window.cancelCodeComment = function(btn) { + const form = $(btn).closest("form"); + if(form.length > 0 && form.hasClass('comment-form')) { + form.addClass('hide'); + form.parent().find('button.comment-form-reply').show(); + } else { + form.closest('.comment-code-cloud').remove() + } +} +window.onOAuthLoginClick = function() { + const oauthLoader = $('#oauth2-login-loader'); + const oauthNav = $('#oauth2-login-navigator'); + + oauthNav.hide(); + oauthLoader.removeClass('disabled'); + + setTimeout(function(){ + // recover previous content to let user try again + // usually redirection will be performed before this action + oauthLoader.addClass('disabled'); + oauthNav.show(); + },5000); +} |