diff --git a/swh/web/assets/src/bundles/browse/browse-utils.js b/swh/web/assets/src/bundles/browse/browse-utils.js new file mode 100644 index 000000000..f5b2d7da3 --- /dev/null +++ b/swh/web/assets/src/bundles/browse/browse-utils.js @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2018 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 + */ + +export function initBrowse(page) { + + $(document).ready(() => { + + $('.dropdown-submenu a.dropdown-item').on('click', e => { + $(e.target).next('div').toggle(); + if ($(e.target).next('div').css('display') !== 'none') { + $(e.target).focus(); + } else { + $(e.target).blur(); + } + e.stopPropagation(); + e.preventDefault(); + }); + + $(`.browse-${page}-item`).addClass('active'); + $(`.browse-${page}-link`).addClass('active'); + + $(`.browse-main-link`).click(event => { + let lastBrowsePage = sessionStorage.getItem('last-browse-page'); + if (lastBrowsePage) { + event.preventDefault(); + window.location = lastBrowsePage; + } + }); + + window.onunload = () => { + if (page === 'main') { + sessionStorage.setItem('last-browse-page', window.location); + } + }; + + }); + +} diff --git a/swh/web/assets/src/bundles/browse/index.js b/swh/web/assets/src/bundles/browse/index.js index e03d15f7c..1639c1fb0 100644 --- a/swh/web/assets/src/bundles/browse/index.js +++ b/swh/web/assets/src/bundles/browse/index.js @@ -1,17 +1,17 @@ /** * Copyright (C) 2018 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 */ // main bundle for the swh-web/browse application import './browse.css'; import './breadcrumbs.css'; import './content.css'; import './snapshot-navigation.css'; export * from './snapshot-navigation'; export * from './origin-search'; -export * from './main-navigation'; +export * from './browse-utils'; diff --git a/swh/web/assets/src/bundles/browse/main-navigation.js b/swh/web/assets/src/bundles/browse/main-navigation.js deleted file mode 100644 index 14420c5f3..000000000 --- a/swh/web/assets/src/bundles/browse/main-navigation.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (C) 2018 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 - */ - -export let browseTabsHash = ['#browse', '#search', '#help', '#vault']; - -export function removeHash() { - history.replaceState('', document.title, window.location.pathname + window.location.search); -} - -export function showTab(hash) { - $('.navbar-nav.swh-browse-nav a[href="' + hash + '"]').tab('show'); - window.scrollTo(0, 0); -} - -export function showRequestedTab() { - let hash = window.location.hash; - if (hash && browseTabsHash.indexOf(hash) === -1) { - return; - } - if (hash) { - showTab(hash); - } else { - showTab('#browse'); - } -} - -export function initMainNavigation() { - - $(document).ready(() => { - - $('.dropdown-submenu a.dropdown-item').on('click', e => { - $(e.target).next('div').toggle(); - if ($(e.target).next('div').css('display') !== 'none') { - $(e.target).focus(); - } else { - $(e.target).blur(); - } - e.stopPropagation(); - e.preventDefault(); - }); - - // Change hash for page reload - $('.navbar-nav.swh-browse-nav a').on('shown.bs.tab', e => { - if (e.target.hash.trim() !== '#browse') { - window.location.hash = e.target.hash; - } else { - let hash = window.location.hash; - if (browseTabsHash.indexOf(hash) !== -1) { - removeHash(); - } - } - showRequestedTab(); - }); - - // update displayed tab when the url fragment changes - $(window).on('hashchange', () => { - showRequestedTab(); - }); - - // show requested tab when loading the page - showRequestedTab(); - }); - -} diff --git a/swh/web/assets/src/bundles/vault/vault-create-tasks.js b/swh/web/assets/src/bundles/vault/vault-create-tasks.js index 0045793c6..a9d7ddf3e 100644 --- a/swh/web/assets/src/bundles/vault/vault-create-tasks.js +++ b/swh/web/assets/src/bundles/vault/vault-create-tasks.js @@ -1,90 +1,90 @@ /** * Copyright (C) 2018 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'; function addVaultCookingTask(cookingTask) { 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.vault_cook_directory(cookingTask.object_id); } else { cookingUrl = Urls.vault_cook_revision_gitfast(cookingTask.object_id); } if (cookingTask.email) { cookingUrl += '?email=' + cookingTask.email; } fetch(cookingUrl, {credentials: 'same-origin', method: 'POST'}) .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'); - swh.browse.showTab('#vault'); + window.location = Urls.browse_vault(); }) .catch(() => { $('#vault-cook-directory-modal').modal('hide'); $('#vault-cook-revision-modal').modal('hide'); }); } else { - swh.browse.showTab('#vault'); + window.location = Urls.browse_vault(); } } 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 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 initTaskCreationUi() { // reparent the modals to the top navigation div in order to be able // to display them $(document).ready(function() { $('.swh-browse-top-navigation').append($('#vault-cook-directory-modal')); $('.swh-browse-top-navigation').append($('#vault-cook-revision-modal')); $('.swh-browse-top-navigation').append($('#invalid-email-modal')); }); } diff --git a/swh/web/assets/src/bundles/webapp/code-highlighting.js b/swh/web/assets/src/bundles/webapp/code-highlighting.js index 872a52739..424f09ed1 100644 --- a/swh/web/assets/src/bundles/webapp/code-highlighting.js +++ b/swh/web/assets/src/bundles/webapp/code-highlighting.js @@ -1,118 +1,121 @@ /** * Copyright (C) 2018 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 */ export async function highlightCode(showLineNumbers = true) { await import(/* webpackChunkName: "highlightjs" */ 'utils/highlightjs'); // empty hljs language definition function noHighlight(hljs) { return {}; } // just a trick to get line numbers working when no highlight // has to be performed hljs.registerLanguage('nohighlight-swh', noHighlight); // keep track of the first highlighted line let firstHighlightedLine = null; // highlighting color let lineHighlightColor = 'rgb(193, 255, 193)'; // function to highlight a line function highlightLine(i) { let lineTd = $(`.swh-content div[data-line-number="${i}"]`).parent().parent(); lineTd.css('background-color', lineHighlightColor); return lineTd; } function removeHash() { history.replaceState('', document.title, window.location.pathname + window.location.search); } // function to reset highlighting function resetHighlightedLines() { firstHighlightedLine = null; $('.swh-content tr').css('background-color', 'inherit'); } function scrollToLine(lineDomElt) { if ($(lineDomElt).closest('.swh-content').length > 0) { $('html, body').animate({ scrollTop: $(lineDomElt).offset().top - 70 }, 500); } } // function to highlight lines based on a url fragment // in the form '#Lx' or '#Lx-Ly' function parseUrlFragmentForLinesToHighlight() { let lines = []; let linesRegexp = new RegExp(/L(\d+)/g); let line = linesRegexp.exec(window.location.hash); while (line) { lines.push(parseInt(line[1])); line = linesRegexp.exec(window.location.hash); } resetHighlightedLines(); if (lines.length === 1) { firstHighlightedLine = parseInt(lines[0]); scrollToLine(highlightLine(lines[0])); } else if (lines[0] < lines[lines.length - 1]) { firstHighlightedLine = parseInt(lines[0]); scrollToLine(highlightLine(lines[0])); for (let i = lines[0] + 1; i <= lines[lines.length - 1]; ++i) { highlightLine(i); } } } $(document).ready(() => { // highlight code and add line numbers $('code').each((i, block) => { hljs.highlightBlock(block); if (showLineNumbers) { hljs.lineNumbersBlock(block); } }); if (!showLineNumbers) { return; } // click handler to dynamically highlight line(s) // when the user clicks on a line number (lines range // can also be highlighted while holding the shift key) $('body').click(evt => { if (evt.target.classList.contains('hljs-ln-n')) { let line = parseInt($(evt.target).data('line-number')); if (evt.shiftKey && firstHighlightedLine && line > firstHighlightedLine) { let firstLine = firstHighlightedLine; resetHighlightedLines(); for (let i = firstLine; i <= line; ++i) { highlightLine(i); } firstHighlightedLine = firstLine; window.location.hash = `#L${firstLine}-L${line}`; } else { resetHighlightedLines(); highlightLine(line); window.location.hash = `#L${line}`; scrollToLine(evt.target); } } else { resetHighlightedLines(); removeHash(); } }); // update lines highlighting when the url fragment changes $(window).on('hashchange', () => parseUrlFragmentForLinesToHighlight()); - $('.navbar-nav.swh-browse-nav a[href="#browse"]').tab('show'); + // schedule lines highlighting if any as hljs.lineNumbersBlock() is async + setTimeout(() => { + parseUrlFragmentForLinesToHighlight(); + }); }); } diff --git a/swh/web/browse/urls.py b/swh/web/browse/urls.py index fc2bf7b8f..99f65719c 100644 --- a/swh/web/browse/urls.py +++ b/swh/web/browse/urls.py @@ -1,39 +1,49 @@ # Copyright (C) 2017-2018 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 from django.conf.urls import url from django.shortcuts import render import swh.web.browse.views.directory # noqa import swh.web.browse.views.content # noqa import swh.web.browse.views.identifiers # noqa import swh.web.browse.views.origin # noqa import swh.web.browse.views.person # noqa import swh.web.browse.views.release # noqa import swh.web.browse.views.revision # noqa import swh.web.browse.views.snapshot # noqa from swh.web.browse.browseurls import BrowseUrls -def default_browse_view(request): - """Default django view used as an entry point - for the swh browse ui web application. - - The url that point to it is /browse/. - - Args: - request: input django http request - """ +def _default_browse_view(request): return render(request, 'browse.html', - {'heading': 'Browse the Software Heritage archive', + {'heading': 'Browse', 'empty_browse': True}) +def _browse_help_view(request): + return render(request, 'browse-help.html', + {'heading': 'Help'}) + + +def _browse_search_view(request): + return render(request, 'browse-search.html', + {'heading': 'Search'}) + + +def _browse_vault_view(request): + return render(request, 'browse-vault-ui.html', + {'heading': 'Vault'}) + + urlpatterns = [ - url(r'^$', default_browse_view, name='browse-homepage') + url(r'^$', _default_browse_view, name='browse-mainpage'), + url(r'^help/$', _browse_help_view, name='browse-help'), + url(r'^search/$', _browse_search_view, name='browse-search'), + url(r'^vault/$', _browse_vault_view, name='browse-vault') ] urlpatterns += BrowseUrls.get_url_patterns() diff --git a/swh/web/templates/includes/browse-help.html b/swh/web/templates/browse-help.html similarity index 95% rename from swh/web/templates/includes/browse-help.html rename to swh/web/templates/browse-help.html index daa03f5ee..30537238c 100644 --- a/swh/web/templates/includes/browse-help.html +++ b/swh/web/templates/browse-help.html @@ -1,167 +1,176 @@ +{% extends "browse-layout.html" %} + {% comment %} Copyright (C) 2017-2018 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 {% endcomment %} +{% block browse-content %} +
This web application aims to provide HTML views to easily navigate in the Software Heritage archive. This is an ongoing development and new features and improvements will be progressively added over the time.
The current URI scheme of that web application is described below and depends on the type of Software Heritage object to browse. Its exhaustive documentation can be consulted from the official Software Heritage development documentation
Context-independent URLs provide information about SWH objects (e.g., revisions, directories, contents, persons, …), independently of the contexts where they have been found (e.g., specific software origins, branches, commits, …).
Below are some examples of endpoints used to just render the corresponding information for user consumption:
Where hyperlinks are created when browsing these kind of endpoints, they always point to other context-independent browsing URLs.
Context-dependent URLs provide information about SWH objects, limited to specific contexts where the objects have been found.
Currently, browsing the Software Heritage objects in the context of an origin is available. Below are some examples of such endpoints:
Searching origins ...
This interface enables to track the status of the different Software Heritage Vault cooking tasks created while browsing the archive.
Once a cooking task is finished, a link will be made available in order to download the associated archive.
Object type | Object id | Cooking status |
---|
- No Software Heritage object currently browsed.
-
- To browse the content of the archive, you can either use
- the Search interface or refer to the URI scheme described
- in the Help page.
-
{{ key }} | -
- {{ val | safe | urlize_links_and_mails | safe }}- |
-
---|
{{ key }} | +
+ {{ val | safe | urlize_links_and_mails | safe }}+ |
+
---|
The long term goal of the Software Heritage initiative is to collect all publicly available software in source code form together with its development history, replicate it massively to ensure its preservation, and share it with everyone who needs it. The Software Heritage archive is growing over time as we crawl new source code from software projects and development forges. We will incrementally release archive search and browse functionalities — as of now you can check whether source code you care about is already present in the archive or not.
A significant amount of source code has already been ingested in the Software Heritage archive. It currently includes:
As of today the archive already contains and keeps safe for you the following amount of objects: