diff --git a/assets/src/bundles/admin/origin-save.js b/assets/src/bundles/admin/origin-save.js
index 4ae62417..87c1dff5 100644
--- a/assets/src/bundles/admin/origin-save.js
+++ b/assets/src/bundles/admin/origin-save.js
@@ -1,363 +1,353 @@
/**
* Copyright (C) 2018-2020 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import {handleFetchError, csrfPost, htmlAlert} from 'utils/functions';
import {swhSpinnerSrc} from 'utils/constants';
let authorizedOriginTable;
let unauthorizedOriginTable;
let pendingSaveRequestsTable;
let acceptedSaveRequestsTable;
let rejectedSaveRequestsTable;
function enableRowSelection(tableSel) {
$(`${tableSel} tbody`).on('click', 'tr', function() {
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
$(tableSel).closest('.tab-pane').find('.swh-action-need-selection').prop('disabled', true);
} else {
$(`${tableSel} tr.selected`).removeClass('selected');
$(this).addClass('selected');
$(tableSel).closest('.tab-pane').find('.swh-action-need-selection').prop('disabled', false);
}
});
}
export function initOriginSaveAdmin() {
$(document).ready(() => {
$.fn.dataTable.ext.errMode = 'throw';
authorizedOriginTable = $('#swh-authorized-origin-urls').DataTable({
serverSide: true,
ajax: Urls.admin_origin_save_authorized_urls_list(),
columns: [{data: 'url', name: 'url'}],
scrollY: '50vh',
scrollCollapse: true,
info: false
});
enableRowSelection('#swh-authorized-origin-urls');
swh.webapp.addJumpToPagePopoverToDataTable(authorizedOriginTable);
unauthorizedOriginTable = $('#swh-unauthorized-origin-urls').DataTable({
serverSide: true,
ajax: Urls.admin_origin_save_unauthorized_urls_list(),
columns: [{data: 'url', name: 'url'}],
scrollY: '50vh',
scrollCollapse: true,
info: false
});
enableRowSelection('#swh-unauthorized-origin-urls');
swh.webapp.addJumpToPagePopoverToDataTable(unauthorizedOriginTable);
let columnsData = [
{
data: 'id',
name: 'id',
visible: false,
searchable: false
},
{
data: 'save_request_date',
name: 'request_date',
render: (data, type, row) => {
if (type === 'display') {
let date = new Date(data);
return date.toLocaleString();
}
return data;
}
},
{
data: 'visit_type',
name: 'visit_type'
},
{
data: 'origin_url',
name: 'origin_url',
render: (data, type, row) => {
if (type === 'display') {
let html = '';
const sanitizedURL = $.fn.dataTable.render.text().display(data);
if (row.save_task_status === 'succeeded') {
let browseOriginUrl = `${Urls.browse_origin()}?origin_url=${encodeURIComponent(sanitizedURL)}`;
if (row.visit_date) {
browseOriginUrl += `×tamp=${encodeURIComponent(row.visit_date)}`;
}
html += `${sanitizedURL}`;
} else {
html += sanitizedURL;
}
html += ` `;
return html;
}
return data;
}
}
];
pendingSaveRequestsTable = $('#swh-origin-save-pending-requests').DataTable({
serverSide: true,
processing: true,
language: {
processing: ``
},
ajax: Urls.origin_save_requests_list('pending'),
searchDelay: 1000,
columns: columnsData,
scrollY: '50vh',
scrollCollapse: true,
order: [[0, 'desc']],
responsive: {
details: {
type: 'none'
}
}
});
enableRowSelection('#swh-origin-save-pending-requests');
swh.webapp.addJumpToPagePopoverToDataTable(pendingSaveRequestsTable);
rejectedSaveRequestsTable = $('#swh-origin-save-rejected-requests').DataTable({
serverSide: true,
processing: true,
language: {
processing: ``
},
ajax: Urls.origin_save_requests_list('rejected'),
searchDelay: 1000,
columns: columnsData,
scrollY: '50vh',
scrollCollapse: true,
order: [[0, 'desc']],
responsive: {
details: {
type: 'none'
}
}
});
enableRowSelection('#swh-origin-save-rejected-requests');
swh.webapp.addJumpToPagePopoverToDataTable(rejectedSaveRequestsTable);
columnsData.push({
data: 'save_task_status',
name: 'save_task_status'
});
columnsData.push({
name: 'info',
render: (data, type, row) => {
if (row.save_task_status === 'succeeded' || row.save_task_status === 'failed') {
return '`;
} else {
return '';
}
}
});
acceptedSaveRequestsTable = $('#swh-origin-save-accepted-requests').DataTable({
serverSide: true,
processing: true,
language: {
processing: ``
},
ajax: Urls.origin_save_requests_list('accepted'),
searchDelay: 1000,
columns: columnsData,
scrollY: '50vh',
scrollCollapse: true,
order: [[0, 'desc']],
responsive: {
details: {
type: 'none'
}
}
});
enableRowSelection('#swh-origin-save-accepted-requests');
swh.webapp.addJumpToPagePopoverToDataTable(acceptedSaveRequestsTable);
$('#swh-origin-save-requests-nav-item').on('shown.bs.tab', () => {
pendingSaveRequestsTable.draw();
});
$('#swh-origin-save-url-filters-nav-item').on('shown.bs.tab', () => {
authorizedOriginTable.draw();
});
$('#swh-authorized-origins-tab').on('shown.bs.tab', () => {
authorizedOriginTable.draw();
});
$('#swh-unauthorized-origins-tab').on('shown.bs.tab', () => {
unauthorizedOriginTable.draw();
});
$('#swh-save-requests-pending-tab').on('shown.bs.tab', () => {
pendingSaveRequestsTable.draw();
});
$('#swh-save-requests-accepted-tab').on('shown.bs.tab', () => {
acceptedSaveRequestsTable.draw();
});
$('#swh-save-requests-rejected-tab').on('shown.bs.tab', () => {
rejectedSaveRequestsTable.draw();
});
$('#swh-save-requests-pending-tab').click(() => {
pendingSaveRequestsTable.ajax.reload(null, false);
});
$('#swh-save-requests-accepted-tab').click(() => {
acceptedSaveRequestsTable.ajax.reload(null, false);
});
$('#swh-save-requests-rejected-tab').click(() => {
rejectedSaveRequestsTable.ajax.reload(null, false);
});
$('body').on('click', e => {
if ($(e.target).parents('.popover').length > 0) {
- event.stopPropagation();
+ e.stopPropagation();
} else if ($(e.target).parents('.swh-save-request-info').length === 0) {
$('.swh-save-request-info').popover('dispose');
}
});
});
}
-export function addAuthorizedOriginUrl() {
- let originUrl = $('#swh-authorized-url-prefix').val();
- let addOriginUrl = Urls.admin_origin_save_add_authorized_url(originUrl);
- csrfPost(addOriginUrl)
- .then(handleFetchError)
- .then(() => {
- authorizedOriginTable.row.add({'url': originUrl}).draw();
- $('.swh-add-authorized-origin-status').html(
- htmlAlert('success', 'The origin url prefix has been successfully added in the authorized list.', true)
- );
- })
- .catch(response => {
- $('.swh-add-authorized-origin-status').html(
- htmlAlert('warning', 'The provided origin url prefix is already registered in the authorized list.', true)
- );
- });
+export async function addAuthorizedOriginUrl() {
+ const originUrl = $('#swh-authorized-url-prefix').val();
+ const addOriginUrl = Urls.admin_origin_save_add_authorized_url(originUrl);
+ try {
+ const response = await csrfPost(addOriginUrl);
+ handleFetchError(response);
+ authorizedOriginTable.row.add({'url': originUrl}).draw();
+ $('.swh-add-authorized-origin-status').html(
+ htmlAlert('success', 'The origin url prefix has been successfully added in the authorized list.', true)
+ );
+ } catch (_) {
+ $('.swh-add-authorized-origin-status').html(
+ htmlAlert('warning', 'The provided origin url prefix is already registered in the authorized list.', true)
+ );
+ }
}
-export function removeAuthorizedOriginUrl() {
- let originUrl = $('#swh-authorized-origin-urls tr.selected').text();
+export async function removeAuthorizedOriginUrl() {
+ const originUrl = $('#swh-authorized-origin-urls tr.selected').text();
if (originUrl) {
- let removeOriginUrl = Urls.admin_origin_save_remove_authorized_url(originUrl);
- csrfPost(removeOriginUrl)
- .then(handleFetchError)
- .then(() => {
- authorizedOriginTable.row('.selected').remove().draw();
- })
- .catch(() => {});
+ const removeOriginUrl = Urls.admin_origin_save_remove_authorized_url(originUrl);
+ try {
+ const response = await csrfPost(removeOriginUrl);
+ handleFetchError(response);
+ authorizedOriginTable.row('.selected').remove().draw();
+ } catch (_) {}
}
}
-export function addUnauthorizedOriginUrl() {
- let originUrl = $('#swh-unauthorized-url-prefix').val();
- let addOriginUrl = Urls.admin_origin_save_add_unauthorized_url(originUrl);
- csrfPost(addOriginUrl)
- .then(handleFetchError)
- .then(() => {
- unauthorizedOriginTable.row.add({'url': originUrl}).draw();
- $('.swh-add-unauthorized-origin-status').html(
- htmlAlert('success', 'The origin url prefix has been successfully added in the unauthorized list.', true)
- );
- })
- .catch(() => {
- $('.swh-add-unauthorized-origin-status').html(
- htmlAlert('warning', 'The provided origin url prefix is already registered in the unauthorized list.', true)
- );
- });
+export async function addUnauthorizedOriginUrl() {
+ const originUrl = $('#swh-unauthorized-url-prefix').val();
+ const addOriginUrl = Urls.admin_origin_save_add_unauthorized_url(originUrl);
+ try {
+ const response = await csrfPost(addOriginUrl);
+ handleFetchError(response);
+ unauthorizedOriginTable.row.add({'url': originUrl}).draw();
+ $('.swh-add-unauthorized-origin-status').html(
+ htmlAlert('success', 'The origin url prefix has been successfully added in the unauthorized list.', true)
+ );
+ } catch (_) {
+ $('.swh-add-unauthorized-origin-status').html(
+ htmlAlert('warning', 'The provided origin url prefix is already registered in the unauthorized list.', true)
+ );
+ }
}
-export function removeUnauthorizedOriginUrl() {
- let originUrl = $('#swh-unauthorized-origin-urls tr.selected').text();
+export async function removeUnauthorizedOriginUrl() {
+ const originUrl = $('#swh-unauthorized-origin-urls tr.selected').text();
if (originUrl) {
- let removeOriginUrl = Urls.admin_origin_save_remove_unauthorized_url(originUrl);
- csrfPost(removeOriginUrl)
- .then(handleFetchError)
- .then(() => {
- unauthorizedOriginTable.row('.selected').remove().draw();
- })
- .catch(() => {});
+ const removeOriginUrl = Urls.admin_origin_save_remove_unauthorized_url(originUrl);
+ try {
+ const response = await csrfPost(removeOriginUrl);
+ handleFetchError(response);
+ unauthorizedOriginTable.row('.selected').remove().draw();
+ } catch (_) {};
}
}
export function acceptOriginSaveRequest() {
- let selectedRow = pendingSaveRequestsTable.row('.selected');
+ const selectedRow = pendingSaveRequestsTable.row('.selected');
if (selectedRow.length) {
- let acceptOriginSaveRequestCallback = () => {
- let rowData = selectedRow.data();
- let acceptSaveRequestUrl = Urls.admin_origin_save_request_accept(rowData['visit_type'], rowData['origin_url']);
- csrfPost(acceptSaveRequestUrl)
- .then(() => {
- pendingSaveRequestsTable.ajax.reload(null, false);
- });
+ const acceptOriginSaveRequestCallback = async() => {
+ const rowData = selectedRow.data();
+ const acceptSaveRequestUrl = Urls.admin_origin_save_request_accept(rowData['visit_type'], rowData['origin_url']);
+ await csrfPost(acceptSaveRequestUrl);
+ pendingSaveRequestsTable.ajax.reload(null, false);
};
swh.webapp.showModalConfirm(
'Accept origin save request ?',
'Are you sure to accept this origin save request ?',
acceptOriginSaveRequestCallback);
}
}
export function rejectOriginSaveRequest() {
- let selectedRow = pendingSaveRequestsTable.row('.selected');
+ const selectedRow = pendingSaveRequestsTable.row('.selected');
if (selectedRow.length) {
- let rejectOriginSaveRequestCallback = () => {
- let rowData = selectedRow.data();
- let rejectSaveRequestUrl = Urls.admin_origin_save_request_reject(rowData['visit_type'], rowData['origin_url']);
- csrfPost(rejectSaveRequestUrl)
- .then(() => {
- pendingSaveRequestsTable.ajax.reload(null, false);
- });
+ let rejectOriginSaveRequestCallback = async() => {
+ const rowData = selectedRow.data();
+ const rejectSaveRequestUrl = Urls.admin_origin_save_request_reject(rowData['visit_type'], rowData['origin_url']);
+ await csrfPost(rejectSaveRequestUrl);
+ pendingSaveRequestsTable.ajax.reload(null, false);
};
swh.webapp.showModalConfirm(
'Reject origin save request ?',
'Are you sure to reject this origin save request ?',
rejectOriginSaveRequestCallback);
}
}
function removeOriginSaveRequest(requestTable) {
let selectedRow = requestTable.row('.selected');
if (selectedRow.length) {
let requestId = selectedRow.data()['id'];
- let removeOriginSaveRequestCallback = () => {
- let removeSaveRequestUrl = Urls.admin_origin_save_request_remove(requestId);
- csrfPost(removeSaveRequestUrl)
- .then(() => {
- requestTable.ajax.reload(null, false);
- });
+ let removeOriginSaveRequestCallback = async() => {
+ const removeSaveRequestUrl = Urls.admin_origin_save_request_remove(requestId);
+ await csrfPost(removeSaveRequestUrl);
+ requestTable.ajax.reload(null, false);
};
swh.webapp.showModalConfirm(
'Remove origin save request ?',
'Are you sure to remove this origin save request ?',
removeOriginSaveRequestCallback);
}
}
export function removePendingOriginSaveRequest() {
removeOriginSaveRequest(pendingSaveRequestsTable);
}
export function removeAcceptedOriginSaveRequest() {
removeOriginSaveRequest(acceptedSaveRequestsTable);
}
export function removeRejectedOriginSaveRequest() {
removeOriginSaveRequest(rejectedSaveRequestsTable);
}
diff --git a/assets/src/bundles/auth/index.js b/assets/src/bundles/auth/index.js
index 652736b1..a560efb4 100644
--- a/assets/src/bundles/auth/index.js
+++ b/assets/src/bundles/auth/index.js
@@ -1,193 +1,190 @@
/**
* Copyright (C) 2020 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import {handleFetchError, csrfPost, removeUrlFragment} from 'utils/functions';
import './auth.css';
let apiTokensTable;
function tokenForm(infoText, buttonText) {
const form =
`
`;
return form;
}
function errorMessage(message) {
return `${message}
`;
}
function successMessage(message) {
return `${message}
`;
}
function disableSubmitButton() {
$('#swh-token-form-submit').prop('disabled', true);
}
function generateToken() {
window.location = Urls.oidc_generate_bearer_token();
}
-function displayToken(tokenId) {
+async function displayToken(tokenId) {
const postData = {
token_id: tokenId
};
- csrfPost(Urls.oidc_get_bearer_token(), {}, JSON.stringify(postData))
- .then(handleFetchError)
- .then(response => response.text())
- .then(token => {
- const tokenHtml =
- `Below is your token.
- ${token}
`;
- swh.webapp.showModalHtml('Display bearer token', tokenHtml);
- })
- .catch(response => {
- response.text().then(responseText => {
- let errorMsg = 'Internal server error.';
- if (response.status === 400) {
- errorMsg = responseText;
- }
- swh.webapp.showModalHtml('Display bearer token', errorMessage(errorMsg));
- });
- });
+ try {
+ const response = await csrfPost(Urls.oidc_get_bearer_token(), {}, JSON.stringify(postData));
+ handleFetchError(response);
+ const token = await response.text();
+ const tokenHtml =
+ `Below is your token.
+ ${token}
`;
+ swh.webapp.showModalHtml('Display bearer token', tokenHtml);
+ } catch (response) {
+ const responseText = await response.text();
+ let errorMsg = 'Internal server error.';
+ if (response.status === 400) {
+ errorMsg = responseText;
+ }
+ swh.webapp.showModalHtml('Display bearer token', errorMessage(errorMsg));
+ }
}
-function revokeTokens(tokenIds) {
+async function revokeTokens(tokenIds) {
const postData = {
token_ids: tokenIds
};
- csrfPost(Urls.oidc_revoke_bearer_tokens(), {}, JSON.stringify(postData))
- .then(handleFetchError)
- .then(() => {
- disableSubmitButton();
- $('#swh-token-form-message').html(
- successMessage(`Bearer token${tokenIds.length > 1 ? 's' : ''} successfully revoked.`));
- apiTokensTable.draw();
- })
- .catch(() => {
- $('#swh-token-form-message').html(errorMessage('Internal server error.'));
- });
+ try {
+ const response = await csrfPost(Urls.oidc_revoke_bearer_tokens(), {}, JSON.stringify(postData));
+ handleFetchError(response);
+ disableSubmitButton();
+ $('#swh-token-form-message').html(
+ successMessage(`Bearer token${tokenIds.length > 1 ? 's' : ''} successfully revoked.`));
+ apiTokensTable.draw();
+ } catch (_) {
+ $('#swh-token-form-message').html(errorMessage('Internal server error.'));
+ }
}
function revokeToken(tokenId) {
revokeTokens([tokenId]);
}
function revokeAllTokens() {
const tokenIds = [];
const rowsData = apiTokensTable.rows().data();
for (let i = 0; i < rowsData.length; ++i) {
tokenIds.push(rowsData[i].id);
}
revokeTokens(tokenIds);
}
export function applyTokenAction(action, tokenId) {
const actionData = {
display: {
submitCallback: displayToken
},
generate: {
modalTitle: 'Bearer token generation',
infoText: 'Click on the button to generate the token. You will be redirected to ' +
'Software Heritage Authentication Service and might be asked to enter ' +
'your password again.',
buttonText: 'Generate token',
submitCallback: generateToken
},
revoke: {
modalTitle: 'Revoke bearer token',
infoText: 'Click on the button to revoke the token.',
buttonText: 'Revoke token',
submitCallback: revokeToken
},
revokeAll: {
modalTitle: 'Revoke all bearer tokens',
infoText: 'Click on the button to revoke all tokens.',
buttonText: 'Revoke tokens',
submitCallback: revokeAllTokens
}
};
if (!actionData[action]) {
return;
}
if (action !== 'display') {
const tokenFormHtml = tokenForm(
actionData[action].infoText, actionData[action].buttonText);
swh.webapp.showModalHtml(actionData[action].modalTitle, tokenFormHtml);
$(`#swh-token-form`).submit(event => {
event.preventDefault();
event.stopPropagation();
actionData[action].submitCallback(tokenId);
});
} else {
actionData[action].submitCallback(tokenId);
}
}
export function initProfilePage() {
$(document).ready(() => {
apiTokensTable = $('#swh-bearer-tokens-table')
.on('error.dt', (e, settings, techNote, message) => {
$('#swh-origin-save-request-list-error').text(
'An error occurred while retrieving the tokens list');
console.log(message);
})
.DataTable({
serverSide: true,
ajax: Urls.oidc_list_bearer_tokens(),
columns: [
{
data: 'creation_date',
name: 'creation_date',
render: (data, type, row) => {
if (type === 'display') {
let date = new Date(data);
return date.toLocaleString();
}
return data;
}
},
{
render: (data, type, row) => {
const html =
`
`;
return html;
}
}
],
ordering: false,
searching: false,
scrollY: '50vh',
scrollCollapse: true
});
$('#swh-oidc-profile-tokens-tab').on('shown.bs.tab', () => {
apiTokensTable.draw();
window.location.hash = '#tokens';
});
$('#swh-oidc-profile-account-tab').on('shown.bs.tab', () => {
removeUrlFragment();
});
if (window.location.hash === '#tokens') {
$('.nav-tabs a[href="#swh-oidc-profile-tokens"]').tab('show');
}
});
}
diff --git a/assets/src/bundles/browse/origin-search.js b/assets/src/bundles/browse/origin-search.js
index 97508473..226d722b 100644
--- a/assets/src/bundles/browse/origin-search.js
+++ b/assets/src/bundles/browse/origin-search.js
@@ -1,270 +1,260 @@
/**
* Copyright (C) 2018-2021 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import {handleFetchError, isArchivedOrigin} from 'utils/functions';
const limit = 100;
let linksPrev = [];
let linkNext = null;
let linkCurrent = null;
let inSearch = false;
function parseLinkHeader(s) {
let re = /<(.+)>; rel="next"/;
return s.match(re)[1];
}
function fixTableRowsStyle() {
setTimeout(() => {
$('#origin-search-results tbody tr').removeAttr('style');
});
}
function clearOriginSearchResultsTable() {
$('#origin-search-results tbody tr').remove();
}
-function populateOriginSearchResultsTable(origins) {
+async function populateOriginSearchResultsTable(origins) {
if (origins.length > 0) {
$('#swh-origin-search-results').show();
$('#swh-no-result').hide();
clearOriginSearchResultsTable();
let table = $('#origin-search-results tbody');
+ let promises = [];
for (let [i, origin] of origins.entries()) {
let browseUrl = `${Urls.browse_origin()}?origin_url=${encodeURIComponent(origin.url)}`;
let tableRow =
``;
tableRow +=
`` +
'' +
'Checking | ';
tableRow +=
'' +
`${origin.url} | `;
tableRow +=
`` +
'' +
'Checking | ';
tableRow += '
';
table.append(tableRow);
// get async latest visit snapshot and update visit status icon
let latestSnapshotUrl = Urls.api_1_origin_visit_latest(origin.url);
latestSnapshotUrl += '?require_snapshot=true';
- fetch(latestSnapshotUrl)
- .then(response => {
- if (response.status === 404) {
- throw new Error();
- }
- return response.json();
- })
- .then(data => {
- if (data.type) {
- $(`#visit-type-origin-${i}`).html(data.type);
- $(`#visit-status-origin-${i}`).html(
- 'Archived');
- } else {
- throw new Error();
- }
- })
- .catch(() => {
- $(`#visit-type-origin-${i}`).html('unknown');
- $(`#visit-status-origin-${i}`).html(
- 'Pending archival');
- if ($('#swh-filter-empty-visits').prop('checked')) {
- $(`#origin-${i}`).remove();
- }
- });
+ promises.push(fetch(latestSnapshotUrl));
+ }
+ const responses = await Promise.all(promises);
+ const responsesData = await Promise.all(responses.map(r => r.json()));
+ for (let i = 0; i < responses.length; ++i) {
+ const response = responses[i];
+ const data = responsesData[i];
+ if (response.status !== 404 && data.type) {
+ $(`#visit-type-origin-${i}`).html(data.type);
+ $(`#visit-status-origin-${i}`).html(
+ 'Archived');
+ } else {
+ $(`#visit-type-origin-${i}`).html('unknown');
+ $(`#visit-status-origin-${i}`).html(
+ 'Pending archival');
+ if ($('#swh-filter-empty-visits').prop('checked')) {
+ $(`#origin-${i}`).remove();
+ }
+ }
}
fixTableRowsStyle();
} else {
$('#swh-origin-search-results').hide();
$('#swh-no-result').text('No origins matching the search criteria were found.');
$('#swh-no-result').show();
}
if (linkNext === null) {
$('#origins-next-results-button').addClass('disabled');
} else {
$('#origins-next-results-button').removeClass('disabled');
}
if (linksPrev.length === 0) {
$('#origins-prev-results-button').addClass('disabled');
} else {
$('#origins-prev-results-button').removeClass('disabled');
}
inSearch = false;
setTimeout(() => {
window.scrollTo(0, 0);
});
}
function searchOriginsFirst(searchQueryText, limit) {
let baseSearchUrl;
let searchMetadata = $('#swh-search-origin-metadata').prop('checked');
if (searchMetadata) {
baseSearchUrl = new URL(Urls.api_1_origin_metadata_search(), window.location);
baseSearchUrl.searchParams.append('fulltext', searchQueryText);
} else {
baseSearchUrl = new URL(Urls.api_1_origin_search(searchQueryText), window.location);
}
let withVisit = $('#swh-search-origins-with-visit').prop('checked');
baseSearchUrl.searchParams.append('limit', limit);
baseSearchUrl.searchParams.append('with_visit', withVisit);
const visitType = $('#swh-search-visit-type').val();
if (visitType !== 'any') {
baseSearchUrl.searchParams.append('visit_type', visitType);
}
let searchUrl = baseSearchUrl.toString();
searchOrigins(searchUrl);
}
-function searchOrigins(searchUrl) {
+async function searchOrigins(searchUrl) {
clearOriginSearchResultsTable();
$('.swh-loading').addClass('show');
- let response = fetch(searchUrl)
- .then(handleFetchError)
- .then(resp => {
- response = resp;
- return response.json();
- })
- .then(data => {
- // Save link to the current results page
- linkCurrent = searchUrl;
- // Save link to the next results page.
- linkNext = null;
- if (response.headers.has('Link')) {
- let parsedLink = parseLinkHeader(response.headers.get('Link'));
- if (parsedLink !== undefined) {
- linkNext = parsedLink;
- }
+ try {
+ const response = await fetch(searchUrl);
+ handleFetchError(response);
+ const data = await response.json();
+ // Save link to the current results page
+ linkCurrent = searchUrl;
+ // Save link to the next results page.
+ linkNext = null;
+ if (response.headers.has('Link')) {
+ let parsedLink = parseLinkHeader(response.headers.get('Link'));
+ if (parsedLink !== undefined) {
+ linkNext = parsedLink;
}
- // prevLinks is updated by the caller, which is the one to know if
- // we're going forward or backward in the pages.
+ }
+ // prevLinks is updated by the caller, which is the one to know if
+ // we're going forward or backward in the pages.
- $('.swh-loading').removeClass('show');
- populateOriginSearchResultsTable(data);
- })
- .catch(response => {
- $('.swh-loading').removeClass('show');
- inSearch = false;
- $('#swh-origin-search-results').hide();
- $('#swh-no-result').text(`Error ${response.status}: ${response.statusText}`);
- $('#swh-no-result').show();
- });
+ $('.swh-loading').removeClass('show');
+ populateOriginSearchResultsTable(data);
+ } catch (response) {
+ $('.swh-loading').removeClass('show');
+ inSearch = false;
+ $('#swh-origin-search-results').hide();
+ $('#swh-no-result').text(`Error ${response.status}: ${response.statusText}`);
+ $('#swh-no-result').show();
+ }
}
async function doSearch() {
$('#swh-no-result').hide();
- let searchQueryText = $('#swh-origins-url-patterns').val();
+ const searchQueryText = $('#swh-origins-url-patterns').val();
inSearch = true;
if (searchQueryText.startsWith('swh:')) {
- // searchQueryText may be a PID so sending search queries to PID resolve endpoint
- let resolveSWHIDUrl = Urls.api_1_resolve_swhid(searchQueryText);
- fetch(resolveSWHIDUrl)
- .then(handleFetchError)
- .then(response => response.json())
- .then(data => {
- // SWHID has been successfully resolved,
- // so redirect to browse page
- window.location = data.browse_url;
- })
- .catch(response => {
- // display a useful error message if the input
- // looks like a SWHID
- response.json().then(data => {
- $('#swh-origin-search-results').hide();
- $('.swh-search-pagination').hide();
- $('#swh-no-result').text(data.reason);
- $('#swh-no-result').show();
- });
- });
+ try {
+ // searchQueryText may be a PID so sending search queries to PID resolve endpoint
+ const resolveSWHIDUrl = Urls.api_1_resolve_swhid(searchQueryText);
+ const response = await fetch(resolveSWHIDUrl);
+ handleFetchError(response);
+ const data = await response.json();
+ // SWHID has been successfully resolved,
+ // so redirect to browse page
+ window.location = data.browse_url;
+ } catch (response) {
+ // display a useful error message if the input
+ // looks like a SWHID
+ const data = await response.json();
+ $('#swh-origin-search-results').hide();
+ $('.swh-search-pagination').hide();
+ $('#swh-no-result').text(data.reason);
+ $('#swh-no-result').show();
+ }
} else if (await isArchivedOrigin(searchQueryText)) {
// redirect to the browse origin
window.location.href =
`${Urls.browse_origin()}?origin_url=${encodeURIComponent(searchQueryText)}`;
} else {
// otherwise, proceed with origins search irrespective of the error
$('#swh-origin-search-results').show();
$('.swh-search-pagination').show();
searchOriginsFirst(searchQueryText, limit);
}
}
export function initOriginSearch() {
$(document).ready(() => {
$('#swh-search-origins').submit(event => {
event.preventDefault();
if (event.target.checkValidity()) {
$(event.target).removeClass('was-validated');
let searchQueryText = $('#swh-origins-url-patterns').val().trim();
let withVisit = $('#swh-search-origins-with-visit').prop('checked');
let withContent = $('#swh-filter-empty-visits').prop('checked');
let searchMetadata = $('#swh-search-origin-metadata').prop('checked');
const visitType = $('#swh-search-visit-type').val();
let queryParameters = new URLSearchParams();
queryParameters.append('q', searchQueryText);
if (withVisit) {
queryParameters.append('with_visit', withVisit);
}
if (withContent) {
queryParameters.append('with_content', withContent);
}
if (searchMetadata) {
queryParameters.append('search_metadata', searchMetadata);
}
if (visitType !== 'any') {
queryParameters.append('visit_type', visitType);
}
// Update the url, triggering page reload and effective search
window.location = `${Urls.browse_search()}?${queryParameters.toString()}`;
} else {
$(event.target).addClass('was-validated');
}
});
$('#origins-next-results-button').click(event => {
if ($('#origins-next-results-button').hasClass('disabled') || inSearch) {
return;
}
inSearch = true;
linksPrev.push(linkCurrent);
searchOrigins(linkNext);
event.preventDefault();
});
$('#origins-prev-results-button').click(event => {
if ($('#origins-prev-results-button').hasClass('disabled') || inSearch) {
return;
}
inSearch = true;
searchOrigins(linksPrev.pop());
event.preventDefault();
});
let urlParams = new URLSearchParams(window.location.search);
let query = urlParams.get('q');
let withVisit = urlParams.has('with_visit');
let withContent = urlParams.has('with_content');
let searchMetadata = urlParams.has('search_metadata');
let visitType = urlParams.get('visit_type');
if (query) {
$('#swh-origins-url-patterns').val(query);
$('#swh-search-origins-with-visit').prop('checked', withVisit);
$('#swh-filter-empty-visits').prop('checked', withContent);
$('#swh-search-origin-metadata').prop('checked', searchMetadata);
if (visitType) {
$('#swh-search-visit-type').val(visitType);
}
doSearch();
}
});
}
diff --git a/assets/src/bundles/revision/diff-utils.js b/assets/src/bundles/revision/diff-utils.js
index 33351d61..8c462d48 100644
--- a/assets/src/bundles/revision/diff-utils.js
+++ b/assets/src/bundles/revision/diff-utils.js
@@ -1,794 +1,793 @@
/**
* Copyright (C) 2018-2020 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import 'waypoints/lib/jquery.waypoints';
import {swhSpinnerSrc} from 'utils/constants';
import {removeUrlFragment} from 'utils/functions';
import diffPanelTemplate from './diff-panel.ejs';
// number of changed files in the revision
let changes = null;
let nbChangedFiles = 0;
// to track the number of already computed files diffs
let nbDiffsComputed = 0;
// the no newline at end of file marker from Github
let noNewLineMarker = '' +
'' +
'';
// to track the total number of added lines in files diffs
let nbAdditions = 0;
// to track the total number of deleted lines in files diffs
let nbDeletions = 0;
// to track the already computed diffs by id
let computedDiffs = {};
// map a diff id to its computation url
let diffsUrls = {};
// to keep track of diff lines to highlight
let startLines = null;
let endLines = null;
// map max line numbers characters to diff
const diffMaxNumberChars = {};
// focused diff for highlighting
let focusedDiff = null;
// highlighting color
const lineHighlightColor = '#fdf3da';
// might contain diff lines to highlight parsed from URL fragment
let selectedDiffLinesInfo;
// URL fragment to append when switching to 'Changes' tab
const changesUrlFragment = '#swh-revision-changes';
// current displayed tab name
let currentTabName = 'Files';
// to check if a DOM element is in the viewport
function isInViewport(elt) {
let elementTop = $(elt).offset().top;
let elementBottom = elementTop + $(elt).outerHeight();
let viewportTop = $(window).scrollTop();
let viewportBottom = viewportTop + $(window).height();
return elementBottom > viewportTop && elementTop < viewportBottom;
}
// to format the diffs line numbers
export function formatDiffLineNumbers(diffId, fromLine, toLine) {
const maxNumberChars = diffMaxNumberChars[diffId];
const fromLineStr = toLnStr(fromLine);
const toLineStr = toLnStr(toLine);
let ret = '';
for (let i = 0; i < (maxNumberChars - fromLineStr.length); ++i) {
ret += ' ';
}
ret += fromLineStr;
ret += ' ';
for (let i = 0; i < (maxNumberChars - toLineStr.length); ++i) {
ret += ' ';
}
ret += toLineStr;
return ret;
}
function parseDiffHunkRangeIfAny(lineText) {
let baseFromLine, baseToLine;
if (lineText.startsWith('@@')) {
let linesInfoRegExp = new RegExp(/^@@ -(\d+),(\d+) \+(\d+),(\d+) @@$/gm);
let linesInfoRegExp2 = new RegExp(/^@@ -(\d+) \+(\d+),(\d+) @@$/gm);
let linesInfoRegExp3 = new RegExp(/^@@ -(\d+),(\d+) \+(\d+) @@$/gm);
let linesInfoRegExp4 = new RegExp(/^@@ -(\d+) \+(\d+) @@$/gm);
let linesInfo = linesInfoRegExp.exec(lineText);
let linesInfo2 = linesInfoRegExp2.exec(lineText);
let linesInfo3 = linesInfoRegExp3.exec(lineText);
let linesInfo4 = linesInfoRegExp4.exec(lineText);
if (linesInfo) {
baseFromLine = parseInt(linesInfo[1]) - 1;
baseToLine = parseInt(linesInfo[3]) - 1;
} else if (linesInfo2) {
baseFromLine = parseInt(linesInfo2[1]) - 1;
baseToLine = parseInt(linesInfo2[2]) - 1;
} else if (linesInfo3) {
baseFromLine = parseInt(linesInfo3[1]) - 1;
baseToLine = parseInt(linesInfo3[3]) - 1;
} else if (linesInfo4) {
baseFromLine = parseInt(linesInfo4[1]) - 1;
baseToLine = parseInt(linesInfo4[2]) - 1;
}
}
if (baseFromLine !== undefined) {
return [baseFromLine, baseToLine];
} else {
return null;
}
}
function toLnInt(lnStr) {
return lnStr ? parseInt(lnStr) : 0;
};
function toLnStr(lnInt) {
return lnInt ? lnInt.toString() : '';
};
// parse diff line numbers to an int array [from, to]
export function parseDiffLineNumbers(lineNumbersStr, from, to) {
let lines;
if (!from && !to) {
lines = lineNumbersStr.replace(/[ ]+/g, ' ').split(' ');
if (lines.length > 2) {
lines.shift();
}
lines = lines.map(x => toLnInt(x));
} else {
let lineNumber = toLnInt(lineNumbersStr.trim());
if (from) {
lines = [lineNumber, 0];
} else if (to) {
lines = [0, lineNumber];
}
}
return lines;
}
// serialize selected line numbers range to string for URL fragment
export function selectedDiffLinesToFragment(startLines, endLines, unified) {
let selectedLinesFragment = '';
selectedLinesFragment += `F${startLines[0] || 0}`;
selectedLinesFragment += `T${startLines[1] || 0}`;
selectedLinesFragment += `-F${endLines[0] || 0}`;
selectedLinesFragment += `T${endLines[1] || 0}`;
if (unified) {
selectedLinesFragment += '-unified';
} else {
selectedLinesFragment += '-split';
}
return selectedLinesFragment;
}
// parse selected lines from URL fragment
export function fragmentToSelectedDiffLines(fragment) {
const RE_LINES = /F([0-9]+)T([0-9]+)-F([0-9]+)T([0-9]+)-([a-z]+)/;
const matchObj = RE_LINES.exec(fragment);
if (matchObj.length === 6) {
return {
startLines: [parseInt(matchObj[1]), parseInt(matchObj[2])],
endLines: [parseInt(matchObj[3]), parseInt(matchObj[4])],
unified: matchObj[5] === 'unified'
};
} else {
return null;
}
}
// function to highlight a single diff line
function highlightDiffLine(diffId, i) {
let line = $(`#${diffId} .hljs-ln-line[data-line-number="${i}"]`);
let lineNumbers = $(`#${diffId} .hljs-ln-numbers[data-line-number="${i}"]`);
lineNumbers.css('color', 'black');
lineNumbers.css('font-weight', 'bold');
line.css('background-color', lineHighlightColor);
line.css('mix-blend-mode', 'multiply');
return line;
}
// function to reset highlighting
function resetHighlightedDiffLines(resetVars = true) {
if (resetVars) {
focusedDiff = null;
startLines = null;
endLines = null;
}
$('.hljs-ln-line[data-line-number]').css('background-color', 'initial');
$('.hljs-ln-line[data-line-number]').css('mix-blend-mode', 'initial');
$('.hljs-ln-numbers[data-line-number]').css('color', '#aaa');
$('.hljs-ln-numbers[data-line-number]').css('font-weight', 'initial');
if (currentTabName === 'Changes' && window.location.hash !== changesUrlFragment) {
window.history.replaceState('', document.title,
window.location.pathname + window.location.search + changesUrlFragment);
}
}
// highlight lines in a diff, return first highlighted line numbers element
function highlightDiffLines(diffId, startLines, endLines, unified) {
let firstHighlightedLine;
// unified diff case
if (unified) {
let start = formatDiffLineNumbers(diffId, startLines[0], startLines[1]);
let end = formatDiffLineNumbers(diffId, endLines[0], endLines[1]);
const startLine = $(`#${diffId} .hljs-ln-line[data-line-number="${start}"]`);
const endLine = $(`#${diffId} .hljs-ln-line[data-line-number="${end}"]`);
if ($(endLine).position().top < $(startLine).position().top) {
[start, end] = [end, start];
firstHighlightedLine = endLine;
} else {
firstHighlightedLine = startLine;
}
const lineTd = highlightDiffLine(diffId, start);
let tr = $(lineTd).closest('tr');
let lineNumbers = $(tr).children('.hljs-ln-line').data('line-number').toString();
while (lineNumbers !== end) {
if (lineNumbers.trim()) {
highlightDiffLine(diffId, lineNumbers);
}
tr = $(tr).next();
lineNumbers = $(tr).children('.hljs-ln-line').data('line-number').toString();
}
highlightDiffLine(diffId, end);
// split diff case
} else {
// highlight only from part of the diff
if (startLines[0] && endLines[0]) {
const start = Math.min(startLines[0], endLines[0]);
const end = Math.max(startLines[0], endLines[0]);
for (let i = start; i <= end; ++i) {
highlightDiffLine(`${diffId}-from`, i);
}
firstHighlightedLine = $(`#${diffId}-from .hljs-ln-line[data-line-number="${start}"]`);
// highlight only to part of the diff
} else if (startLines[1] && endLines[1]) {
const start = Math.min(startLines[1], endLines[1]);
const end = Math.max(startLines[1], endLines[1]);
for (let i = start; i <= end; ++i) {
highlightDiffLine(`${diffId}-to`, i);
}
firstHighlightedLine = $(`#${diffId}-to .hljs-ln-line[data-line-number="${start}"]`);
// highlight both part of the diff
} else {
let left, right;
if (startLines[0] && endLines[1]) {
left = startLines[0];
right = endLines[1];
} else {
left = endLines[0];
right = startLines[1];
}
const leftLine = $(`#${diffId}-from .hljs-ln-line[data-line-number="${left}"]`);
const rightLine = $(`#${diffId}-to .hljs-ln-line[data-line-number="${right}"]`);
const leftLineAbove = $(leftLine).position().top < $(rightLine).position().top;
if (leftLineAbove) {
firstHighlightedLine = leftLine;
} else {
firstHighlightedLine = rightLine;
}
let fromTr = $(`#${diffId}-from tr`).first();
let fromLn = $(fromTr).children('.hljs-ln-line').data('line-number');
let toTr = $(`#${diffId}-to tr`).first();
let toLn = $(toTr).children('.hljs-ln-line').data('line-number');
let canHighlight = false;
while (true) {
if (leftLineAbove && fromLn === left) {
canHighlight = true;
} else if (!leftLineAbove && toLn === right) {
canHighlight = true;
}
if (canHighlight && fromLn) {
highlightDiffLine(`${diffId}-from`, fromLn);
}
if (canHighlight && toLn) {
highlightDiffLine(`${diffId}-to`, toLn);
}
if ((leftLineAbove && toLn === right) || (!leftLineAbove && fromLn === left)) {
break;
}
fromTr = $(fromTr).next();
fromLn = $(fromTr).children('.hljs-ln-line').data('line-number');
toTr = $(toTr).next();
toLn = $(toTr).children('.hljs-ln-line').data('line-number');
}
}
}
let selectedLinesFragment = selectedDiffLinesToFragment(startLines, endLines, unified);
window.location.hash = `diff_${diffId}+${selectedLinesFragment}`;
return firstHighlightedLine;
}
// callback to switch from side-by-side diff to unified one
export function showUnifiedDiff(diffId) {
$(`#${diffId}-split-diff`).css('display', 'none');
$(`#${diffId}-unified-diff`).css('display', 'block');
}
// callback to switch from unified diff to side-by-side one
export function showSplitDiff(diffId) {
$(`#${diffId}-unified-diff`).css('display', 'none');
$(`#${diffId}-split-diff`).css('display', 'block');
}
// to compute diff and process it for display
-export function computeDiff(diffUrl, diffId) {
+export async function computeDiff(diffUrl, diffId) {
// force diff computation ?
let force = diffUrl.indexOf('force=true') !== -1;
// it no forced computation and diff already computed, do nothing
if (!force && computedDiffs.hasOwnProperty(diffId)) {
return;
}
function setLineNumbers(lnElt, lineNumbers) {
$(lnElt).attr('data-line-number', lineNumbers || '');
$(lnElt).children().attr('data-line-number', lineNumbers || '');
$(lnElt).siblings().attr('data-line-number', lineNumbers || '');
}
// mark diff computation as already requested
computedDiffs[diffId] = true;
$(`#${diffId}-loading`).css('visibility', 'visible');
// set spinner visible while requesting diff
$(`#${diffId}-loading`).css('display', 'block');
$(`#${diffId}-highlightjs`).css('display', 'none');
// request diff computation and process it
- fetch(diffUrl)
- .then(response => response.json())
- .then(data => {
- // increment number of computed diffs
- ++nbDiffsComputed;
- // toggle the 'Compute all diffs' button if all diffs have been computed
- if (nbDiffsComputed === changes.length) {
- $('#swh-compute-all-diffs').addClass('active');
- }
+ const response = await fetch(diffUrl);
+ const data = await response.json();
+
+ // increment number of computed diffs
+ ++nbDiffsComputed;
+ // toggle the 'Compute all diffs' button if all diffs have been computed
+ if (nbDiffsComputed === changes.length) {
+ $('#swh-compute-all-diffs').addClass('active');
+ }
- // Large diff (> threshold) are not automatically computed,
- // add a button to force its computation
- if (data.diff_str.indexOf('Large diff') === 0) {
- $(`#${diffId}`)[0].innerHTML = data.diff_str +
+ // Large diff (> threshold) are not automatically computed,
+ // add a button to force its computation
+ if (data.diff_str.indexOf('Large diff') === 0) {
+ $(`#${diffId}`)[0].innerHTML = data.diff_str +
`
';
- setDiffVisible(diffId);
- } else if (data.diff_str.indexOf('@@') !== 0) {
- $(`#${diffId}`).text(data.diff_str);
- setDiffVisible(diffId);
- } else {
+ setDiffVisible(diffId);
+ } else if (data.diff_str.indexOf('@@') !== 0) {
+ $(`#${diffId}`).text(data.diff_str);
+ setDiffVisible(diffId);
+ } else {
- // prepare code highlighting
- $(`.${diffId}`).removeClass('nohighlight');
- $(`.${diffId}`).addClass(data.language);
+ // prepare code highlighting
+ $(`.${diffId}`).removeClass('nohighlight');
+ $(`.${diffId}`).addClass(data.language);
- // set unified diff text
- $(`#${diffId}`).text(data.diff_str);
+ // set unified diff text
+ $(`#${diffId}`).text(data.diff_str);
- // code highlighting for unified diff
- $(`#${diffId}`).each((i, elt) => {
- hljs.highlightElement(elt);
- hljs.lineNumbersElementSync(elt);
- });
+ // code highlighting for unified diff
+ $(`#${diffId}`).each((i, elt) => {
+ hljs.highlightElement(elt);
+ hljs.lineNumbersElementSync(elt);
+ });
- // process unified diff lines in order to generate side-by-side diffs text
- // but also compute line numbers for unified and side-by-side diffs
- let baseFromLine = '';
- let baseToLine = '';
- let fromToLines = [];
- let fromLines = [];
- let toLines = [];
- let maxNumberChars = 0;
- let diffFromStr = '';
- let diffToStr = '';
- let linesOffset = 0;
-
- $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
- let lnText = lnElt.nextSibling.innerText;
- let linesInfo = parseDiffHunkRangeIfAny(lnText);
- let fromLine = '';
- let toLine = '';
- // parsed lines info from the diff output
- if (linesInfo) {
- baseFromLine = linesInfo[0];
- baseToLine = linesInfo[1];
- linesOffset = 0;
- diffFromStr += (lnText + '\n');
- diffToStr += (lnText + '\n');
- fromLines.push('');
+ // process unified diff lines in order to generate side-by-side diffs text
+ // but also compute line numbers for unified and side-by-side diffs
+ let baseFromLine = '';
+ let baseToLine = '';
+ let fromToLines = [];
+ let fromLines = [];
+ let toLines = [];
+ let maxNumberChars = 0;
+ let diffFromStr = '';
+ let diffToStr = '';
+ let linesOffset = 0;
+
+ $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
+ let lnText = lnElt.nextSibling.innerText;
+ let linesInfo = parseDiffHunkRangeIfAny(lnText);
+ let fromLine = '';
+ let toLine = '';
+ // parsed lines info from the diff output
+ if (linesInfo) {
+ baseFromLine = linesInfo[0];
+ baseToLine = linesInfo[1];
+ linesOffset = 0;
+ diffFromStr += (lnText + '\n');
+ diffToStr += (lnText + '\n');
+ fromLines.push('');
+ toLines.push('');
+ // line removed in the from file
+ } else if (lnText.length > 0 && lnText[0] === '-') {
+ baseFromLine = baseFromLine + 1;
+ fromLine = baseFromLine.toString();
+ fromLines.push(fromLine);
+ ++nbDeletions;
+ diffFromStr += (lnText + '\n');
+ ++linesOffset;
+ // line added in the to file
+ } else if (lnText.length > 0 && lnText[0] === '+') {
+ baseToLine = baseToLine + 1;
+ toLine = baseToLine.toString();
+ toLines.push(toLine);
+ ++nbAdditions;
+ diffToStr += (lnText + '\n');
+ --linesOffset;
+ // line present in both files
+ } else {
+ baseFromLine = baseFromLine + 1;
+ baseToLine = baseToLine + 1;
+ fromLine = baseFromLine.toString();
+ toLine = baseToLine.toString();
+ for (let j = 0; j < Math.abs(linesOffset); ++j) {
+ if (linesOffset > 0) {
+ diffToStr += '\n';
toLines.push('');
- // line removed in the from file
- } else if (lnText.length > 0 && lnText[0] === '-') {
- baseFromLine = baseFromLine + 1;
- fromLine = baseFromLine.toString();
- fromLines.push(fromLine);
- ++nbDeletions;
- diffFromStr += (lnText + '\n');
- ++linesOffset;
- // line added in the to file
- } else if (lnText.length > 0 && lnText[0] === '+') {
- baseToLine = baseToLine + 1;
- toLine = baseToLine.toString();
- toLines.push(toLine);
- ++nbAdditions;
- diffToStr += (lnText + '\n');
- --linesOffset;
- // line present in both files
} else {
- baseFromLine = baseFromLine + 1;
- baseToLine = baseToLine + 1;
- fromLine = baseFromLine.toString();
- toLine = baseToLine.toString();
- for (let j = 0; j < Math.abs(linesOffset); ++j) {
- if (linesOffset > 0) {
- diffToStr += '\n';
- toLines.push('');
- } else {
- diffFromStr += '\n';
- fromLines.push('');
- }
- }
- linesOffset = 0;
- diffFromStr += (lnText + '\n');
- diffToStr += (lnText + '\n');
- toLines.push(toLine);
- fromLines.push(fromLine);
- }
- if (!baseFromLine) {
- fromLine = '';
- }
- if (!baseToLine) {
- toLine = '';
+ diffFromStr += '\n';
+ fromLines.push('');
}
- fromToLines[i] = [fromLine, toLine];
- maxNumberChars = Math.max(maxNumberChars, fromLine.length);
- maxNumberChars = Math.max(maxNumberChars, toLine.length);
- });
+ }
+ linesOffset = 0;
+ diffFromStr += (lnText + '\n');
+ diffToStr += (lnText + '\n');
+ toLines.push(toLine);
+ fromLines.push(fromLine);
+ }
+ if (!baseFromLine) {
+ fromLine = '';
+ }
+ if (!baseToLine) {
+ toLine = '';
+ }
+ fromToLines[i] = [fromLine, toLine];
+ maxNumberChars = Math.max(maxNumberChars, fromLine.length);
+ maxNumberChars = Math.max(maxNumberChars, toLine.length);
+ });
- diffMaxNumberChars[diffId] = maxNumberChars;
+ diffMaxNumberChars[diffId] = maxNumberChars;
- // set side-by-side diffs text
- $(`#${diffId}-from`).text(diffFromStr);
- $(`#${diffId}-to`).text(diffToStr);
+ // set side-by-side diffs text
+ $(`#${diffId}-from`).text(diffFromStr);
+ $(`#${diffId}-to`).text(diffToStr);
- // code highlighting for side-by-side diffs
- $(`#${diffId}-from, #${diffId}-to`).each((i, elt) => {
- hljs.highlightElement(elt);
- hljs.lineNumbersElementSync(elt);
- });
+ // code highlighting for side-by-side diffs
+ $(`#${diffId}-from, #${diffId}-to`).each((i, elt) => {
+ hljs.highlightElement(elt);
+ hljs.lineNumbersElementSync(elt);
+ });
- // diff highlighting for added/removed lines on top of code highlighting
- $(`.${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
- let lnText = lnElt.nextSibling.innerText;
- if (lnText.startsWith('@@')) {
- $(lnElt).parent().addClass('swh-diff-lines-info');
- let linesInfoText = $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').text();
- $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').children().remove();
- $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').text('');
- $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').append(`${linesInfoText}`);
- } else if (lnText.length > 0 && lnText[0] === '-') {
- $(lnElt).parent().addClass('swh-diff-removed-line');
- } else if (lnText.length > 0 && lnText[0] === '+') {
- $(lnElt).parent().addClass('swh-diff-added-line');
- }
- });
+ // diff highlighting for added/removed lines on top of code highlighting
+ $(`.${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
+ let lnText = lnElt.nextSibling.innerText;
+ if (lnText.startsWith('@@')) {
+ $(lnElt).parent().addClass('swh-diff-lines-info');
+ let linesInfoText = $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').text();
+ $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').children().remove();
+ $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').text('');
+ $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').append(`${linesInfoText}`);
+ } else if (lnText.length > 0 && lnText[0] === '-') {
+ $(lnElt).parent().addClass('swh-diff-removed-line');
+ } else if (lnText.length > 0 && lnText[0] === '+') {
+ $(lnElt).parent().addClass('swh-diff-added-line');
+ }
+ });
- // set line numbers for unified diff
- $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
- const lineNumbers = formatDiffLineNumbers(diffId, fromToLines[i][0], fromToLines[i][1]);
- setLineNumbers(lnElt, lineNumbers);
- });
+ // set line numbers for unified diff
+ $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
+ const lineNumbers = formatDiffLineNumbers(diffId, fromToLines[i][0], fromToLines[i][1]);
+ setLineNumbers(lnElt, lineNumbers);
+ });
- // set line numbers for the from side-by-side diff
- $(`#${diffId}-from .hljs-ln-numbers`).each((i, lnElt) => {
- setLineNumbers(lnElt, fromLines[i]);
- });
+ // set line numbers for the from side-by-side diff
+ $(`#${diffId}-from .hljs-ln-numbers`).each((i, lnElt) => {
+ setLineNumbers(lnElt, fromLines[i]);
+ });
- // set line numbers for the to side-by-side diff
- $(`#${diffId}-to .hljs-ln-numbers`).each((i, lnElt) => {
- setLineNumbers(lnElt, toLines[i]);
- });
+ // set line numbers for the to side-by-side diff
+ $(`#${diffId}-to .hljs-ln-numbers`).each((i, lnElt) => {
+ setLineNumbers(lnElt, toLines[i]);
+ });
- // last processing:
- // - remove the '+' and '-' at the beginning of the diff lines
- // from code highlighting
- // - add the "no new line at end of file marker" if needed
- $(`.${diffId} .hljs-ln-code`).each((i, lnElt) => {
- if (lnElt.firstChild) {
- if (lnElt.firstChild.nodeName !== '#text') {
- let lineText = lnElt.firstChild.innerHTML;
- if (lineText[0] === '-' || lineText[0] === '+') {
- lnElt.firstChild.innerHTML = lineText.substr(1);
- let newTextNode = document.createTextNode(lineText[0]);
- $(lnElt).prepend(newTextNode);
- }
- }
- $(lnElt).contents().filter((i, elt) => {
- return elt.nodeType === 3; // Node.TEXT_NODE
- }).each((i, textNode) => {
- let swhNoNewLineMarker = '[swh-no-nl-marker]';
- if (textNode.textContent.indexOf(swhNoNewLineMarker) !== -1) {
- textNode.textContent = textNode.textContent.replace(swhNoNewLineMarker, '');
- $(lnElt).append($(noNewLineMarker));
- }
- });
+ // last processing:
+ // - remove the '+' and '-' at the beginning of the diff lines
+ // from code highlighting
+ // - add the "no new line at end of file marker" if needed
+ $(`.${diffId} .hljs-ln-code`).each((i, lnElt) => {
+ if (lnElt.firstChild) {
+ if (lnElt.firstChild.nodeName !== '#text') {
+ let lineText = lnElt.firstChild.innerHTML;
+ if (lineText[0] === '-' || lineText[0] === '+') {
+ lnElt.firstChild.innerHTML = lineText.substr(1);
+ let newTextNode = document.createTextNode(lineText[0]);
+ $(lnElt).prepend(newTextNode);
+ }
+ }
+ $(lnElt).contents().filter((i, elt) => {
+ return elt.nodeType === 3; // Node.TEXT_NODE
+ }).each((i, textNode) => {
+ let swhNoNewLineMarker = '[swh-no-nl-marker]';
+ if (textNode.textContent.indexOf(swhNoNewLineMarker) !== -1) {
+ textNode.textContent = textNode.textContent.replace(swhNoNewLineMarker, '');
+ $(lnElt).append($(noNewLineMarker));
}
});
+ }
+ });
- // hide the diff mode switch button in case of not generated diffs
- if (data.diff_str.indexOf('Diffs are not generated for non textual content') !== 0) {
- $(`#diff_${diffId} .diff-styles`).css('visibility', 'visible');
- }
+ // hide the diff mode switch button in case of not generated diffs
+ if (data.diff_str.indexOf('Diffs are not generated for non textual content') !== 0) {
+ $(`#diff_${diffId} .diff-styles`).css('visibility', 'visible');
+ }
- setDiffVisible(diffId);
+ setDiffVisible(diffId);
- // highlight diff lines if provided in URL fragment
- if (selectedDiffLinesInfo &&
+ // highlight diff lines if provided in URL fragment
+ if (selectedDiffLinesInfo &&
selectedDiffLinesInfo.diffPanelId.indexOf(diffId) !== -1) {
- if (!selectedDiffLinesInfo.unified) {
- showSplitDiff(diffId);
- }
- const firstHighlightedLine = highlightDiffLines(
- diffId, selectedDiffLinesInfo.startLines,
- selectedDiffLinesInfo.endLines, selectedDiffLinesInfo.unified);
-
- $('html, body').animate(
- {
- scrollTop: firstHighlightedLine.offset().top - 50
- },
- {
- duration: 500
- }
- );
- }
+ if (!selectedDiffLinesInfo.unified) {
+ showSplitDiff(diffId);
}
- });
+ const firstHighlightedLine = highlightDiffLines(
+ diffId, selectedDiffLinesInfo.startLines,
+ selectedDiffLinesInfo.endLines, selectedDiffLinesInfo.unified);
+
+ $('html, body').animate(
+ {
+ scrollTop: firstHighlightedLine.offset().top - 50
+ },
+ {
+ duration: 500
+ }
+ );
+ }
+ }
+
}
function setDiffVisible(diffId) {
// set the unified diff visible by default
$(`#${diffId}-loading`).css('display', 'none');
$(`#${diffId}-highlightjs`).css('display', 'block');
// update displayed counters
$('#swh-revision-lines-added').text(`${nbAdditions} additions`);
$('#swh-revision-lines-deleted').text(`${nbDeletions} deletions`);
$('#swh-nb-diffs-computed').text(nbDiffsComputed);
// refresh the waypoints triggering diffs computation as
// the DOM layout has been updated
Waypoint.refreshAll();
}
// to compute all visible diffs in the viewport
function computeVisibleDiffs() {
$('.swh-file-diff-panel').each((i, elt) => {
if (isInViewport(elt)) {
let diffId = elt.id.replace('diff_', '');
computeDiff(diffsUrls[diffId], diffId);
}
});
}
function genDiffPanel(diffData) {
let diffPanelTitle = diffData.path;
if (diffData.type === 'rename') {
diffPanelTitle = `${diffData.from_path} → ${diffData.to_path}`;
}
return diffPanelTemplate({
diffData: diffData,
diffPanelTitle: diffPanelTitle,
swhSpinnerSrc: swhSpinnerSrc
});
}
// setup waypoints to request diffs computation on the fly while scrolling
function setupWaypoints() {
for (let i = 0; i < changes.length; ++i) {
let diffData = changes[i];
// create a waypoint that will trigger diff computation when
// the top of the diff panel hits the bottom of the viewport
$(`#diff_${diffData.id}`).waypoint({
handler: function() {
if (isInViewport(this.element)) {
let diffId = this.element.id.replace('diff_', '');
computeDiff(diffsUrls[diffId], diffId);
this.destroy();
}
},
offset: '100%'
});
// create a waypoint that will trigger diff computation when
// the bottom of the diff panel hits the top of the viewport
$(`#diff_${diffData.id}`).waypoint({
handler: function() {
if (isInViewport(this.element)) {
let diffId = this.element.id.replace('diff_', '');
computeDiff(diffsUrls[diffId], diffId);
this.destroy();
}
},
offset: function() {
return -$(this.element).height();
}
});
}
Waypoint.refreshAll();
}
function scrollToDiffPanel(diffPanelId, setHash = true) {
// disable waypoints while scrolling as we do not want to
// launch computation of diffs the user is not interested in
// (file changes list can be large)
Waypoint.disableAll();
$('html, body').animate(
{
scrollTop: $(diffPanelId).offset().top
},
{
duration: 500,
complete: () => {
if (setHash) {
window.location.hash = diffPanelId;
}
// enable waypoints back after scrolling
Waypoint.enableAll();
// compute diffs visible in the viewport
computeVisibleDiffs();
}
});
}
// callback when the user clicks on the 'Compute all diffs' button
export function computeAllDiffs(event) {
$(event.currentTarget).addClass('active');
for (let diffId in diffsUrls) {
if (diffsUrls.hasOwnProperty(diffId)) {
computeDiff(diffsUrls[diffId], diffId);
}
}
event.stopPropagation();
}
export async function initRevisionDiff(revisionMessageBody, diffRevisionUrl) {
await import(/* webpackChunkName: "highlightjs" */ 'utils/highlightjs');
// callback when the 'Changes' tab is activated
- $(document).on('shown.bs.tab', 'a[data-toggle="tab"]', e => {
+ $(document).on('shown.bs.tab', 'a[data-toggle="tab"]', async e => {
currentTabName = e.currentTarget.text.trim();
if (currentTabName === 'Changes') {
window.location.hash = changesUrlFragment;
$('#readme-panel').css('display', 'none');
if (changes) {
return;
}
// request computation of revision file changes list
// when navigating to the 'Changes' tab and add diff panels
// to the DOM when receiving the result
- fetch(diffRevisionUrl)
- .then(response => response.json())
- .then(data => {
- changes = data.changes;
- nbChangedFiles = data.total_nb_changes;
- let changedFilesText = `${nbChangedFiles} changed file`;
- if (nbChangedFiles !== 1) {
- changedFilesText += 's';
- }
- $('#swh-revision-changed-files').text(changedFilesText);
- $('#swh-total-nb-diffs').text(changes.length);
- $('#swh-revision-changes-list pre')[0].innerHTML = data.changes_msg;
-
- $('#swh-revision-changes-loading').css('display', 'none');
- $('#swh-revision-changes-list pre').css('display', 'block');
- $('#swh-compute-all-diffs').css('visibility', 'visible');
- $('#swh-revision-changes-list').removeClass('in');
-
- if (nbChangedFiles > changes.length) {
- $('#swh-too-large-revision-diff').css('display', 'block');
- $('#swh-nb-loaded-diffs').text(changes.length);
- }
+ const response = await fetch(diffRevisionUrl);
+ const data = await response.json();
+
+ changes = data.changes;
+ nbChangedFiles = data.total_nb_changes;
+ let changedFilesText = `${nbChangedFiles} changed file`;
+ if (nbChangedFiles !== 1) {
+ changedFilesText += 's';
+ }
+ $('#swh-revision-changed-files').text(changedFilesText);
+ $('#swh-total-nb-diffs').text(changes.length);
+ $('#swh-revision-changes-list pre')[0].innerHTML = data.changes_msg;
+
+ $('#swh-revision-changes-loading').css('display', 'none');
+ $('#swh-revision-changes-list pre').css('display', 'block');
+ $('#swh-compute-all-diffs').css('visibility', 'visible');
+ $('#swh-revision-changes-list').removeClass('in');
+
+ if (nbChangedFiles > changes.length) {
+ $('#swh-too-large-revision-diff').css('display', 'block');
+ $('#swh-nb-loaded-diffs').text(changes.length);
+ }
- for (let i = 0; i < changes.length; ++i) {
- let diffData = changes[i];
- diffsUrls[diffData.id] = diffData.diff_url;
- $('#swh-revision-diffs').append(genDiffPanel(diffData));
- }
+ for (let i = 0; i < changes.length; ++i) {
+ let diffData = changes[i];
+ diffsUrls[diffData.id] = diffData.diff_url;
+ $('#swh-revision-diffs').append(genDiffPanel(diffData));
+ }
- setupWaypoints();
- computeVisibleDiffs();
+ setupWaypoints();
+ computeVisibleDiffs();
- if (selectedDiffLinesInfo) {
- scrollToDiffPanel(selectedDiffLinesInfo.diffPanelId, false);
- }
+ if (selectedDiffLinesInfo) {
+ scrollToDiffPanel(selectedDiffLinesInfo.diffPanelId, false);
+ }
- });
} else if (currentTabName === 'Files') {
removeUrlFragment();
$('#readme-panel').css('display', 'block');
}
});
$(document).ready(() => {
if (revisionMessageBody.length > 0) {
$('#swh-revision-message').addClass('in');
} else {
$('#swh-collapse-revision-message').attr('data-toggle', '');
}
// callback when the user requests to scroll on a specific diff or back to top
$('#swh-revision-changes-list a[href^="#"], #back-to-top a[href^="#"]').click(e => {
let href = $.attr(e.currentTarget, 'href');
scrollToDiffPanel(href);
return false;
});
// click callback for highlighting diff lines
$('body').click(evt => {
if (currentTabName !== 'Changes') {
return;
}
if (evt.target.classList.contains('hljs-ln-n')) {
const diffId = $(evt.target).closest('code').prop('id');
const from = diffId.indexOf('-from') !== -1;
const to = diffId.indexOf('-to') !== -1;
const lineNumbers = $(evt.target).data('line-number').toString();
const currentDiff = diffId.replace('-from', '').replace('-to', '');
if (!evt.shiftKey || currentDiff !== focusedDiff || !lineNumbers.trim()) {
resetHighlightedDiffLines();
focusedDiff = currentDiff;
}
if (currentDiff === focusedDiff && lineNumbers.trim()) {
if (!evt.shiftKey) {
startLines = parseDiffLineNumbers(lineNumbers, from, to);
highlightDiffLines(currentDiff, startLines, startLines, !from && !to);
} else if (startLines) {
resetHighlightedDiffLines(false);
endLines = parseDiffLineNumbers(lineNumbers, from, to);
highlightDiffLines(currentDiff, startLines, endLines, !from && !to);
}
}
} else {
resetHighlightedDiffLines();
}
});
// if an URL fragment for highlighting a diff is present
// parse highlighting info and initiate diff loading
const fragment = window.location.hash;
if (fragment) {
const split = fragment.split('+');
if (split.length === 2) {
selectedDiffLinesInfo = fragmentToSelectedDiffLines(split[1]);
if (selectedDiffLinesInfo) {
selectedDiffLinesInfo.diffPanelId = split[0];
$(`.nav-tabs a[href="${changesUrlFragment}"]`).tab('show');
}
}
if (fragment === changesUrlFragment) {
$(`.nav-tabs a[href="${changesUrlFragment}"]`).tab('show');
}
}
});
}
diff --git a/assets/src/bundles/save/index.js b/assets/src/bundles/save/index.js
index 857493d1..c2e901f6 100644
--- a/assets/src/bundles/save/index.js
+++ b/assets/src/bundles/save/index.js
@@ -1,567 +1,563 @@
/**
* Copyright (C) 2018-2021 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import {csrfPost, handleFetchError, isGitRepoUrl, htmlAlert, removeUrlFragment} from 'utils/functions';
import {swhSpinnerSrc} from 'utils/constants';
import artifactFormRowTemplate from './artifact-form-row.ejs';
let saveRequestsTable;
-function originSaveRequest(
+async function originSaveRequest(
originType, originUrl, extraData,
acceptedCallback, pendingCallback, errorCallback
) {
// Actually trigger the origin save request
let addSaveOriginRequestUrl = Urls.api_1_save_origin(originType, originUrl);
$('.swh-processing-save-request').css('display', 'block');
let headers = {};
let body = null;
if (extraData !== {}) {
body = JSON.stringify(extraData);
headers = {
'Content-Type': 'application/json'
};
};
- csrfPost(addSaveOriginRequestUrl, headers, body)
- .then(handleFetchError)
- .then(response => response.json())
- .then(data => {
- $('.swh-processing-save-request').css('display', 'none');
- if (data.save_request_status === 'accepted') {
- acceptedCallback();
- } else {
- pendingCallback();
- }
- })
- .catch(response => {
- $('.swh-processing-save-request').css('display', 'none');
- response.json().then(errorData => {
- errorCallback(response.status, errorData);
- });
- });
+ try {
+ const response = await csrfPost(addSaveOriginRequestUrl, headers, body);
+ handleFetchError(response);
+ const data = await response.json();
+ $('.swh-processing-save-request').css('display', 'none');
+ if (data.save_request_status === 'accepted') {
+ acceptedCallback();
+ } else {
+ pendingCallback();
+ }
+ } catch (response) {
+ $('.swh-processing-save-request').css('display', 'none');
+ const errorData = await response.json();
+ errorCallback(response.status, errorData);
+ };
}
function addArtifactVersionAutofillHandler(formId) {
// autofill artifact version input with the filename from
// the artifact url without extensions
$(`#swh-input-artifact-url-${formId}`).on('input', function(event) {
const artifactUrl = $(this).val().trim();
let filename = artifactUrl.split('/').slice(-1)[0];
if (filename !== artifactUrl) {
filename = filename.replace(/tar.*$/, 'tar');
const filenameNoExt = filename.split('.').slice(0, -1).join('.');
const artifactVersion = $(`#swh-input-artifact-version-${formId}`);
if (filenameNoExt !== filename) {
artifactVersion.val(filenameNoExt);
}
}
});
}
export function maybeRequireExtraInputs() {
// Read the actual selected value and depending on the origin type, display some extra
// inputs or hide them. This makes the extra inputs disabled when not displayed.
const originType = $('#swh-input-visit-type').val();
let display = 'none';
let disabled = true;
if (originType === 'archives') {
display = 'flex';
disabled = false;
}
$('.swh-save-origin-archives-form').css('display', display);
if (!disabled) {
// help paragraph must have block display for proper rendering
$('#swh-save-origin-archives-help').css('display', 'block');
}
$('.swh-save-origin-archives-form .form-control').prop('disabled', disabled);
if (originType === 'archives' && $('.swh-save-origin-archives-form').length === 1) {
// insert first artifact row when the archives visit type is selected for the first time
$('.swh-save-origin-archives-form').last().after(
artifactFormRowTemplate({deletableRow: false, formId: 0}));
addArtifactVersionAutofillHandler(0);
}
}
export function addArtifactFormRow() {
const formId = $('.swh-save-origin-artifact-form').length;
$('.swh-save-origin-artifact-form').last().after(
artifactFormRowTemplate({
deletableRow: true,
formId: formId
})
);
addArtifactVersionAutofillHandler(formId);
}
export function deleteArtifactFormRow(event) {
$(event.target).closest('.swh-save-origin-artifact-form').remove();
}
const userRequestsFilterCheckbox = `
`;
export function initOriginSave() {
- $(document).ready(() => {
+ $(document).ready(async() => {
$.fn.dataTable.ext.errMode = 'none';
- fetch(Urls.origin_save_types_list())
- .then(response => response.json())
- .then(data => {
- for (let originType of data) {
- $('#swh-input-visit-type').append(``);
- }
- // set git as the default value as before
- $('#swh-input-visit-type').val('git');
- });
+ const response = await fetch(Urls.origin_save_types_list());
+ const data = await response.json();
+
+ for (let originType of data) {
+ $('#swh-input-visit-type').append(``);
+ }
+ // set git as the default value as before
+ $('#swh-input-visit-type').val('git');
saveRequestsTable = $('#swh-origin-save-requests')
.on('error.dt', (e, settings, techNote, message) => {
$('#swh-origin-save-request-list-error').text('An error occurred while retrieving the save requests list');
console.log(message);
})
.DataTable({
serverSide: true,
processing: true,
language: {
processing: ``
},
ajax: {
url: Urls.origin_save_requests_list('all'),
data: (d) => {
if (swh.webapp.isUserLoggedIn() && $('#swh-save-requests-user-filter').prop('checked')) {
d.user_requests_only = '1';
}
}
},
searchDelay: 1000,
// see https://datatables.net/examples/advanced_init/dom_toolbar.html and the comments section
// this option customizes datatables UI components by adding an extra checkbox above the table
// while keeping bootstrap layout
dom: '<"row"<"col-sm-3"l><"col-sm-6 text-left user-requests-filter"><"col-sm-3"f>>' +
'<"row"<"col-sm-12"tr>>' +
'<"row"<"col-sm-5"i><"col-sm-7"p>>',
fnInitComplete: function() {
if (swh.webapp.isUserLoggedIn()) {
$('div.user-requests-filter').html(userRequestsFilterCheckbox);
$('#swh-save-requests-user-filter').on('change', () => {
saveRequestsTable.draw();
});
}
},
columns: [
{
data: 'save_request_date',
name: 'request_date',
render: (data, type, row) => {
if (type === 'display') {
let date = new Date(data);
return date.toLocaleString();
}
return data;
}
},
{
data: 'visit_type',
name: 'visit_type'
},
{
data: 'origin_url',
name: 'origin_url',
render: (data, type, row) => {
if (type === 'display') {
let html = '';
const sanitizedURL = $.fn.dataTable.render.text().display(data);
if (row.save_task_status === 'succeeded') {
let browseOriginUrl = `${Urls.browse_origin()}?origin_url=${encodeURIComponent(sanitizedURL)}`;
if (row.visit_date) {
browseOriginUrl += `×tamp=${encodeURIComponent(row.visit_date)}`;
}
html += `${sanitizedURL}`;
} else {
html += sanitizedURL;
}
html += ` `;
return html;
}
return data;
}
},
{
data: 'save_request_status',
name: 'status'
},
{
data: 'save_task_status',
name: 'loading_task_status'
},
{
name: 'info',
render: (data, type, row) => {
if (row.save_task_status === 'succeeded' || row.save_task_status === 'failed') {
return ``;
} else {
return '';
}
}
},
{
render: (data, type, row) => {
if (row.save_request_status === 'accepted') {
const saveAgainButton =
'';
return saveAgainButton;
} else {
return '';
}
}
}
],
scrollY: '50vh',
scrollCollapse: true,
order: [[0, 'desc']],
responsive: {
details: {
type: 'none'
}
}
});
swh.webapp.addJumpToPagePopoverToDataTable(saveRequestsTable);
$('#swh-origin-save-requests-list-tab').on('shown.bs.tab', () => {
saveRequestsTable.draw();
window.location.hash = '#requests';
});
$('#swh-origin-save-request-help-tab').on('shown.bs.tab', () => {
removeUrlFragment();
$('.swh-save-request-info').popover('dispose');
});
let saveRequestAcceptedAlert = htmlAlert(
'success',
'The "save code now" request has been accepted and will be processed as soon as possible.',
true
);
let saveRequestPendingAlert = htmlAlert(
'warning',
'The "save code now" request has been put in pending state and may be accepted for processing after manual review.',
true
);
let saveRequestRateLimitedAlert = htmlAlert(
'danger',
'The rate limit for "save code now" requests has been reached. Please try again later.',
true
);
let saveRequestUnknownErrorAlert = htmlAlert(
'danger',
'An unexpected error happened when submitting the "save code now request".',
true
);
$('#swh-save-origin-form').submit(event => {
event.preventDefault();
event.stopPropagation();
$('.alert').alert('close');
if (event.target.checkValidity()) {
$(event.target).removeClass('was-validated');
let originType = $('#swh-input-visit-type').val();
let originUrl = $('#swh-input-origin-url').val();
// read the extra inputs for the 'archives' type
let extraData = {};
if (originType === 'archives') {
extraData['archives_data'] = [];
for (let i = 0; i < $('.swh-save-origin-artifact-form').length; ++i) {
extraData['archives_data'].push({
'artifact_url': $(`#swh-input-artifact-url-${i}`).val(),
'artifact_version': $(`#swh-input-artifact-version-${i}`).val()
});
}
}
originSaveRequest(originType, originUrl, extraData,
() => $('#swh-origin-save-request-status').html(saveRequestAcceptedAlert),
() => $('#swh-origin-save-request-status').html(saveRequestPendingAlert),
(statusCode, errorData) => {
$('#swh-origin-save-request-status').css('color', 'red');
if (statusCode === 403) {
const errorAlert = htmlAlert('danger', `Error: ${errorData['reason']}`);
$('#swh-origin-save-request-status').html(errorAlert);
} else if (statusCode === 429) {
$('#swh-origin-save-request-status').html(saveRequestRateLimitedAlert);
} else if (statusCode === 400) {
const errorAlert = htmlAlert('danger', errorData['reason']);
$('#swh-origin-save-request-status').html(errorAlert);
} else {
$('#swh-origin-save-request-status').html(saveRequestUnknownErrorAlert);
}
});
} else {
$(event.target).addClass('was-validated');
}
});
$('#swh-show-origin-save-requests-list').on('click', (event) => {
event.preventDefault();
$('.nav-tabs a[href="#swh-origin-save-requests-list"]').tab('show');
});
$('#swh-input-origin-url').on('input', function(event) {
let originUrl = $(this).val().trim();
$(this).val(originUrl);
$('#swh-input-visit-type option').each(function() {
let val = $(this).val();
if (val && originUrl.includes(val)) {
$(this).prop('selected', true);
}
});
});
if (window.location.hash === '#requests') {
$('.nav-tabs a[href="#swh-origin-save-requests-list"]').tab('show');
}
});
}
export function validateSaveOriginUrl(input) {
let originType = $('#swh-input-visit-type').val();
let originUrl = null;
let validUrl = true;
try {
originUrl = new URL(input.value.trim());
} catch (TypeError) {
validUrl = false;
}
if (validUrl) {
let allowedProtocols = ['http:', 'https:', 'svn:', 'git:'];
validUrl = (
allowedProtocols.find(protocol => protocol === originUrl.protocol) !== undefined
);
}
if (validUrl && originType === 'git') {
// additional checks for well known code hosting providers
switch (originUrl.hostname) {
case 'github.com':
validUrl = isGitRepoUrl(originUrl);
break;
case 'git.code.sf.net':
validUrl = isGitRepoUrl(originUrl, '/p/');
break;
case 'bitbucket.org':
validUrl = isGitRepoUrl(originUrl);
break;
default:
if (originUrl.hostname.startsWith('gitlab.')) {
validUrl = isGitRepoUrl(originUrl);
}
break;
}
}
if (validUrl) {
input.setCustomValidity('');
} else {
input.setCustomValidity('The origin url is not valid or does not reference a code repository');
}
}
export function initTakeNewSnapshot() {
let newSnapshotRequestAcceptedAlert = htmlAlert(
'success',
'The "take new snapshot" request has been accepted and will be processed as soon as possible.',
true
);
let newSnapshotRequestPendingAlert = htmlAlert(
'warning',
'The "take new snapshot" request has been put in pending state and may be accepted for processing after manual review.',
true
);
let newSnapshotRequestRateLimitAlert = htmlAlert(
'danger',
'The rate limit for "take new snapshot" requests has been reached. Please try again later.',
true
);
let newSnapshotRequestUnknownErrorAlert = htmlAlert(
'danger',
'An unexpected error happened when submitting the "save code now request".',
true
);
$(document).ready(() => {
$('#swh-take-new-snapshot-form').submit(event => {
event.preventDefault();
event.stopPropagation();
let originType = $('#swh-input-visit-type').val();
let originUrl = $('#swh-input-origin-url').val();
let extraData = {};
originSaveRequest(originType, originUrl, extraData,
() => $('#swh-take-new-snapshot-request-status').html(newSnapshotRequestAcceptedAlert),
() => $('#swh-take-new-snapshot-request-status').html(newSnapshotRequestPendingAlert),
(statusCode, errorData) => {
$('#swh-take-new-snapshot-request-status').css('color', 'red');
if (statusCode === 403) {
const errorAlert = htmlAlert('danger', `Error: ${errorData['detail']}`, true);
$('#swh-take-new-snapshot-request-status').html(errorAlert);
} else if (statusCode === 429) {
$('#swh-take-new-snapshot-request-status').html(newSnapshotRequestRateLimitAlert);
} else {
$('#swh-take-new-snapshot-request-status').html(newSnapshotRequestUnknownErrorAlert);
}
});
});
});
}
export function formatValuePerType(type, value) {
// Given some typed value, format and return accordingly formatted value
const mapFormatPerTypeFn = {
'json': (v) => JSON.stringify(v, null, 2),
'date': (v) => new Date(v).toLocaleString(),
'raw': (v) => v,
'duration': (v) => v + ' seconds'
};
return value === null ? null : mapFormatPerTypeFn[type](value);
}
-export function displaySaveRequestInfo(event, saveRequestId) {
+export async function displaySaveRequestInfo(event, saveRequestId) {
event.stopPropagation();
const saveRequestTaskInfoUrl = Urls.origin_save_task_info(saveRequestId);
// close popover when clicking again on the info icon
if ($(event.target).data('bs.popover')) {
$(event.target).popover('dispose');
return;
}
$('.swh-save-request-info').popover('dispose');
$(event.target).popover({
animation: false,
boundary: 'viewport',
container: 'body',
title: 'Save request task information ' +
'`,
content: `
Fetching task information ...
`,
html: true,
placement: 'left',
sanitizeFn: swh.webapp.filterXSS
});
$(event.target).on('shown.bs.popover', function() {
const popoverId = $(this).attr('aria-describedby');
$(`#${popoverId} .mdi-close`).click(() => {
$(this).popover('dispose');
});
});
$(event.target).popover('show');
- fetch(saveRequestTaskInfoUrl)
- .then(response => response.json())
- .then(saveRequestTaskInfo => {
- let content;
- if ($.isEmptyObject(saveRequestTaskInfo)) {
- content = 'Not available';
- } else {
- let saveRequestInfo = [];
- const taskData = {
- 'Type': ['raw', 'type'],
- 'Visit status': ['raw', 'visit_status'],
- 'Arguments': ['json', 'arguments'],
- 'Id': ['raw', 'id'],
- 'Backend id': ['raw', 'backend_id'],
- 'Scheduling date': ['date', 'scheduled'],
- 'Start date': ['date', 'started'],
- 'Completion date': ['date', 'ended'],
- 'Duration': ['duration', 'duration'],
- 'Runner': ['raw', 'worker'],
- 'Log': ['raw', 'message']
- };
- for (const [title, [type, property]] of Object.entries(taskData)) {
- if (saveRequestTaskInfo.hasOwnProperty(property)) {
- saveRequestInfo.push({
- key: title,
- value: formatValuePerType(type, saveRequestTaskInfo[property])
- });
- }
- }
- content = '';
- for (let info of saveRequestInfo) {
- content +=
+ const response = await fetch(saveRequestTaskInfoUrl);
+ const saveRequestTaskInfo = await response.json();
+
+ let content;
+ if ($.isEmptyObject(saveRequestTaskInfo)) {
+ content = 'Not available';
+ } else {
+ let saveRequestInfo = [];
+ const taskData = {
+ 'Type': ['raw', 'type'],
+ 'Visit status': ['raw', 'visit_status'],
+ 'Arguments': ['json', 'arguments'],
+ 'Id': ['raw', 'id'],
+ 'Backend id': ['raw', 'backend_id'],
+ 'Scheduling date': ['date', 'scheduled'],
+ 'Start date': ['date', 'started'],
+ 'Completion date': ['date', 'ended'],
+ 'Duration': ['duration', 'duration'],
+ 'Runner': ['raw', 'worker'],
+ 'Log': ['raw', 'message']
+ };
+ for (const [title, [type, property]] of Object.entries(taskData)) {
+ if (saveRequestTaskInfo.hasOwnProperty(property)) {
+ saveRequestInfo.push({
+ key: title,
+ value: formatValuePerType(type, saveRequestTaskInfo[property])
+ });
+ }
+ }
+ content = '';
+ for (let info of saveRequestInfo) {
+ content +=
`
${info.key} |
${info.value}
|
`;
- }
- content += '
';
- }
- $('.swh-popover').html(content);
- $(event.target).popover('update');
- });
+ }
+ content += '
';
+ }
+ $('.swh-popover').html(content);
+ $(event.target).popover('update');
}
export function fillSaveRequestFormAndScroll(visitType, originUrl) {
$('#swh-input-origin-url').val(originUrl);
let originTypeFound = false;
$('#swh-input-visit-type option').each(function() {
let val = $(this).val();
if (val && originUrl.includes(val)) {
$(this).prop('selected', true);
originTypeFound = true;
}
});
if (!originTypeFound) {
$('#swh-input-visit-type option').each(function() {
let val = $(this).val();
if (val === visitType) {
$(this).prop('selected', true);
}
});
}
window.scrollTo(0, 0);
}
diff --git a/assets/src/bundles/vault/vault-create-tasks.js b/assets/src/bundles/vault/vault-create-tasks.js
index 5fab1b1c..c043a90e 100644
--- a/assets/src/bundles/vault/vault-create-tasks.js
+++ b/assets/src/bundles/vault/vault-create-tasks.js
@@ -1,161 +1,155 @@
/**
* Copyright (C) 2018-2019 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import {handleFetchError, csrfPost, htmlAlert} from 'utils/functions';
const alertStyle = {
'position': 'fixed',
'left': '1rem',
'bottom': '1rem',
'z-index': '100000'
};
-export function vaultRequest(objectType, objectId) {
+export async function vaultRequest(objectType, objectId) {
let vaultUrl;
if (objectType === 'directory') {
vaultUrl = Urls.api_1_vault_cook_directory(objectId);
} else {
vaultUrl = Urls.api_1_vault_cook_revision_gitfast(objectId);
}
// check if object has already been cooked
- fetch(vaultUrl)
- .then(response => response.json())
- .then(data => {
- // object needs to be cooked
- if (data.exception === 'NotFoundExc' || data.status === 'failed') {
- // if last cooking has failed, remove previous task info from localStorage
- // in order to force the recooking of the object
- swh.vault.removeCookingTaskInfo([objectId]);
- $(`#vault-cook-${objectType}-modal`).modal('show');
- // object has been cooked and should be in the vault cache,
- // it will be asked to cook it again if it is not
- } else if (data.status === 'done') {
- $(`#vault-fetch-${objectType}-modal`).modal('show');
- } else {
- const cookingServiceDownAlert =
+ const response = await fetch(vaultUrl);
+ const data = await response.json();
+
+ // object needs to be cooked
+ if (data.exception === 'NotFoundExc' || data.status === 'failed') {
+ // if last cooking has failed, remove previous task info from localStorage
+ // in order to force the recooking of the object
+ swh.vault.removeCookingTaskInfo([objectId]);
+ $(`#vault-cook-${objectType}-modal`).modal('show');
+ // object has been cooked and should be in the vault cache,
+ // it will be asked to cook it again if it is not
+ } else if (data.status === 'done') {
+ $(`#vault-fetch-${objectType}-modal`).modal('show');
+ } else {
+ const cookingServiceDownAlert =
$(htmlAlert('danger',
'Archive cooking service is currently experiencing issues.
' +
'Please try again later.',
true));
- cookingServiceDownAlert.css(alertStyle);
- $('body').append(cookingServiceDownAlert);
- }
- });
+ cookingServiceDownAlert.css(alertStyle);
+ $('body').append(cookingServiceDownAlert);
+ }
}
-function addVaultCookingTask(cookingTask) {
+async function addVaultCookingTask(cookingTask) {
const swhidsContext = swh.webapp.getSwhIdsContext();
cookingTask.origin = swhidsContext[cookingTask.object_type].context.origin;
cookingTask.path = swhidsContext[cookingTask.object_type].context.path;
cookingTask.browse_url = swhidsContext[cookingTask.object_type].swhid_with_context_url;
if (!cookingTask.browse_url) {
cookingTask.browse_url = swhidsContext[cookingTask.object_type].swhid_url;
}
let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
if (!vaultCookingTasks) {
vaultCookingTasks = [];
}
if (vaultCookingTasks.find(val => {
return val.object_type === cookingTask.object_type &&
val.object_id === cookingTask.object_id;
}) === undefined) {
let cookingUrl;
if (cookingTask.object_type === 'directory') {
cookingUrl = Urls.api_1_vault_cook_directory(cookingTask.object_id);
} else {
cookingUrl = Urls.api_1_vault_cook_revision_gitfast(cookingTask.object_id);
}
if (cookingTask.email) {
cookingUrl += '?email=' + cookingTask.email;
}
- csrfPost(cookingUrl)
- .then(handleFetchError)
- .then(() => {
- vaultCookingTasks.push(cookingTask);
- localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
- $('#vault-cook-directory-modal').modal('hide');
- $('#vault-cook-revision-modal').modal('hide');
- const cookingTaskCreatedAlert =
+ try {
+ const response = await csrfPost(cookingUrl);
+ handleFetchError(response);
+ vaultCookingTasks.push(cookingTask);
+ localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
+ $('#vault-cook-directory-modal').modal('hide');
+ $('#vault-cook-revision-modal').modal('hide');
+ const cookingTaskCreatedAlert =
$(htmlAlert('success',
'Archive cooking request successfully submitted.
' +
`Go to the Downloads page ` +
'to get the download link once it is ready.',
true));
- cookingTaskCreatedAlert.css(alertStyle);
- $('body').append(cookingTaskCreatedAlert);
- })
- .catch(() => {
- $('#vault-cook-directory-modal').modal('hide');
- $('#vault-cook-revision-modal').modal('hide');
- const cookingTaskFailedAlert =
+ cookingTaskCreatedAlert.css(alertStyle);
+ $('body').append(cookingTaskCreatedAlert);
+ } catch (_) {
+ $('#vault-cook-directory-modal').modal('hide');
+ $('#vault-cook-revision-modal').modal('hide');
+ const cookingTaskFailedAlert =
$(htmlAlert('danger',
'Archive cooking request submission failed.',
true));
- cookingTaskFailedAlert.css(alertStyle);
- $('body').append(cookingTaskFailedAlert);
- });
+ cookingTaskFailedAlert.css(alertStyle);
+ $('body').append(cookingTaskFailedAlert);
+ }
}
}
function validateEmail(email) {
let re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}
export function cookDirectoryArchive(directoryId) {
let email = $('#swh-vault-directory-email').val().trim();
if (!email || validateEmail(email)) {
let cookingTask = {
'object_type': 'directory',
'object_id': directoryId,
'email': email,
'status': 'new'
};
addVaultCookingTask(cookingTask);
} else {
$('#invalid-email-modal').modal('show');
}
}
-export function fetchDirectoryArchive(directoryId) {
+export async function fetchDirectoryArchive(directoryId) {
$('#vault-fetch-directory-modal').modal('hide');
const vaultUrl = Urls.api_1_vault_cook_directory(directoryId);
- fetch(vaultUrl)
- .then(response => response.json())
- .then(data => {
- swh.vault.fetchCookedObject(data.fetch_url);
- });
+ const response = await fetch(vaultUrl);
+ const data = await response.json();
+ swh.vault.fetchCookedObject(data.fetch_url);
}
export function cookRevisionArchive(revisionId) {
let email = $('#swh-vault-revision-email').val().trim();
if (!email || validateEmail(email)) {
let cookingTask = {
'object_type': 'revision',
'object_id': revisionId,
'email': email,
'status': 'new'
};
addVaultCookingTask(cookingTask);
} else {
$('#invalid-email-modal').modal('show');
}
}
-export function fetchRevisionArchive(revisionId) {
+export async function fetchRevisionArchive(revisionId) {
$('#vault-fetch-directory-modal').modal('hide');
const vaultUrl = Urls.api_1_vault_cook_revision_gitfast(revisionId);
- fetch(vaultUrl)
- .then(response => response.json())
- .then(data => {
- swh.vault.fetchCookedObject(data.fetch_url);
- });
+ const response = await fetch(vaultUrl);
+ const data = await response.json();
+ swh.vault.fetchCookedObject(data.fetch_url);
}
diff --git a/assets/src/bundles/vault/vault-ui.js b/assets/src/bundles/vault/vault-ui.js
index 9d756bc9..22ca093c 100644
--- a/assets/src/bundles/vault/vault-ui.js
+++ b/assets/src/bundles/vault/vault-ui.js
@@ -1,241 +1,241 @@
/**
* Copyright (C) 2018-2019 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import {handleFetchError, handleFetchErrors, csrfPost} from 'utils/functions';
import vaultTableRowTemplate from './vault-table-row.ejs';
let progress =
`;`;
let pollingInterval = 5000;
let checkVaultId;
function updateProgressBar(progressBar, cookingTask) {
if (cookingTask.status === 'new') {
progressBar.css('background-color', 'rgba(128, 128, 128, 0.5)');
} else if (cookingTask.status === 'pending') {
progressBar.css('background-color', 'rgba(0, 0, 255, 0.5)');
} else if (cookingTask.status === 'done') {
progressBar.css('background-color', '#5cb85c');
} else if (cookingTask.status === 'failed') {
progressBar.css('background-color', 'rgba(255, 0, 0, 0.5)');
progressBar.css('background-image', 'none');
}
progressBar.text(cookingTask.progress_message || cookingTask.status);
if (cookingTask.status === 'new' || cookingTask.status === 'pending') {
progressBar.addClass('progress-bar-animated');
} else {
progressBar.removeClass('progress-bar-striped');
}
}
let recookTask;
// called when the user wants to download a cooked archive
-export function fetchCookedObject(fetchUrl) {
+export async function fetchCookedObject(fetchUrl) {
recookTask = null;
// first, check if the link is still available from the vault
- fetch(fetchUrl)
- .then(response => {
- // link is still alive, proceed to download
- if (response.ok) {
- $('#vault-fetch-iframe').attr('src', fetchUrl);
- // link is dead
- } else {
- // get the associated cooking task
- let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
- for (let i = 0; i < vaultCookingTasks.length; ++i) {
- if (vaultCookingTasks[i].fetch_url === fetchUrl) {
- recookTask = vaultCookingTasks[i];
- break;
- }
- }
- // display a modal asking the user if he wants to recook the archive
- $('#vault-recook-object-modal').modal('show');
+ const response = await fetch(fetchUrl);
+
+ // link is still alive, proceed to download
+ if (response.ok) {
+ $('#vault-fetch-iframe').attr('src', fetchUrl);
+ // link is dead
+ } else {
+ // get the associated cooking task
+ let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
+ for (let i = 0; i < vaultCookingTasks.length; ++i) {
+ if (vaultCookingTasks[i].fetch_url === fetchUrl) {
+ recookTask = vaultCookingTasks[i];
+ break;
}
- });
+ }
+ // display a modal asking the user if he wants to recook the archive
+ $('#vault-recook-object-modal').modal('show');
+ }
}
// called when the user wants to recook an archive
// for which the download link is not available anymore
-export function recookObject() {
+export async function recookObject() {
if (recookTask) {
// stop cooking tasks status polling
clearTimeout(checkVaultId);
// build cook request url
let cookingUrl;
if (recookTask.object_type === 'directory') {
cookingUrl = Urls.api_1_vault_cook_directory(recookTask.object_id);
} else {
cookingUrl = Urls.api_1_vault_cook_revision_gitfast(recookTask.object_id);
}
if (recookTask.email) {
cookingUrl += '?email=' + recookTask.email;
}
+ try {
// request archive cooking
- csrfPost(cookingUrl)
- .then(handleFetchError)
- .then(() => {
- // update task status
- recookTask.status = 'new';
- let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
- for (let i = 0; i < vaultCookingTasks.length; ++i) {
- if (vaultCookingTasks[i].object_id === recookTask.object_id) {
- vaultCookingTasks[i] = recookTask;
- break;
- }
+ const response = await csrfPost(cookingUrl);
+ handleFetchError(response);
+
+ // update task status
+ recookTask.status = 'new';
+ let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
+ for (let i = 0; i < vaultCookingTasks.length; ++i) {
+ if (vaultCookingTasks[i].object_id === recookTask.object_id) {
+ vaultCookingTasks[i] = recookTask;
+ break;
}
- // save updated tasks to local storage
- localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
- // restart cooking tasks status polling
- checkVaultCookingTasks();
- // hide recook archive modal
- $('#vault-recook-object-modal').modal('hide');
- })
+ }
+ // save updated tasks to local storage
+ localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
+ // restart cooking tasks status polling
+ checkVaultCookingTasks();
+ // hide recook archive modal
+ $('#vault-recook-object-modal').modal('hide');
+ } catch (_) {
// something went wrong
- .catch(() => {
- checkVaultCookingTasks();
- $('#vault-recook-object-modal').modal('hide');
- });
+ checkVaultCookingTasks();
+ $('#vault-recook-object-modal').modal('hide');
+ }
}
}
-function checkVaultCookingTasks() {
+async function checkVaultCookingTasks() {
let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
if (!vaultCookingTasks || vaultCookingTasks.length === 0) {
$('.swh-vault-table tbody tr').remove();
checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval);
return;
}
let cookingTaskRequests = [];
let tasks = {};
let currentObjectIds = [];
for (let i = 0; i < vaultCookingTasks.length; ++i) {
let cookingTask = vaultCookingTasks[i];
currentObjectIds.push(cookingTask.object_id);
tasks[cookingTask.object_id] = cookingTask;
let cookingUrl;
if (cookingTask.object_type === 'directory') {
cookingUrl = Urls.api_1_vault_cook_directory(cookingTask.object_id);
} else {
cookingUrl = Urls.api_1_vault_cook_revision_gitfast(cookingTask.object_id);
}
if (cookingTask.status !== 'done' && cookingTask.status !== 'failed') {
cookingTaskRequests.push(fetch(cookingUrl));
}
}
$('.swh-vault-table tbody tr').each((i, row) => {
let objectId = $(row).find('.vault-object-info').data('object-id');
if ($.inArray(objectId, currentObjectIds) === -1) {
$(row).remove();
}
});
- Promise.all(cookingTaskRequests)
- .then(handleFetchErrors)
- .then(responses => Promise.all(responses.map(r => r.json())))
- .then(cookingTasks => {
- let table = $('#vault-cooking-tasks tbody');
- for (let i = 0; i < cookingTasks.length; ++i) {
- let cookingTask = tasks[cookingTasks[i].obj_id];
- cookingTask.status = cookingTasks[i].status;
- cookingTask.fetch_url = cookingTasks[i].fetch_url;
- cookingTask.progress_message = cookingTasks[i].progress_message;
- }
- for (let i = 0; i < vaultCookingTasks.length; ++i) {
- let cookingTask = vaultCookingTasks[i];
- let rowTask = $(`#vault-task-${cookingTask.object_id}`);
-
- if (!rowTask.length) {
-
- let browseUrl = cookingTask.browse_url;
- if (!browseUrl) {
- if (cookingTask.object_type === 'directory') {
- browseUrl = Urls.browse_directory(cookingTask.object_id);
- } else {
- browseUrl = Urls.browse_revision(cookingTask.object_id);
- }
+ try {
+ const responses = await Promise.all(cookingTaskRequests);
+ handleFetchErrors(responses);
+ const cookingTasks = await Promise.all(responses.map(r => r.json()));
+
+ let table = $('#vault-cooking-tasks tbody');
+ for (let i = 0; i < cookingTasks.length; ++i) {
+ let cookingTask = tasks[cookingTasks[i].obj_id];
+ cookingTask.status = cookingTasks[i].status;
+ cookingTask.fetch_url = cookingTasks[i].fetch_url;
+ cookingTask.progress_message = cookingTasks[i].progress_message;
+ }
+ for (let i = 0; i < vaultCookingTasks.length; ++i) {
+ let cookingTask = vaultCookingTasks[i];
+ let rowTask = $(`#vault-task-${cookingTask.object_id}`);
+
+ if (!rowTask.length) {
+
+ let browseUrl = cookingTask.browse_url;
+ if (!browseUrl) {
+ if (cookingTask.object_type === 'directory') {
+ browseUrl = Urls.browse_directory(cookingTask.object_id);
+ } else {
+ browseUrl = Urls.browse_revision(cookingTask.object_id);
}
+ }
- let progressBar = $.parseHTML(progress)[0];
- let progressBarContent = $(progressBar).find('.progress-bar');
- updateProgressBar(progressBarContent, cookingTask);
- table.prepend(vaultTableRowTemplate({
- browseUrl: browseUrl,
- cookingTask: cookingTask,
- progressBar: progressBar,
- Urls: Urls,
- swh: swh
- }));
- } else {
- let progressBar = rowTask.find('.progress-bar');
- updateProgressBar(progressBar, cookingTask);
- let downloadLink = rowTask.find('.vault-dl-link');
- if (cookingTask.status === 'done') {
- downloadLink[0].innerHTML =
+ let progressBar = $.parseHTML(progress)[0];
+ let progressBarContent = $(progressBar).find('.progress-bar');
+ updateProgressBar(progressBarContent, cookingTask);
+ table.prepend(vaultTableRowTemplate({
+ browseUrl: browseUrl,
+ cookingTask: cookingTask,
+ progressBar: progressBar,
+ Urls: Urls,
+ swh: swh
+ }));
+ } else {
+ let progressBar = rowTask.find('.progress-bar');
+ updateProgressBar(progressBar, cookingTask);
+ let downloadLink = rowTask.find('.vault-dl-link');
+ if (cookingTask.status === 'done') {
+ downloadLink[0].innerHTML =
'';
- } else {
- downloadLink[0].innerHTML = '';
- }
+ } else {
+ downloadLink[0].innerHTML = '';
}
}
- localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
- checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval);
- })
- .catch(error => {
- console.log('Error when fetching vault cooking tasks:', error);
- });
+ }
+ localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
+ checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval);
+
+ } catch (error) {
+ console.log('Error when fetching vault cooking tasks:', error);
+ }
}
export function removeCookingTaskInfo(tasksToRemove) {
let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
if (!vaultCookingTasks) {
return;
}
vaultCookingTasks = $.grep(vaultCookingTasks, task => {
return $.inArray(task.object_id, tasksToRemove) === -1;
});
localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
}
export function initUi() {
$('#vault-tasks-toggle-selection').change(event => {
$('.vault-task-toggle-selection').prop('checked', event.currentTarget.checked);
});
$('#vault-remove-tasks').click(() => {
clearTimeout(checkVaultId);
let tasksToRemove = [];
$('.swh-vault-table tbody tr').each((i, row) => {
let taskSelected = $(row).find('.vault-task-toggle-selection').prop('checked');
if (taskSelected) {
let objectId = $(row).find('.vault-object-info').data('object-id');
tasksToRemove.push(objectId);
$(row).remove();
}
});
removeCookingTaskInfo(tasksToRemove);
$('#vault-tasks-toggle-selection').prop('checked', false);
checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval);
});
checkVaultCookingTasks();
window.onfocus = () => {
clearTimeout(checkVaultId);
checkVaultCookingTasks();
};
}
diff --git a/assets/src/bundles/webapp/notebook-rendering.js b/assets/src/bundles/webapp/notebook-rendering.js
index fce6d48e..6bc1d3d1 100644
--- a/assets/src/bundles/webapp/notebook-rendering.js
+++ b/assets/src/bundles/webapp/notebook-rendering.js
@@ -1,137 +1,136 @@
/**
* Copyright (C) 2019-2020 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import 'script-loader!notebookjs';
import AnsiUp from 'ansi_up';
import './notebook.css';
const ansiup = new AnsiUp();
ansiup.escape_for_html = false;
function escapeHTML(text) {
text = text.replace(//g, '>');
return text;
}
function unescapeHTML(text) {
text = text.replace(/</g, '<');
text = text.replace(/>/g, '>');
return text;
}
function escapeLaTeX(text) {
let blockMath = /\$\$(.+?)\$\$|\\\\\[(.+?)\\\\\]/msg;
let inlineMath = /\$(.+?)\$|\\\\\((.+?)\\\\\)/g;
let latexEnvironment = /\\begin\{([a-z]*\*?)\}(.+?)\\end\{\1\}/msg;
let mathTextFound = [];
let bm;
while ((bm = blockMath.exec(text)) !== null) {
mathTextFound.push(bm[1]);
}
let im;
while ((im = inlineMath.exec(text)) !== null) {
mathTextFound.push(im[1]);
}
let le;
while ((le = latexEnvironment.exec(text)) !== null) {
mathTextFound.push(le[1]);
}
for (let mathText of mathTextFound) {
// showdown will remove line breaks in LaTex array and
// some escaping sequences when converting md to html.
// So we use the following escaping hacks to keep them in the html
// output and avoid MathJax typesetting errors.
let escapedText = mathText.replace('\\\\', '\\\\\\\\');
for (let specialLaTexChar of ['{', '}', '#', '%', '&', '_']) {
escapedText = escapedText.replace(new RegExp(`\\\\${specialLaTexChar}`, 'g'),
`\\\\${specialLaTexChar}`);
}
// some html escaping is also needed
escapedText = escapeHTML(escapedText);
// hack to prevent showdown to replace _ characters
// by html em tags as it will break some math typesetting
// (setting the literalMidWordUnderscores option is not
// enough as iy only works for _ characters contained in words)
escapedText = escapedText.replace(/_/g, '{@}underscore{@}');
if (mathText !== escapedText) {
text = text.replace(mathText, escapedText);
}
}
return text;
}
export async function renderNotebook(nbJsonUrl, domElt) {
let showdown = await import(/* webpackChunkName: "showdown" */ 'utils/showdown');
await import(/* webpackChunkName: "highlightjs" */ 'utils/highlightjs');
function renderMarkdown(text) {
let converter = new showdown.Converter({
tables: true,
simplifiedAutoLink: true,
rawHeaderId: true,
literalMidWordUnderscores: true
});
// some LaTeX escaping is required to get correct math typesetting
text = escapeLaTeX(text);
// render markdown
let rendered = converter.makeHtml(text);
// restore underscores in rendered HTML (see escapeLaTeX function)
rendered = rendered.replace(/{@}underscore{@}/g, '_');
return rendered;
}
function highlightCode(text, preElt, codeElt, lang) {
// no need to unescape text processed by ansiup
if (text.indexOf(' {
+ $(document).ready(async() => {
$('#pdf-prev').click(onPrevPage);
$('#pdf-next').click(onNextPage);
- let loadingTask = pdfjs.getDocument(pdfUrl);
- loadingTask.promise.then(pdf => {
+ try {
+ const pdf = await pdfjs.getDocument(pdfUrl).promise;
pdfDoc = pdf;
$('#pdf-page-count').text(pdfDoc.numPages);
// Initial/first page rendering
renderPage(pageNum);
- }, function(reason) {
+ } catch (reason) {
// PDF loading error
console.error(reason);
- });
+ }
// Render PDF on resize
$(window).on('resize', function() {
queueRenderPage(pageNum);
});
});
}
diff --git a/assets/src/bundles/webapp/readme-rendering.js b/assets/src/bundles/webapp/readme-rendering.js
index 88f048ab..2651f6c7 100644
--- a/assets/src/bundles/webapp/readme-rendering.js
+++ b/assets/src/bundles/webapp/readme-rendering.js
@@ -1,123 +1,118 @@
/**
- * Copyright (C) 2018-2019 The Software Heritage developers
+ * Copyright (C) 2018-2021 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import {handleFetchError} from 'utils/functions';
import {decode} from 'html-encoder-decoder';
export async function renderMarkdown(domElt, markdownDocUrl) {
let showdown = await import(/* webpackChunkName: "showdown" */ 'utils/showdown');
await import(/* webpackChunkName: "highlightjs" */ 'utils/highlightjs');
// Adapted from https://github.com/Bloggify/showdown-highlight
// Copyright (c) 2016-19 Bloggify (https://bloggify.org)
function showdownHighlight() {
return [{
type: 'output',
filter: function(text, converter, options) {
let left = ']*>';
let right = '
';
let flags = 'g';
let classAttr = 'class="';
let replacement = (wholeMatch, match, left, right) => {
match = decode(match);
let lang = (left.match(/class="([^ "]+)/) || [])[1];
if (left.includes(classAttr)) {
let attrIndex = left.indexOf(classAttr) + classAttr.length;
left = left.slice(0, attrIndex) + 'hljs ' + left.slice(attrIndex);
} else {
left = left.slice(0, -1) + ' class="hljs">';
}
if (lang && hljs.getLanguage(lang)) {
return left + hljs.highlight(match, {language: lang}).value + right;
} else {
return left + match + right;
}
};
return showdown.helper.replaceRecursiveRegExp(text, replacement, left, right, flags);
}
}];
}
- $(document).ready(() => {
+ $(document).ready(async() => {
let converter = new showdown.Converter({
tables: true,
extensions: [showdownHighlight]
});
- fetch(markdownDocUrl)
- .then(handleFetchError)
- .then(response => response.text())
- .then(data => {
- $(domElt).addClass('swh-showdown');
- $(domElt).html(swh.webapp.filterXSS(converter.makeHtml(data)));
- })
- .catch(() => {
- $(domElt).text('Readme bytes are not available');
- });
+
+ try {
+ const response = await fetch(markdownDocUrl);
+ handleFetchError(response);
+ const data = await response.text();
+ $(domElt).addClass('swh-showdown');
+ $(domElt).html(swh.webapp.filterXSS(converter.makeHtml(data)));
+ } catch (_) {
+ $(domElt).text('Readme bytes are not available');
+ }
});
}
export async function renderOrgData(domElt, orgDocData) {
let org = await import(/* webpackChunkName: "org" */ 'utils/org');
let parser = new org.Parser();
let orgDocument = parser.parse(orgDocData, {toc: false});
let orgHTMLDocument = orgDocument.convert(org.ConverterHTML, {});
$(domElt).addClass('swh-org');
$(domElt).html(swh.webapp.filterXSS(orgHTMLDocument.toString()));
// remove toc and section numbers to get consistent
// with other readme renderings
$('.swh-org ul').first().remove();
$('.section-number').remove();
}
export function renderOrg(domElt, orgDocUrl) {
-
- $(document).ready(() => {
- fetch(orgDocUrl)
- .then(handleFetchError)
- .then(response => response.text())
- .then(data => {
- renderOrgData(domElt, data);
- })
- .catch(() => {
- $(domElt).text('Readme bytes are not available');
- });
+ $(document).ready(async() => {
+ try {
+ const response = await fetch(orgDocUrl);
+ handleFetchError(response);
+ const data = await response.text();
+ renderOrgData(domElt, data);
+ } catch (_) {
+ $(domElt).text('Readme bytes are not available');
+ }
});
-
}
export function renderTxt(domElt, txtDocUrl) {
-
- $(document).ready(() => {
- fetch(txtDocUrl)
- .then(handleFetchError)
- .then(response => response.text())
- .then(data => {
- let orgMode = '-*- mode: org -*-';
- if (data.indexOf(orgMode) !== -1) {
- renderOrgData(domElt, data.replace(orgMode, ''));
- } else {
- $(domElt).addClass('swh-readme-txt');
- $(domElt)
+ $(document).ready(async() => {
+ try {
+ const response = await fetch(txtDocUrl);
+ handleFetchError(response);
+ const data = await response.text();
+
+ let orgMode = '-*- mode: org -*-';
+ if (data.indexOf(orgMode) !== -1) {
+ renderOrgData(domElt, data.replace(orgMode, ''));
+ } else {
+ $(domElt).addClass('swh-readme-txt');
+ $(domElt)
.html('')
.append($('').text(data));
- }
- })
- .catch(() => {
- $(domElt).text('Readme bytes are not available');
- });
+ }
+ } catch (_) {
+ $(domElt).text('Readme bytes are not available');
+ }
});
-
}
diff --git a/assets/src/bundles/webapp/status-widget.js b/assets/src/bundles/webapp/status-widget.js
index 5f28d9de..7eb31b3b 100644
--- a/assets/src/bundles/webapp/status-widget.js
+++ b/assets/src/bundles/webapp/status-widget.js
@@ -1,50 +1,50 @@
/**
* Copyright (C) 2020 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import './status-widget.css';
const statusCodeColor = {
'100': 'green', // Operational
'200': 'blue', // Scheduled Maintenance
'300': 'yellow', // Degraded Performance
'400': 'yellow', // Partial Service Disruption
'500': 'red', // Service Disruption
'600': 'red' // Security Event
};
export function initStatusWidget(statusDataURL) {
- $('.swh-current-status-indicator').ready(() => {
+ $('.swh-current-status-indicator').ready(async() => {
let maxStatusCode = '';
let maxStatusDescription = '';
let sc = '';
let sd = '';
- fetch(statusDataURL)
- .then(resp => resp.json())
- .then(data => {
- for (let s of data.result.status) {
- sc = s.status_code;
- sd = s.status;
- if (maxStatusCode < sc) {
- maxStatusCode = sc;
- maxStatusDescription = sd;
- }
- }
- if (maxStatusCode === '') {
- $('.swh-current-status').remove();
- return;
+ try {
+ const response = await fetch(statusDataURL);
+ const data = await response.json();
+
+ for (let s of data.result.status) {
+ sc = s.status_code;
+ sd = s.status;
+ if (maxStatusCode < sc) {
+ maxStatusCode = sc;
+ maxStatusDescription = sd;
}
- $('.swh-current-status-indicator').removeClass('green');
- $('.swh-current-status-indicator').addClass(statusCodeColor[maxStatusCode]);
- $('#swh-current-status-description').text(maxStatusDescription);
- })
- .catch(e => {
- console.log(e);
+ }
+ if (maxStatusCode === '') {
$('.swh-current-status').remove();
- });
+ return;
+ }
+ $('.swh-current-status-indicator').removeClass('green');
+ $('.swh-current-status-indicator').addClass(statusCodeColor[maxStatusCode]);
+ $('#swh-current-status-description').text(maxStatusDescription);
+ } catch (e) {
+ console.log(e);
+ $('.swh-current-status').remove();
+ }
});
}
diff --git a/assets/src/bundles/webapp/webapp-utils.js b/assets/src/bundles/webapp/webapp-utils.js
index db762941..797a2469 100644
--- a/assets/src/bundles/webapp/webapp-utils.js
+++ b/assets/src/bundles/webapp/webapp-utils.js
@@ -1,401 +1,400 @@
/**
* Copyright (C) 2018-2021 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import objectFitImages from 'object-fit-images';
import {selectText} from 'utils/functions';
import {BREAKPOINT_MD} from 'utils/constants';
let collapseSidebar = false;
let previousSidebarState = localStorage.getItem('remember.lte.pushmenu');
if (previousSidebarState !== undefined) {
collapseSidebar = previousSidebarState === 'sidebar-collapse';
}
$(document).on('DOMContentLoaded', () => {
// set state to collapsed on smaller devices
if ($(window).width() < BREAKPOINT_MD) {
collapseSidebar = true;
}
// restore previous sidebar state (collapsed/expanded)
if (collapseSidebar) {
// hack to avoid animated transition for collapsing sidebar
// when loading a page
let sidebarTransition = $('.main-sidebar, .main-sidebar:before').css('transition');
let sidebarEltsTransition = $('.sidebar .nav-link p, .main-sidebar .brand-text, .sidebar .user-panel .info').css('transition');
$('.main-sidebar, .main-sidebar:before').css('transition', 'none');
$('.sidebar .nav-link p, .main-sidebar .brand-text, .sidebar .user-panel .info').css('transition', 'none');
$('body').addClass('sidebar-collapse');
$('.swh-words-logo-swh').css('visibility', 'visible');
// restore transitions for user navigation
setTimeout(() => {
$('.main-sidebar, .main-sidebar:before').css('transition', sidebarTransition);
$('.sidebar .nav-link p, .main-sidebar .brand-text, .sidebar .user-panel .info').css('transition', sidebarEltsTransition);
});
}
});
$(document).on('collapsed.lte.pushmenu', event => {
if ($('body').width() >= BREAKPOINT_MD) {
$('.swh-words-logo-swh').css('visibility', 'visible');
}
});
$(document).on('shown.lte.pushmenu', event => {
$('.swh-words-logo-swh').css('visibility', 'hidden');
});
function ensureNoFooterOverflow() {
$('body').css('padding-bottom', $('footer').outerHeight() + 'px');
}
$(document).ready(() => {
// redirect to last browse page if any when clicking on the 'Browse' entry
// in the sidebar
$(`.swh-browse-link`).click(event => {
let lastBrowsePage = sessionStorage.getItem('last-browse-page');
if (lastBrowsePage) {
event.preventDefault();
window.location = lastBrowsePage;
}
});
const mainSideBar = $('.main-sidebar');
function updateSidebarState() {
const body = $('body');
if (body.hasClass('sidebar-collapse') &&
!mainSideBar.hasClass('swh-sidebar-collapsed')) {
mainSideBar.removeClass('swh-sidebar-expanded');
mainSideBar.addClass('swh-sidebar-collapsed');
$('.swh-words-logo-swh').css('visibility', 'visible');
} else if (!body.hasClass('sidebar-collapse') &&
!mainSideBar.hasClass('swh-sidebar-expanded')) {
mainSideBar.removeClass('swh-sidebar-collapsed');
mainSideBar.addClass('swh-sidebar-expanded');
$('.swh-words-logo-swh').css('visibility', 'hidden');
}
// ensure correct sidebar state when loading a page
if (body.hasClass('hold-transition')) {
setTimeout(() => {
updateSidebarState();
});
}
}
// set sidebar state after collapse / expand animation
mainSideBar.on('transitionend', evt => {
updateSidebarState();
});
updateSidebarState();
// ensure footer do not overflow main content for mobile devices
// or after resizing the browser window
ensureNoFooterOverflow();
$(window).resize(function() {
ensureNoFooterOverflow();
if ($('body').hasClass('sidebar-collapse') && $('body').width() >= BREAKPOINT_MD) {
$('.swh-words-logo-swh').css('visibility', 'visible');
}
});
// activate css polyfill 'object-fit: contain' in old browsers
objectFitImages();
// reparent the modals to the top navigation div in order to be able
// to display them
$('.swh-browse-top-navigation').append($('.modal'));
let selectedCode = null;
function getCodeOrPreEltUnderPointer(e) {
let elts = document.elementsFromPoint(e.clientX, e.clientY);
for (let elt of elts) {
if (elt.nodeName === 'CODE' || elt.nodeName === 'PRE') {
return elt;
}
}
return null;
}
// click handler to set focus on code block for copy
$(document).click(e => {
selectedCode = getCodeOrPreEltUnderPointer(e);
});
function selectCode(event, selectedCode) {
if (selectedCode) {
let hljsLnCodeElts = $(selectedCode).find('.hljs-ln-code');
if (hljsLnCodeElts.length) {
selectText(hljsLnCodeElts[0], hljsLnCodeElts[hljsLnCodeElts.length - 1]);
} else {
selectText(selectedCode.firstChild, selectedCode.lastChild);
}
event.preventDefault();
}
}
// select the whole text of focused code block when user
// double clicks or hits Ctrl+A
$(document).dblclick(e => {
if ((e.ctrlKey || e.metaKey)) {
selectCode(e, getCodeOrPreEltUnderPointer(e));
}
});
$(document).keydown(e => {
if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
selectCode(e, selectedCode);
}
});
// show/hide back-to-top button
let scrollThreshold = 0;
scrollThreshold += $('.swh-top-bar').height() || 0;
scrollThreshold += $('.navbar').height() || 0;
$(window).scroll(() => {
if ($(window).scrollTop() > scrollThreshold) {
$('#back-to-top').css('display', 'block');
} else {
$('#back-to-top').css('display', 'none');
}
});
// navbar search form submission callback
$('#swh-origins-search-top').submit(event => {
event.preventDefault();
if (event.target.checkValidity()) {
$(event.target).removeClass('was-validated');
let searchQueryText = $('#swh-origins-search-top-input').val().trim();
let queryParameters = new URLSearchParams();
queryParameters.append('q', searchQueryText);
queryParameters.append('with_visit', true);
queryParameters.append('with_content', true);
window.location = `${Urls.browse_search()}?${queryParameters.toString()}`;
} else {
$(event.target).addClass('was-validated');
}
});
});
export function initPage(page) {
$(document).ready(() => {
// set relevant sidebar link to page active
$(`.swh-${page}-item`).addClass('active');
$(`.swh-${page}-link`).addClass('active');
// triggered when unloading the current page
$(window).on('unload', () => {
// backup current browse page
if (page === 'browse') {
sessionStorage.setItem('last-browse-page', window.location);
}
});
});
}
export function initHomePage() {
- $(document).ready(() => {
+ $(document).ready(async() => {
$('.swh-coverage-list').iFrameResize({heightCalculationMethod: 'taggedElement'});
- fetch(Urls.stat_counters())
- .then(response => response.json())
- .then(data => {
- if (data.stat_counters && !$.isEmptyObject(data.stat_counters)) {
- for (let objectType of ['content', 'revision', 'origin', 'directory', 'person', 'release']) {
- const count = data.stat_counters[objectType];
- if (count !== undefined) {
- $(`#swh-${objectType}-count`).html(count.toLocaleString());
- } else {
- $(`#swh-${objectType}-count`).closest('.swh-counter-container').hide();
- }
- }
+ const response = await fetch(Urls.stat_counters());
+ const data = await response.json();
+
+ if (data.stat_counters && !$.isEmptyObject(data.stat_counters)) {
+ for (let objectType of ['content', 'revision', 'origin', 'directory', 'person', 'release']) {
+ const count = data.stat_counters[objectType];
+ if (count !== undefined) {
+ $(`#swh-${objectType}-count`).html(count.toLocaleString());
} else {
- $('.swh-counter').html('0');
+ $(`#swh-${objectType}-count`).closest('.swh-counter-container').hide();
}
- if (data.stat_counters_history && !$.isEmptyObject(data.stat_counters_history)) {
- for (let objectType of ['content', 'revision', 'origin']) {
- const history = data.stat_counters_history[objectType];
- if (history) {
- swh.webapp.drawHistoryCounterGraph(`#swh-${objectType}-count-history`, history);
- } else {
- $(`#swh-${objectType}-count-history`).hide();
- }
-
- }
+ }
+ } else {
+ $('.swh-counter').html('0');
+ }
+ if (data.stat_counters_history && !$.isEmptyObject(data.stat_counters_history)) {
+ for (let objectType of ['content', 'revision', 'origin']) {
+ const history = data.stat_counters_history[objectType];
+ if (history) {
+ swh.webapp.drawHistoryCounterGraph(`#swh-${objectType}-count-history`, history);
} else {
- $('.swh-counter-history').hide();
+ $(`#swh-${objectType}-count-history`).hide();
}
- });
+
+ }
+ } else {
+ $('.swh-counter-history').hide();
+ }
});
initPage('home');
}
export function showModalMessage(title, message) {
$('#swh-web-modal-message .modal-title').text(title);
$('#swh-web-modal-message .modal-content p').text(message);
$('#swh-web-modal-message').modal('show');
}
export function showModalConfirm(title, message, callback) {
$('#swh-web-modal-confirm .modal-title').text(title);
$('#swh-web-modal-confirm .modal-content p').text(message);
$('#swh-web-modal-confirm #swh-web-modal-confirm-ok-btn').bind('click', () => {
callback();
$('#swh-web-modal-confirm').modal('hide');
$('#swh-web-modal-confirm #swh-web-modal-confirm-ok-btn').unbind('click');
});
$('#swh-web-modal-confirm').modal('show');
}
export function showModalHtml(title, html) {
$('#swh-web-modal-html .modal-title').text(title);
$('#swh-web-modal-html .modal-body').html(html);
$('#swh-web-modal-html').modal('show');
}
export function addJumpToPagePopoverToDataTable(dataTableElt) {
dataTableElt.on('draw.dt', function() {
$('.paginate_button.disabled').css('cursor', 'pointer');
$('.paginate_button.disabled').on('click', event => {
const pageInfo = dataTableElt.page.info();
let content = ' / ${pageInfo.pages}`;
$(event.target).popover({
'title': 'Jump to page',
'content': content,
'html': true,
'placement': 'top',
'sanitizeFn': swh.webapp.filterXSS
});
$(event.target).popover('show');
$('.jump-to-page').on('change', function() {
$('.paginate_button.disabled').popover('hide');
const pageNumber = parseInt($(this).val()) - 1;
dataTableElt.page(pageNumber).draw('page');
});
});
});
dataTableElt.on('preXhr.dt', () => {
$('.paginate_button.disabled').popover('hide');
});
}
let swhObjectIcons;
export function setSwhObjectIcons(icons) {
swhObjectIcons = icons;
}
export function getSwhObjectIcon(swhObjectType) {
return swhObjectIcons[swhObjectType];
}
let browsedSwhObjectMetadata = {};
export function setBrowsedSwhObjectMetadata(metadata) {
browsedSwhObjectMetadata = metadata;
}
export function getBrowsedSwhObjectMetadata() {
return browsedSwhObjectMetadata;
}
// This will contain a mapping between an archived object type
// and its related SWHID metadata for each object reachable from
// the current browse view.
// SWHID metadata contain the following keys:
// * object_type: type of archived object
// * object_id: sha1 object identifier
// * swhid: SWHID without contextual info
// * swhid_url: URL to resolve SWHID without contextual info
// * context: object describing SWHID context
// * swhid_with_context: SWHID with contextual info
// * swhid_with_context_url: URL to resolve SWHID with contextual info
let swhidsContext_ = {};
export function setSwhIdsContext(swhidsContext) {
swhidsContext_ = {};
for (let swhidContext of swhidsContext) {
swhidsContext_[swhidContext.object_type] = swhidContext;
}
}
export function getSwhIdsContext() {
return swhidsContext_;
}
function setFullWidth(fullWidth) {
if (fullWidth) {
$('#swh-web-content').removeClass('container');
$('#swh-web-content').addClass('container-fluid');
} else {
$('#swh-web-content').removeClass('container-fluid');
$('#swh-web-content').addClass('container');
}
localStorage.setItem('swh-web-full-width', JSON.stringify(fullWidth));
$('#swh-full-width-switch').prop('checked', fullWidth);
}
export function fullWidthToggled(event) {
setFullWidth($(event.target).prop('checked'));
}
export function setContainerFullWidth() {
let previousFullWidthState = JSON.parse(localStorage.getItem('swh-web-full-width'));
if (previousFullWidthState !== null) {
setFullWidth(previousFullWidthState);
}
}
function coreSWHIDIsLowerCase(swhid) {
const qualifiersPos = swhid.indexOf(';');
let coreSWHID = swhid;
if (qualifiersPos !== -1) {
coreSWHID = swhid.slice(0, qualifiersPos);
}
return coreSWHID.toLowerCase() === coreSWHID;
}
export async function validateSWHIDInput(swhidInputElt) {
const swhidInput = swhidInputElt.value.trim();
let customValidity = '';
if (swhidInput.toLowerCase().startsWith('swh:')) {
if (coreSWHIDIsLowerCase(swhidInput)) {
const resolveSWHIDUrl = Urls.api_1_resolve_swhid(swhidInput);
const response = await fetch(resolveSWHIDUrl);
const responseData = await response.json();
if (responseData.hasOwnProperty('exception')) {
customValidity = responseData.reason;
}
} else {
const qualifiersPos = swhidInput.indexOf(';');
if (qualifiersPos === -1) {
customValidity = 'Invalid SWHID: all characters must be in lowercase. ';
customValidity += `Valid SWHID is ${swhidInput.toLowerCase()}`;
} else {
customValidity = 'Invalid SWHID: the core part must be in lowercase. ';
const coreSWHID = swhidInput.slice(0, qualifiersPos);
customValidity += `Valid SWHID is ${swhidInput.replace(coreSWHID, coreSWHID.toLowerCase())}`;
}
}
}
swhidInputElt.setCustomValidity(customValidity);
$(swhidInputElt).siblings('.invalid-feedback').text(customValidity);
}
export function isUserLoggedIn() {
return JSON.parse($('#swh_user_logged_in').text());
}