|
|
|
/* 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,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function timeAddManual() {
|
|
|
|
$('.mini.modal')
|
|
|
|
.modal({
|
|
|
|
duration: 200,
|
|
|
|
onApprove: function() {
|
|
|
|
$('#add_time_manual_form').submit();
|
|
|
|
}
|
|
|
|
}).modal('show')
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
function toggleStopwatch() {
|
|
|
|
$("#toggle_stopwatch_form").submit();
|
|
|
|
}
|
|
|
|
function cancelStopwatch() {
|
|
|
|
$("#cancel_stopwatch_form").submit();
|
|
|
|
}
|
|
|
|
|
|
|
|
function initHeatmap(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
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function toggleDeadlineForm() {
|
|
|
|
$('#deadlineForm').fadeToggle(150);
|
|
|
|
}
|
|
|
|
|
|
|
|
function setDeadline() {
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function deleteDependencyModal(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();
|
Allow cross-repository dependencies on issues (#7901)
* in progress changes for #7405, added ability to add cross-repo dependencies
* removed unused repolink var
* fixed query that was breaking ci tests; fixed check in issue dependency add so that the id of the issue and dependency is checked rather than the indexes
* reverted removal of string in local files becasue these are done via crowdin, not updated manually
* removed 'Select("issue.*")' from getBlockedByDependencies and getBlockingDependencies based on comments in PR review
* changed getBlockedByDependencies and getBlockingDependencies to use a more xorm-like query, also updated the sidebar as a result
* simplified the getBlockingDependencies and getBlockedByDependencies methods; changed the sidebar to show the dependencies in a different format where you can see the name of the repository
* made some changes to the issue view in the dependencies (issue name on top, repo full name on separate line). Change view of issue in the dependency search results (also showing the full repo name on separate line)
* replace call to FindUserAccessibleRepoIDs with SearchRepositoryByName. The former was hardcoded to use isPrivate = false on the repo search, but this code needed it to be true. The SearchRepositoryByName method is used more in the code including on the user's dashboard
* some more tweaks to the layout of the issues when showing dependencies and in the search box when you add new dependencies
* added Name to the RepositoryMeta struct
* updated swagger doc
* fixed total count for link header on SearchIssues
* fixed indentation
* fixed aligment of remove icon on dependencies in issue sidebar
* removed unnecessary nil check (unnecessary because issue.loadRepo is called prior to this block)
* reverting .css change, somehow missed or forgot that less is used
* updated less file and generated css; updated sidebar template with styles to line up delete and issue index
* added ordering to the blocked by/depends on queries
* fixed sorting in issue dependency search and the depends on/blocks views to show issues from the current repo first, then by created date descending; added a "all cross repository dependencies" setting to allow this feature to be turned off, if turned off, the issue dependency search will work the way it did before (restricted to the current repository)
* re-applied my swagger changes after merge
* fixed split string condition in issue search
* changed ALLOW_CROSS_REPOSITORY_DEPENDENCIES description to sound more global than just the issue dependency search; returning 400 in the cross repo issue search api method if not enabled; fixed bug where the issue count did not respect the state parameter
* when adding a dependency to an issue, added a check to make sure the issue and dependency are in the same repo if cross repo dependencies is not enabled
* updated sortIssuesSession call in PullRequests, another commit moved this method from pull.go to pull_list.go so I had to re-apply my change here
* fixed incorrect setting of user id parameter in search repos call
5 years ago
|
|
|
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) +
|
Allow cross-repository dependencies on issues (#7901)
* in progress changes for #7405, added ability to add cross-repo dependencies
* removed unused repolink var
* fixed query that was breaking ci tests; fixed check in issue dependency add so that the id of the issue and dependency is checked rather than the indexes
* reverted removal of string in local files becasue these are done via crowdin, not updated manually
* removed 'Select("issue.*")' from getBlockedByDependencies and getBlockingDependencies based on comments in PR review
* changed getBlockedByDependencies and getBlockingDependencies to use a more xorm-like query, also updated the sidebar as a result
* simplified the getBlockingDependencies and getBlockedByDependencies methods; changed the sidebar to show the dependencies in a different format where you can see the name of the repository
* made some changes to the issue view in the dependencies (issue name on top, repo full name on separate line). Change view of issue in the dependency search results (also showing the full repo name on separate line)
* replace call to FindUserAccessibleRepoIDs with SearchRepositoryByName. The former was hardcoded to use isPrivate = false on the repo search, but this code needed it to be true. The SearchRepositoryByName method is used more in the code including on the user's dashboard
* some more tweaks to the layout of the issues when showing dependencies and in the search box when you add new dependencies
* added Name to the RepositoryMeta struct
* updated swagger doc
* fixed total count for link header on SearchIssues
* fixed indentation
* fixed aligment of remove icon on dependencies in issue sidebar
* removed unnecessary nil check (unnecessary because issue.loadRepo is called prior to this block)
* reverting .css change, somehow missed or forgot that less is used
* updated less file and generated css; updated sidebar template with styles to line up delete and issue index
* added ordering to the blocked by/depends on queries
* fixed sorting in issue dependency search and the depends on/blocks views to show issues from the current repo first, then by created date descending; added a "all cross repository dependencies" setting to allow this feature to be turned off, if turned off, the issue dependency search will work the way it did before (restricted to the current repository)
* re-applied my swagger changes after merge
* fixed split string condition in issue search
* changed ALLOW_CROSS_REPOSITORY_DEPENDENCIES description to sound more global than just the issue dependency search; returning 400 in the cross repo issue search api method if not enabled; fixed bug where the issue count did not respect the state parameter
* when adding a dependency to an issue, added a check to make sure the issue and dependency are in the same repo if cross repo dependencies is not enabled
* updated sortIssuesSession call in PullRequests, another commit moved this method from pull.go to pull_list.go so I had to re-apply my change here
* fixed incorrect setting of user id parameter in search repos call
5 years ago
|
|
|
'<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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function cancelCodeComment(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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onOAuthLoginClick() {
|
|
|
|
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);
|
|
|
|
}
|