diff --git a/assets/config/webpack.config.development.js b/assets/config/webpack.config.development.js --- a/assets/config/webpack.config.development.js +++ b/assets/config/webpack.config.development.js @@ -344,6 +344,11 @@ outputPath: 'img/thirdParty/' } }] + }, + { + test: /\.ya?ml$/, + type: 'json', + use: 'yaml-loader' } ], // tell webpack to not parse already minified files to speedup build process diff --git a/assets/src/bundles/browse/swhid-utils.js b/assets/src/bundles/browse/swhid-utils.js --- a/assets/src/bundles/browse/swhid-utils.js +++ b/assets/src/bundles/browse/swhid-utils.js @@ -85,9 +85,15 @@ $('#swh-identifiers').css('width', '1000px'); } + // prevent automatic closing of SWHIDs tab during guided tour + // as it is displayed programmatically + function clickScreenToCloseFilter() { + return $('.introjs-overlay').length > 0; + } + const tabSlideOptions = { tabLocation: 'right', - clickScreenToCloseFilters: ['.ui-slideouttab-panel', '.modal'], + clickScreenToCloseFilters: [clickScreenToCloseFilter, '.ui-slideouttab-panel', '.modal'], offset: function() { const width = $(window).width(); if (width < BREAKPOINT_SM) { diff --git a/assets/src/bundles/guided_tour/guided-tour-steps.yaml b/assets/src/bundles/guided_tour/guided-tour-steps.yaml new file mode 100644 --- /dev/null +++ b/assets/src/bundles/guided_tour/guided-tour-steps.yaml @@ -0,0 +1,306 @@ +# Copyright (C) 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 + +homepage: + - title: Welcome to the guided tour ! + intro: | + This guided tour will showcase Software Heritage web application + features in order to help you navigate into the archive + + - title: Homepage + intro: | + This is the entry point of Software Heritage web application, + let's see what we can do from here. + + - element: .swh-search-box + title: Search archived software origins + intro: | + An origin corresponds to a location from which a coherent set of + source codes has been obtained, like a git repository, a directory + containing tarballs, etc.

+ Software origins are identified by URLs (git clone URLs for instance).

+ That form enables to search for terms in the full set of archived software + origin URLs. You will be redirected to a dedicated interface displaying search + results. Clicking on an origin URL will then take you to the source code browsing + interface. If you enter a complete archived origin URL, you will be immediately + redirected to its source code browsing interface. + + - element: .swh-origin-save-link + title: Save code now + intro: | + If you haven't found the software origin you were looking for, you can use the + Save Code Now interface to submit a save request that will be immediately processed. + + - element: .swh-vault-link + title: Downloads from the vault + intro: | + Show the list of downloads you requested from the Software Heritage Vault + while browsing the archive.
+ Those downloads correspond to tarballs containing source directories + archived by Software Heritage.
+ That list of downloads is stored in your browser local storage so it + will be persistent across your visits. + + - element: .swh-help-link + title: Launch guided tour + intro: Replay that guided tour. + + - element: "#swh-login" + title: Login or register + intro: | + Come and join our users community with a Software Heritage account. + Click here and register in less than 30 seconds. + When authenticated, you can benefit from extended features like a higher + rate-limit quota for the Web API.

+ If you are already logged in, that link will take you to your user + profile interface where you can generate bearer token for Web API + authentication. + + - element: "#swh-web-api-link" + title: Software Heritage Web API + intro: | + In the Software Heritage Web API documentation you will find the complete list + of endpoints and how to use each one with a detailed example.
+ Please note that the Web API can also be queried from your web browser + through a dedicated HTML interface displaying query results. + + - title: Browsing source code of an archived software origin + intro: | + Come on in, let's introduce the Web UI to browse the content of an + archived software origin. + +browseOrigin: + - title: Browse source code of an archived software origin + intro: | + You just arrived into the first view of the archived source code of an origin. + The displayed source code files are taken from the most recent snapshot taken by + Software Heritage. By default, the content of the HEAD branch is displayed. + Continue your journey and dive deeper into the code and its development history. + + - element: "#swh-origin-url" + title: Software origin URL + intro: | + Here you can find the URL of the archived software origin.
+ Following that link will always bring you back to the code in the HEAD branch + as captured by the latest Software Heritage visit. + position: bottom + + - element: "#swh-go-to-origin" + title: Visit software origin + intro: | + You can visit the software origin URL where source code was captured from + by following that link. + position: bottom + + - element: "#swh-origin-visit" + title: Software Heritage origin visit date + intro: | + Here you can find the date when Software Heritage captured the source code of + that origin.
+ Following that link will always bring you back to the code in the HEAD branch + as captured by that visit. + position: bottom + + - element: "#swh-browse-code-nav-link" + title: Browse source code + intro: | + Here you can browse the source code of a software origin.
+ Clicking on the Code tab will always bring you back to the code in the HEAD branch + for the currently selected Software Heritage visit. + position: bottom + + - element: "#swh-browse-snapshot-branches-nav-link" + title: Browse branches + intro: | + Here you can browse the list of branches for a software origin.
+ Links are offered to browse the source code contained in each branch. + position: bottom + + - element: "#swh-browse-snapshot-releases-nav-link" + title: Browse releases + intro: | + Here you can browse the list of releases for a software origin.
+ Links are offered to browse the source code contained in each release.
+ Please note that for git origins, only annotated tags are considered as releases. + For non annotated git tags, you can browse them in the Branches tab. + position: bottom + + - element: "#swh-browse-origin-visits-nav-link" + title: Browse origin visits + intro: | + Here you can find when Software Heritage captured the source code. + These visits are called snapshots and visualized in various ways: timeline, + calendar and simple list. + Like with a way-back machine, you can travel in time and see the code as it was + when crawled by Software Heritage. + position: bottom + + - element: "#swh-branches-releases-dd" + title: Switch between branches and releases + intro: | + You can easily switch between different branches and releases using this dropdown. + position: bottom + + - element: "#swh-breadcrumbs-container" + title: Current navigation path + intro: | + You can see here the current path you are taking in the code, which will make it + easier to navigate back. + position: bottom + + - element: .swh-tr-link + title: Browse revisions history + intro: | + Display the list of revisions (aka commits) for the current branch in various + orderings. Links are offered to browse source code as it was in each revision. + The list of files changes introduced in each revision can also be computed and + the associated diffs displayed. + position: bottom + + - element: .swh-vault-download + title: Download source code in an archive + intro: | + You can request the creation of an archive in .tar.gz format that will contain + the currently browsed directory. + You can follow the archive creation progress and download it once done by + visiting the Downloads page (link can be found in the left sidebar). + position: bottom + + - element: "#swh-take-new-snashot" + title: Request to save origin again + intro: | + If the archived software origin currently browsed is not synchronized with its + upstream version (for instance when new commits have been issued), you can + explicitly request Software Heritage to take a new snapshot of it. + position: bottom + + - element: "#swh-tip-revision" + title: Branch tip revision + intro: | + Here you can see the latest revision (commit) archived by Software Heritage + for the current branch. + position: bottom + + - element: "#swhids-handle" + title: Display SWHIDs of browsed objects + intro: | + When clicking on this handle, a tab will be displayed containing Software Heritage + IDentifiers of currently browsed objects. + position: left + + - element: "#swh-identifiers" + title: Get SWHIDs of browsed objects + intro: | + In that tab, you can get the SWHIDs of currently browsed objects. + Let's see what we can do from here. + position: left + + - element: "#swhid-object-types" + title: Select archived object type + intro: | + Software Heritage computes identifiers for all archived objects whose type can be: + + Based on the current context, you can get the SWHID of each browsed object in a + dedicated tab. + position: left + + - element: .swh-badges + title: Software Heritage badges + intro: | + You can include Software Heritage badges in the README file of you code repository + to indicate its archival by Software Heritage.
+ Clicking on a badge will show you how to do so depending on your README format. + + - element: .swhid + title: Software Heritage IDentifier (SWHID) + intro: | + Here you can find the SWHID of the selected object. + position: left + + - element: "#swhid-options" + title: Add / remove qualifiers to SWHID + intro: | + Toggle the adding of qualifiers to the SWHID which adds extra information regarding + the context the object has been found. + position: bottom + + - element: "#swhid-copy-buttons" + title: Copy SWHID for a given browsed object + intro: | + You can easily copy to clipboard a SWHID or its permalink using these dedicated + buttons. + position: bottom + + - title: Browsing a source code file + intro: | + Special features are also offered when browsing a source code file.
+ This is what we will see in the next part of that tour. + +browseContent: + + - title: Browsing a source code file + intro: | + You just arrived in the source code view of Software Heritage web application.
+ Extra features are available in it compared to source directory view, let's make + a review of them. + + - element: .swh-tr-link + title: Download source code file + intro: | + You can download the raw bytes of the source code file and save it locally + by using the "Save Page" feature of your browser. + position: bottom + + - element: .chosen-container + title: Select programming language + intro: | + If Software Heritage did not manage to automatically find a programming language + for the browsed source code file or did not find the right one, you can explicitly + set the language to highlight using this dropdown. + position: bottom + + - element: .hljs-ln-numbers[data-line-number="11"] + title: Highlight a source code line + intro: | + Click on the line number to highlight the corresponding line of code.
+ When a line gets selected, it is automatically added in the SWHID qualifiers + for the associated content object. It enables to easily browse back that + specific line of code. + position: bottom + + - element: .hljs-ln-numbers[data-line-number="17"] + title: Highlight a range of source code lines, + intro: | + Hold Shift key and click on the line number to highlight a range of source + code lines.
+ When a range of lines get selected, it is automatically added in the SWHID qualifiers + for the associated content object. It enables to easily browse back that specific + code snippet. + position: bottom + + - title: End of guided tour + intro: | + Thank your for having followed that guided tour !
+ You will be now redirected to the page you were browsing prior launching it. diff --git a/assets/src/bundles/guided_tour/index.js b/assets/src/bundles/guided_tour/index.js new file mode 100644 --- /dev/null +++ b/assets/src/bundles/guided_tour/index.js @@ -0,0 +1,182 @@ +/** + * Copyright (C) 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 * as introJs from 'intro.js'; +import 'intro.js/introjs.css'; +import './swh-introjs.css'; +import guidedTourSteps from './guided-tour-steps.yaml'; +import {disableScrolling, enableScrolling} from 'utils/scrolling'; + +let guidedTour = []; +let tour = null; +let previousElement = null; +// we use a origin available both in production and swh-web tests +// environment to ease tour testing +const originUrl = 'https://github.com/memononen/libtess2'; + +// init guided tour configuration when page loads in order +// to hack on it in cypress tests +$(() => { + // tour is defined by an array of objects containing: + // - URL of page to run a tour + // - intro.js configuration with tour steps + // - optional intro.js callback function for tour interactivity + guidedTour = [ + { + url: Urls.swh_web_homepage(), + introJsOptions: { + disableInteraction: true, + scrollToElement: false, + steps: guidedTourSteps.homepage + } + }, + { + url: `${Urls.browse_origin_directory()}?origin_url=${originUrl}`, + introJsOptions: { + disableInteraction: true, + scrollToElement: false, + steps: guidedTourSteps.browseOrigin + }, + onBeforeChange: function(targetElement) { + // open SWHIDs tab before its tour step + if (targetElement && targetElement.id === 'swh-identifiers') { + if (!$('#swh-identifiers').tabSlideOut('isOpen')) { + $('.introjs-helperLayer, .introjs-tooltipReferenceLayer').hide(); + $('#swh-identifiers').tabSlideOut('open'); + setTimeout(() => { + $('.introjs-helperLayer, .introjs-tooltipReferenceLayer').show(); + tour.nextStep(); + }, 500); + return false; + } + } + return true; + } + }, + { + url: `${Urls.browse_origin_content()}?origin_url=${originUrl}&path=Example/example.c`, + introJsOptions: { + steps: guidedTourSteps.browseContent + }, + onBeforeChange: function(targetElement) { + const lineNumberStart = 11; + const lineNumberEnd = 17; + // forbid move to next step until user clicks on line numbers + if (targetElement && targetElement.dataset.lineNumber === `${lineNumberEnd}`) { + const background = $(`.hljs-ln-numbers[data-line-number="${lineNumberStart}"]`).css('background-color'); + const canGoNext = background !== 'rgba(0, 0, 0, 0)'; + if (!canGoNext && $('#swh-next-step-disabled').length === 0) { + $('.introjs-tooltiptext').append( + `

+ You need to select the line number before proceeding to
next step. +

`); + } + previousElement = targetElement; + return canGoNext; + } else if (previousElement && previousElement.dataset.lineNumber === `${lineNumberEnd}`) { + let canGoNext = true; + for (let i = lineNumberStart; i <= lineNumberEnd; ++i) { + const background = $(`.hljs-ln-numbers[data-line-number="${i}"]`).css('background-color'); + canGoNext = canGoNext && background !== 'rgba(0, 0, 0, 0)'; + if (!canGoNext) { + swh.webapp.resetHighlightedLines(); + swh.webapp.scrollToLine(swh.webapp.highlightLine(lineNumberStart, true)); + if ($('#swh-next-step-disabled').length === 0) { + $('.introjs-tooltiptext').append( + `

+ You need to select the line numbers range from ${lineNumberStart} + to ${lineNumberEnd} before proceeding to next step. +

`); + } + break; + } + } + return canGoNext; + } + previousElement = targetElement; + return true; + } + } + ]; + // init guided tour on page if guided_tour query parameter is present + const searchParams = new URLSearchParams(window.location.search); + if (searchParams && searchParams.has('guided_tour')) { + initGuidedTour(parseInt(searchParams.get('guided_tour'))); + } +}); + +export function getGuidedTour() { + return guidedTour; +} + +export function guidedTourButtonClick(event) { + event.preventDefault(); + initGuidedTour(); +} + +export function initGuidedTour(page = 0) { + if (page >= guidedTour.length) { + return; + } + const pageUrl = new URL(window.location.origin + guidedTour[page].url); + const currentUrl = new URL(window.location.href); + const guidedTourNext = currentUrl.searchParams.get('guided_tour_next'); + currentUrl.searchParams.delete('guided_tour'); + currentUrl.searchParams.delete('guided_tour_next'); + const pageUrlStr = decodeURIComponent(pageUrl.toString()); + const currentUrlStr = decodeURIComponent(currentUrl.toString()); + if (currentUrlStr !== pageUrlStr) { + // go to guided tour page URL if current one does not match + pageUrl.searchParams.set('guided_tour', page); + if (page === 0) { + // user will be redirected to the page he was at the end of the tour + pageUrl.searchParams.set('guided_tour_next', currentUrlStr); + } + window.location = decodeURIComponent(pageUrl.toString()); + } else { + // create intro.js guided tour and configure it + tour = introJs().setOptions(guidedTour[page].introJsOptions); + tour.setOptions({ + 'exitOnOverlayClick': false, + 'showBullets': false + }); + if (page < guidedTour.length - 1) { + // if not on the last page of the tour, rename next button label + // and schedule next page loading when clicking on it + tour.setOption('doneLabel', 'Next page') + .onexit(() => { + // re-enable page scrolling when exiting tour + enableScrolling(); + }) + .oncomplete(() => { + const nextPageUrl = new URL(window.location.origin + guidedTour[page + 1].url); + nextPageUrl.searchParams.set('guided_tour', page + 1); + if (guidedTourNext) { + nextPageUrl.searchParams.set('guided_tour_next', guidedTourNext); + } + window.location.href = decodeURIComponent(nextPageUrl.toString()); + }); + } else { + tour.oncomplete(() => { + enableScrolling(); // re-enable page scrolling when tour is complete + if (guidedTourNext) { + window.location.href = guidedTourNext; + } + }); + } + if (guidedTour[page].hasOwnProperty('onBeforeChange')) { + tour.onbeforechange(guidedTour[page].onBeforeChange); + } + setTimeout(() => { + // run guided tour with a little delay to ensure every asynchronous operations + // after page load have been executed + disableScrolling(); // disable page scrolling with mouse or keyboard while tour runs. + tour.start(); + window.scrollTo(0, 0); + }, 500); + } +}; diff --git a/assets/src/bundles/guided_tour/swh-introjs.css b/assets/src/bundles/guided_tour/swh-introjs.css new file mode 100644 --- /dev/null +++ b/assets/src/bundles/guided_tour/swh-introjs.css @@ -0,0 +1,18 @@ +/** + * Copyright (C) 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 + */ + +.introjs-tooltip { + min-width: 500px; +} + +.introjs-tooltip.introjs-floating { + /* center tooltip not attached to a DOM element to the center of the screen */ + position: fixed !important; + top: 50% !important; + margin: 0 auto !important; + transform: translate(-50%, -50%) !important; +} diff --git a/assets/src/bundles/webapp/code-highlighting.js b/assets/src/bundles/webapp/code-highlighting.js --- a/assets/src/bundles/webapp/code-highlighting.js +++ b/assets/src/bundles/webapp/code-highlighting.js @@ -7,35 +7,38 @@ import {removeUrlFragment} from 'utils/functions'; -export async function highlightCode(showLineNumbers = true) { +// keep track of the first highlighted line +let firstHighlightedLine = null; +// highlighting color +const lineHighlightColor = 'rgb(193, 255, 193)'; - await import(/* webpackChunkName: "highlightjs" */ 'utils/highlightjs'); +// function to highlight a line +export function highlightLine(i, firstHighlighted = false) { + const lineTd = $(`.hljs-ln-line[data-line-number="${i}"]`); + lineTd.css('background-color', lineHighlightColor); + if (firstHighlighted) { + firstHighlightedLine = i; + } + return lineTd; +} - // keep track of the first highlighted line - let firstHighlightedLine = null; - // highlighting color - const lineHighlightColor = 'rgb(193, 255, 193)'; +// function to reset highlighting +export function resetHighlightedLines() { + firstHighlightedLine = null; + $('.hljs-ln-line[data-line-number]').css('background-color', 'inherit'); +} - // function to highlight a line - function highlightLine(i) { - const lineTd = $(`.hljs-ln-line[data-line-number="${i}"]`); - lineTd.css('background-color', lineHighlightColor); - return lineTd; +export function scrollToLine(lineDomElt) { + if ($(lineDomElt).closest('.swh-content').length > 0) { + $('html, body').animate({ + scrollTop: $(lineDomElt).offset().top - 70 + }, 500); } +} - // function to reset highlighting - function resetHighlightedLines() { - firstHighlightedLine = null; - $('.hljs-ln-line[data-line-number]').css('background-color', 'inherit'); - } +export async function highlightCode(showLineNumbers = true) { - function scrollToLine(lineDomElt) { - if ($(lineDomElt).closest('.swh-content').length > 0) { - $('html, body').animate({ - scrollTop: $(lineDomElt).offset().top - 70 - }, 500); - } - } + await import(/* webpackChunkName: "highlightjs" */ 'utils/highlightjs'); // function to highlight lines based on a url fragment // in the form '#Lx' or '#Lx-Ly' diff --git a/assets/src/utils/scrolling.js b/assets/src/utils/scrolling.js new file mode 100644 --- /dev/null +++ b/assets/src/utils/scrolling.js @@ -0,0 +1,47 @@ +/** + * Copyright (C) 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 + */ + +// adapted from https://stackoverflow.com/questions/4770025/how-to-disable-scrolling-temporarily + +// up: 38, down: 40, spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36 +const keys = {38: 1, 40: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1}; + +function preventDefault(e) { + e.preventDefault(); +} + +function preventDefaultForScrollKeys(e) { + if (keys[e.keyCode]) { + preventDefault(e); + return false; + } +} + +// modern Chrome requires { passive: false } when adding event +let supportsPassive = false; +try { + window.addEventListener('test', null, Object.defineProperty({}, 'passive', { + get: function() { supportsPassive = true; } + })); +} catch (e) {} + +const wheelOpt = supportsPassive ? {passive: false} : false; +const wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel'; + +export function disableScrolling() { + window.addEventListener('DOMMouseScroll', preventDefault, false); // older FF + window.addEventListener(wheelEvent, preventDefault, wheelOpt); // modern desktop + window.addEventListener('touchmove', preventDefault, wheelOpt); // mobile + window.addEventListener('keydown', preventDefaultForScrollKeys, false); +} + +export function enableScrolling() { + window.removeEventListener('DOMMouseScroll', preventDefault, false); + window.removeEventListener(wheelEvent, preventDefault, wheelOpt); + window.removeEventListener('touchmove', preventDefault, wheelOpt); + window.removeEventListener('keydown', preventDefaultForScrollKeys, false); +} diff --git a/cypress/integration/guided-tour.spec.js b/cypress/integration/guided-tour.spec.js new file mode 100644 --- /dev/null +++ b/cypress/integration/guided-tour.spec.js @@ -0,0 +1,123 @@ +/** + * Copyright (C) 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 + */ + +describe('Guided Tour Tests', function() { + + // utility function to traverse all guided tour steps in a page + const clickNextStepButtons = (stopAtTitle = null) => { + cy.get('.introjs-nextbutton').then($button => { + const buttonText = $button.text(); + const headerText = $button.parent().siblings('.introjs-tooltip-header').text(); + if (buttonText === 'Next' && headerText.slice(0, -1) !== stopAtTitle) { + cy.get('.introjs-nextbutton') + .click({force: true}) + .then(() => { + cy.get('.introjs-tooltip').should('be.visible'); + clickNextStepButtons(stopAtTitle); + }); + } + }); + }; + + it('should start UI guided tour when clicking on help button', function() { + cy.ambassadorLogin(); + cy.visit('/'); + cy.get('.swh-help-link') + .click(); + + cy.get('.introjs-tooltip') + .should('exist'); + }); + + it('should change guided tour page after current page steps', function() { + cy.ambassadorLogin(); + cy.visit('/'); + + cy.get('.swh-help-link') + .click(); + + cy.url().then(url => { + clickNextStepButtons(); + cy.get('.introjs-nextbutton') + .should('have.text', 'Next page') + .click(); + cy.url().should('not.eq', url); + }); + + }); + + it('should automatically open SWHIDs tab on second page of the guided tour', function() { + const guidedTourPageIndex = 1; + cy.ambassadorLogin(); + cy.visit('/').window().then(win => { + const guidedTour = win.swh.guided_tour.getGuidedTour(); + // jump to third guided tour page + cy.visit(guidedTour[guidedTourPageIndex].url); + cy.window().then(win => { + // SWHIDs tab should be closed when tour begins + cy.get('.ui-slideouttab-open').should('not.exist'); + // init guided tour on the page + win.swh.guided_tour.initGuidedTour(guidedTourPageIndex); + clickNextStepButtons(); + // SWHIDs tab should be opened when tour begins + cy.get('.ui-slideouttab-open').should('exist'); + }); + }); + }); + + it('should stay at step while line numbers not clicked on content view tour', function() { + const guidedTourPageIndex = 2; + cy.ambassadorLogin(); + // jump to third guided tour page + cy.visit('/').window().then(win => { + const guidedTour = win.swh.guided_tour.getGuidedTour(); + cy.visit(guidedTour[guidedTourPageIndex].url); + cy.window().then(win => { + // init guided tour on the page + win.swh.guided_tour.initGuidedTour(guidedTourPageIndex); + + clickNextStepButtons('Highlight a source code line'); + + cy.get('.introjs-tooltip-header').then($header => { + const headerText = $header.text(); + // user did not click yet on line numbers and should stay + // blocked on first step of the tour + cy.get('.introjs-nextbutton') + .click(); + cy.get('.introjs-tooltip-header') + .should('have.text', headerText); + // click on line numbers + cy.get('.hljs-ln-numbers[data-line-number="11"]') + .click(); + // check move to next step is allowed + cy.get('.introjs-nextbutton') + .click(); + cy.get('.introjs-tooltip-header') + .should('not.have.text', headerText); + }); + + cy.get('.introjs-tooltip-header').then($header => { + const headerText = $header.text(); + // user did not click yet on line numbers and should stay + // blocked on first step of the tour + cy.get('.introjs-nextbutton') + .click(); + cy.get('.introjs-tooltip-header') + .should('have.text', headerText); + // click on line numbers + cy.get('.hljs-ln-numbers[data-line-number="17"]') + .click({shiftKey: true}); + // check move to next step is allowed + cy.get('.introjs-nextbutton') + .click(); + cy.get('.introjs-tooltip-header') + .should('not.have.text', headerText); + }); + }); + }); + }); +}); diff --git a/package.json b/package.json --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "highlightjs-line-numbers.js": "^2.8.0", "html-encoder-decoder": "^1.3.9", "iframe-resizer": "^4.3.2", + "intro.js": "^4.1.0", "jquery": "^3.6.0", "js-cookie": "^2.2.1", "js-year-calendar": "^1.0.2", @@ -114,7 +115,8 @@ "webpack": "^5.44.0", "webpack-bundle-tracker": "^1.1.0", "webpack-cli": "^4.7.2", - "webpack-dev-server": "^3.11.2" + "webpack-dev-server": "^3.11.2", + "yaml-loader": "^0.6.0" }, "resolutions": { "jquery": "^3.6.0" diff --git a/swh/web/templates/homepage.html b/swh/web/templates/homepage.html --- a/swh/web/templates/homepage.html +++ b/swh/web/templates/homepage.html @@ -24,7 +24,7 @@ {% block content %} -
+