From 1012dbc3c4b879fdd5c11ef9144f186c43d62d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Alby?= Date: Mon, 29 Jun 2020 15:05:08 +0200 Subject: [PATCH] Merge pull request #2961 from overleaf/ta-spike-outline Document Outline Spike GitOrigin-RevId: adc315a3546147eb10c7a40ae70f9cab1cbf7b8d --- .../src/Features/Project/ProjectController.js | 1 + .../app/views/project/editor/file-tree.pug | 12 + services/web/frontend/js/ide.js | 2 + .../frontend/js/ide/outline/OutlineManager.js | 60 ++++ .../frontend/js/ide/outline/OutlineParser.js | 50 ++++ .../js/ide/outline/components/OutlineItem.js | 65 +++++ .../js/ide/outline/components/OutlineList.js | 31 +++ .../js/ide/outline/components/OutlinePane.js | 73 +++++ .../js/ide/outline/components/OutlineRoot.js | 32 +++ .../outline/controllers/OutlineController.js | 27 ++ .../controllers/SettingsController.js | 10 +- .../web/frontend/js/main/exposed-settings.js | 7 +- .../web/frontend/js/main/is-valid-tex-file.js | 12 + .../web/frontend/stylesheets/app/editor.less | 1 + .../stylesheets/app/editor/file-tree.less | 10 +- .../stylesheets/app/editor/outline.less | 185 +++++++++++++ services/web/package-lock.json | 261 +++++++++++++++--- services/web/package.json | 3 + 18 files changed, 786 insertions(+), 56 deletions(-) create mode 100644 services/web/frontend/js/ide/outline/OutlineManager.js create mode 100644 services/web/frontend/js/ide/outline/OutlineParser.js create mode 100644 services/web/frontend/js/ide/outline/components/OutlineItem.js create mode 100644 services/web/frontend/js/ide/outline/components/OutlineList.js create mode 100644 services/web/frontend/js/ide/outline/components/OutlinePane.js create mode 100644 services/web/frontend/js/ide/outline/components/OutlineRoot.js create mode 100644 services/web/frontend/js/ide/outline/controllers/OutlineController.js create mode 100644 services/web/frontend/js/main/is-valid-tex-file.js create mode 100644 services/web/frontend/stylesheets/app/editor/outline.less diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index bc2f8829df..2c89f63ce1 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -762,6 +762,7 @@ const ProjectController = { featureSwitches: user.featureSwitches, features: user.features, refProviders: user.refProviders, + alphaProgram: user.alphaProgram, betaProgram: user.betaProgram, isAdmin: user.isAdmin }, diff --git a/services/web/app/views/project/editor/file-tree.pug b/services/web/app/views/project/editor/file-tree.pug index 7e161c1c75..6e72a55aa2 100644 --- a/services/web/app/views/project/editor/file-tree.pug +++ b/services/web/app/views/project/editor/file-tree.pug @@ -88,6 +88,18 @@ aside.file-tree(ng-controller="FileTreeController", ng-class="{ 'multi-selected' span {{ entity.name }} + + if user.alphaProgram + .outline-container( + ng-controller="OutlineController" + ) + outline-pane( + is-tex-file="isTexFile" + outline="outline" + jump-to-line="jumpToLine" + ) + + script(type='text/ng-template', id='entityListItemTemplate') li( ng-class="{ 'selected': entity.selected, 'multi-selected': entity.multiSelected }", diff --git a/services/web/frontend/js/ide.js b/services/web/frontend/js/ide.js index e08abd67e8..565d6509ba 100644 --- a/services/web/frontend/js/ide.js +++ b/services/web/frontend/js/ide.js @@ -29,6 +29,7 @@ import BinaryFilesManager from './ide/binary-files/BinaryFilesManager' import ReferencesManager from './ide/references/ReferencesManager' import MetadataManager from './ide/metadata/MetadataManager' import ReviewPanelManager from './ide/review-panel/ReviewPanelManager' +import OutlineManager from './ide/outline/OutlineManager' import SafariScrollPatcher from './ide/SafariScrollPatcher' import './ide/cobranding/CobrandingDataService' import './ide/settings/index' @@ -191,6 +192,7 @@ App.controller('IdeController', function( ide.permissionsManager = new PermissionsManager(ide, $scope) ide.binaryFilesManager = new BinaryFilesManager(ide, $scope) ide.metadataManager = new MetadataManager(ide, $scope, metadata) + ide.outlineManager = new OutlineManager(ide, $scope) let inited = false $scope.$on('project:joined', function() { diff --git a/services/web/frontend/js/ide/outline/OutlineManager.js b/services/web/frontend/js/ide/outline/OutlineManager.js new file mode 100644 index 0000000000..7fa2ba79ec --- /dev/null +++ b/services/web/frontend/js/ide/outline/OutlineManager.js @@ -0,0 +1,60 @@ +import './controllers/OutlineController' +import './components/OutlinePane' +import './components/OutlineRoot' +import './components/OutlineList' +import './components/OutlineItem' +import parseOutline from './OutlineParser' +import isValidTeXFile from '../../main/is-valid-tex-file' + +class OutlineManager { + constructor(ide, scope) { + this.ide = ide + this.scope = scope + this.shareJsDoc = null + this.isTexFile = false + this.outline = [] + + scope.$watch('editor.sharejs_doc', shareJsDoc => { + this.shareJsDoc = shareJsDoc + this.isTexFile = isValidTeXFile(scope.editor.open_doc_name) + this.updateOutline() + this.broadcastChangeEvent() + }) + + scope.$watch('openFile.name', openFileName => { + this.isTexFile = isValidTeXFile(openFileName) + this.updateOutline() + this.broadcastChangeEvent() + }) + + scope.$on('doc:changed', () => { + this.updateOutline() + this.broadcastChangeEvent() + }) + } + + updateOutline() { + this.outline = [] + if (this.isTexFile) { + const content = this.ide.editorManager.getCurrentDocValue() + if (content) { + this.outline = parseOutline(content) + } + } + } + + jumpToLine(line) { + this.ide.editorManager.openDocId(this.shareJsDoc.doc.doc_id, { + gotoLine: line + }) + } + + broadcastChangeEvent() { + this.scope.$broadcast('outline-manager:outline-changed', { + isTexFile: this.isTexFile, + outline: this.outline + }) + } +} + +export default OutlineManager diff --git a/services/web/frontend/js/ide/outline/OutlineParser.js b/services/web/frontend/js/ide/outline/OutlineParser.js new file mode 100644 index 0000000000..594217e7e8 --- /dev/null +++ b/services/web/frontend/js/ide/outline/OutlineParser.js @@ -0,0 +1,50 @@ +const COMMAND_LEVELS = { + section: 0, + subsection: 1, + subsubsection: 2 +} + +function matchOutline(content) { + const lines = content.split('\n') + const flatOutline = [] + lines.forEach((line, lineId) => { + const match = line.match(/\\(?[sub]*section)\{(?[^}]+)\}/) + if (!match) return + const { + groups: { command, title } + } = match + flatOutline.push({ + line: lineId + 1, + title, + level: COMMAND_LEVELS[command] + }) + }) + return flatOutline +} + +function nestOutline(flatOutline) { + let parentOutlines = {} + let nestedOutlines = [] + flatOutline.forEach(outline => { + const parentOutline = parentOutlines[outline.level - 1] + if (!parentOutline) { + // top level + nestedOutlines.push(outline) + } else if (!parentOutline.children) { + // first outline in this node + parentOutline.children = [outline] + } else { + // push outline to node + parentOutline.children.push(outline) + } + parentOutlines[outline.level] = outline + }) + return nestedOutlines +} + +function parseOutline(content) { + const flatOutline = matchOutline(content) + return nestOutline(flatOutline) +} + +export default parseOutline diff --git a/services/web/frontend/js/ide/outline/components/OutlineItem.js b/services/web/frontend/js/ide/outline/components/OutlineItem.js new file mode 100644 index 0000000000..86d59060e3 --- /dev/null +++ b/services/web/frontend/js/ide/outline/components/OutlineItem.js @@ -0,0 +1,65 @@ +import React, { useState } from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' +import OutlineList from './OutlineList' + +function OutlineItem({ outlineItem, jumpToLine }) { + const [expanded, setExpanded] = useState(true) + + const mainItemClasses = classNames('outline-item', { + 'outline-item-no-children': !outlineItem.children + }) + + const expandCollapseIconClasses = classNames('fa', 'outline-caret-icon', { + 'fa-angle-down': expanded, + 'fa-angle-right': !expanded + }) + + function handleExpandCollapseClick() { + setExpanded(!expanded) + } + + function handleOutlineItemLinkClick() { + jumpToLine(outlineItem.line) + } + + return ( + <li className={mainItemClasses}> + <div className="outline-item-row"> + {outlineItem.children ? ( + <button + className="outline-item-expand-collapse-btn" + onClick={handleExpandCollapseClick} + > + <i className={expandCollapseIconClasses} /> + </button> + ) : null} + <button + className="outline-item-link" + onClick={handleOutlineItemLinkClick} + > + {outlineItem.title} + </button> + </div> + {expanded && outlineItem.children ? ( + <OutlineList + outline={outlineItem.children} + jumpToLine={jumpToLine} + isRoot={false} + /> + ) : null} + </li> + ) +} + +OutlineItem.propTypes = { + outlineItem: PropTypes.exact({ + line: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, + level: PropTypes.number.isRequired, + children: PropTypes.array + }).isRequired, + jumpToLine: PropTypes.func.isRequired +} + +export default OutlineItem diff --git a/services/web/frontend/js/ide/outline/components/OutlineList.js b/services/web/frontend/js/ide/outline/components/OutlineList.js new file mode 100644 index 0000000000..fc05db6c41 --- /dev/null +++ b/services/web/frontend/js/ide/outline/components/OutlineList.js @@ -0,0 +1,31 @@ +import React from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' +import OutlineItem from './OutlineItem' + +function OutlineList({ outline, jumpToLine, isRoot }) { + const listClasses = classNames('outline-item-list', { + 'outline-item-list-root': isRoot + }) + return ( + <ul className={listClasses}> + {outline.map((outlineItem, idx) => { + return ( + <OutlineItem + key={`${outlineItem.level}-${idx}`} + outlineItem={outlineItem} + jumpToLine={jumpToLine} + /> + ) + })} + </ul> + ) +} + +OutlineList.propTypes = { + outline: PropTypes.array.isRequired, + jumpToLine: PropTypes.func.isRequired, + isRoot: PropTypes.bool +} + +export default OutlineList diff --git a/services/web/frontend/js/ide/outline/components/OutlinePane.js b/services/web/frontend/js/ide/outline/components/OutlinePane.js new file mode 100644 index 0000000000..b75744ffdd --- /dev/null +++ b/services/web/frontend/js/ide/outline/components/OutlinePane.js @@ -0,0 +1,73 @@ +import React, { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import { OverlayTrigger, Tooltip } from 'react-bootstrap' +import classNames from 'classnames' +import OutlineRoot from './OutlineRoot' + +function OutlinePane({ isTexFile, outline, jumpToLine }) { + const [expanded, setExpanded] = useState(true) + + useEffect(() => setExpanded(isTexFile), [isTexFile]) + + const expandCollapseIconClasses = classNames('fa', 'outline-caret-icon', { + 'fa-angle-down': expanded, + 'fa-angle-right': !expanded + }) + + const headerClasses = classNames('outline-pane', { + 'outline-pane-disabled': !isTexFile + }) + + function handleExpandCollapseClick() { + if (isTexFile) { + setExpanded(!expanded) + } + } + + return ( + <div className={headerClasses}> + <header className="outline-header"> + <button + className="outline-header-expand-collapse-btn" + disabled={!isTexFile} + onClick={handleExpandCollapseClick} + > + <i className={expandCollapseIconClasses} /> + <h4 className="outline-header-name">File outline</h4> + </button> + <OverlayTrigger placement="top" overlay={tooltip} delayHide={100}> + <a + href="/beta/participate" + target="_blank" + rel="noopener noreferrer" + className="outline-header-beta-badge" + > + <span className="sr-only"> + The File outline is a beta feature. Click here to manage your beta + program membership. + </span> + </a> + </OverlayTrigger> + </header> + {expanded && isTexFile ? ( + <div className="outline-body"> + <OutlineRoot outline={outline} jumpToLine={jumpToLine} /> + </div> + ) : null} + </div> + ) +} + +const tooltip = ( + <Tooltip id="outline-beta-badge-tooltip"> + The <strong>File outline</strong> is a beta feature. + </Tooltip> +) + +OutlinePane.propTypes = { + isTexFile: PropTypes.bool.isRequired, + outline: PropTypes.array.isRequired, + jumpToLine: PropTypes.func.isRequired +} + +export default OutlinePane diff --git a/services/web/frontend/js/ide/outline/components/OutlineRoot.js b/services/web/frontend/js/ide/outline/components/OutlineRoot.js new file mode 100644 index 0000000000..2e5e1f7edb --- /dev/null +++ b/services/web/frontend/js/ide/outline/components/OutlineRoot.js @@ -0,0 +1,32 @@ +import React from 'react' +import PropTypes from 'prop-types' +import OutlineList from './OutlineList' + +function OutlineRoot({ outline, jumpToLine }) { + return ( + <div> + {outline.length ? ( + <OutlineList outline={outline} jumpToLine={jumpToLine} isRoot /> + ) : ( + <div className="outline-body-no-elements"> + We can’t find any sections or subsections in this file.{' '} + <a + href="/learn/how-to/Using_the_File_Outline_feature" + className="outline-body-link" + target="_blank" + rel="noopener noreferrer" + > + Know more about the file outline + </a> + </div> + )} + </div> + ) +} + +OutlineRoot.propTypes = { + outline: PropTypes.array.isRequired, + jumpToLine: PropTypes.func.isRequired +} + +export default OutlineRoot diff --git a/services/web/frontend/js/ide/outline/controllers/OutlineController.js b/services/web/frontend/js/ide/outline/controllers/OutlineController.js new file mode 100644 index 0000000000..fdfc2d782e --- /dev/null +++ b/services/web/frontend/js/ide/outline/controllers/OutlineController.js @@ -0,0 +1,27 @@ +import App from '../../../base' +import OutlinePane from '../components/OutlinePane' +import { react2angular } from 'react2angular' + +App.controller('OutlineController', function($scope, ide) { + $scope.isTexFile = false + $scope.outline = [] + + $scope.$on('outline-manager:outline-changed', onOutlineChange) + + function onOutlineChange(e, outlineInfo) { + $scope.$applyAsync(() => { + $scope.isTexFile = outlineInfo.isTexFile + $scope.outline = outlineInfo.outline + }) + } + + $scope.jumpToLine = lineNo => { + ide.outlineManager.jumpToLine(lineNo) + } +}) + +// Wrap React component as Angular component. Only needed for "top-level" component +App.component( + 'outlinePane', + react2angular(OutlinePane, ['outline', 'jumpToLine', 'isTexFile']) +) diff --git a/services/web/frontend/js/ide/settings/controllers/SettingsController.js b/services/web/frontend/js/ide/settings/controllers/SettingsController.js index 51a06dbc94..d87537c276 100644 --- a/services/web/frontend/js/ide/settings/controllers/SettingsController.js +++ b/services/web/frontend/js/ide/settings/controllers/SettingsController.js @@ -13,19 +13,13 @@ import _ from 'lodash' * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ import App from '../../../base' +import isValidTeXFile from '../../../main/is-valid-tex-file' export default App.controller('SettingsController', function( $scope, - ExposedSettings, settings, ide ) { - const validRootDocExtensions = ExposedSettings.validRootDocExtensions - const validRootDocRegExp = new RegExp( - `\\.(${validRootDocExtensions.join('|')})$`, - 'i' - ) - $scope.overallThemesList = window.overallThemes $scope.ui = { loadingStyleSheet: false } @@ -83,7 +77,7 @@ export default App.controller('SettingsController', function( // To gracefully handle that case, make sure we also show the current main file (ignoring extension). filteredDocs = $scope.docs.filter( doc => - validRootDocRegExp.test(doc.doc.name) || + isValidTeXFile(doc.doc.name) || $scope.project.rootDoc_id === doc.doc.id ) } diff --git a/services/web/frontend/js/main/exposed-settings.js b/services/web/frontend/js/main/exposed-settings.js index ca589932f9..8fbfb2644c 100644 --- a/services/web/frontend/js/main/exposed-settings.js +++ b/services/web/frontend/js/main/exposed-settings.js @@ -1,2 +1,7 @@ import App from '../base' -App.constant('ExposedSettings', window.ExposedSettings) + +const ExposedSettings = window.ExposedSettings + +App.constant('ExposedSettings', ExposedSettings) + +export default ExposedSettings diff --git a/services/web/frontend/js/main/is-valid-tex-file.js b/services/web/frontend/js/main/is-valid-tex-file.js new file mode 100644 index 0000000000..c3c3aad63e --- /dev/null +++ b/services/web/frontend/js/main/is-valid-tex-file.js @@ -0,0 +1,12 @@ +import ExposedSettings from './exposed-settings' + +const validTeXFileRegExp = new RegExp( + `\\.(${ExposedSettings.validRootDocExtensions.join('|')})$`, + 'i' +) + +function isValidTeXFile(filename) { + return validTeXFileRegExp.test(filename) +} + +export default isValidTeXFile diff --git a/services/web/frontend/stylesheets/app/editor.less b/services/web/frontend/stylesheets/app/editor.less index 5ad0fb4c48..f6c8d46755 100644 --- a/services/web/frontend/stylesheets/app/editor.less +++ b/services/web/frontend/stylesheets/app/editor.less @@ -13,6 +13,7 @@ @import './editor/review-panel.less'; @import './editor/rich-text.less'; @import './editor/publish-modal.less'; +@import './editor/outline.less'; @ui-layout-toggler-def-height: 50px; @ui-resizer-size: 7px; diff --git a/services/web/frontend/stylesheets/app/editor/file-tree.less b/services/web/frontend/stylesheets/app/editor/file-tree.less index 5ea40f7886..cee305f235 100644 --- a/services/web/frontend/stylesheets/app/editor/file-tree.less +++ b/services/web/frontend/stylesheets/app/editor/file-tree.less @@ -9,18 +9,18 @@ } .file-tree { + display: flex; + flex-direction: column; + .toolbar.toolbar-filetree { .toolbar-small-mixin; .toolbar-alt-mixin; padding: 0 5px; + flex-shrink: 0; } .file-tree-inner { - position: absolute; - top: 32px; - bottom: 0; - left: 0; - right: 0; + flex-grow: 1; overflow-y: auto; background-color: @file-tree-bg; diff --git a/services/web/frontend/stylesheets/app/editor/outline.less b/services/web/frontend/stylesheets/app/editor/outline.less new file mode 100644 index 0000000000..da526354eb --- /dev/null +++ b/services/web/frontend/stylesheets/app/editor/outline.less @@ -0,0 +1,185 @@ +@outline-v-rhythm: 24px; +@outline-h-rhythm: 24px; +@outline-item-h-padding: @outline-h-rhythm * 0.25; + +.outline-container { + width: 100%; + max-height: 50%; // While we don't support resizing. + background-color: @file-tree-bg; +} + +.outline-pane { + display: flex; + flex-flow: column; + height: 100%; + font-size: @font-size-small; + color: #fff; +} + +.outline-pane-disabled { + opacity: 0.5; +} + +.outline-header { + .toolbar-small-mixin; + .toolbar-alt-mixin; + display: flex; + align-items: center; + padding-right: @outline-h-rhythm * 0.25; + flex-shrink: 0; +} + +.outline-header-expand-collapse-btn { + display: inline-block; + background-color: transparent; + border: 0; + padding: 0; + font-size: inherit; + vertical-align: inherit; + color: #fff; + flex-grow: 1; + text-align: left; + white-space: nowrap; + overflow: hidden; + border-radius: @border-radius-base; + text-overflow: ellipsis; + &:hover, + &:focus { + outline: 0; + background-color: @ol-blue-gray-5; + } +} + +.outline-header-name { + display: inline-block; + font-family: @font-family-sans-serif; + font-size: @font-size-small; + color: #fff; + margin: 0; +} + +.outline-header-beta-badge { + display: inline-block; + border-radius: @border-radius-base; + background-color: @orange; + width: @outline-h-rhythm * 0.75; + height: @outline-v-rhythm * 0.75; + line-height: @outline-v-rhythm * 0.75; + text-align: center; + cursor: pointer; + color: #fff; + + &::before { + content: 'β'; + } + + &:hover, + &:focus { + color: #fff; + text-decoration: none; + } +} + +.outline-body { + overflow-y: auto; + background-color: @file-tree-bg; + padding-right: @outline-h-rhythm * 0.25; +} + +.outline-body-no-elements { + color: @ol-blue-gray-2; + text-align: center; + padding: @outline-v-rhythm @outline-h-rhythm (@outline-v-rhythm * 2); + margin-right: -(@outline-h-rhythm * 0.25); +} + +.outline-body-link { + display: block; + color: #fff; + text-decoration: underline; + &:hover, + &:focus { + color: #fff; + text-decoration: underline; + } +} + +.outline-item-list { + position: relative; + list-style: none; + padding-left: @outline-h-rhythm; + + &::before { + content: ''; + background-color: @ol-blue-gray-3; + top: @outline-h-rhythm / 4; + bottom: @outline-h-rhythm / 4; + width: 1px; + left: (@outline-h-rhythm * 1.5); + position: absolute; + } + + &.outline-item-list-root { + padding-left: 0; + &::before { + left: (@outline-h-rhythm * 0.5); + } + } +} + +.outline-item { +} + +.outline-item-no-children { + padding-left: @outline-h-rhythm - @outline-item-h-padding; +} + +.outline-item-row { + display: flex; + overflow: hidden; + white-space: nowrap; +} + +.outline-item-expand-collapse-btn { + display: inline; + border: 0; + padding: 0; + font-size: inherit; + vertical-align: inherit; + position: relative; + z-index: 1; + background-color: @file-tree-bg; + color: @ol-blue-gray-2; + margin-right: -(@outline-item-h-padding); + border-radius: @border-radius-base; + &:hover, + &:focus { + outline: 0; + background-color: @ol-blue-gray-5; + } +} + +.outline-item-link { + display: inline; + color: #fff; + background-color: transparent; + border: 0; + position: relative; + z-index: 1; + padding: 0 @outline-item-h-padding; + line-height: @outline-v-rhythm; + border-radius: @border-radius-base; + overflow: hidden; + text-overflow: ellipsis; + &:hover, + &:focus { + outline: 0; + background-color: @ol-blue-gray-5; + } +} + +.outline-caret-icon { + width: @outline-h-rhythm; + font-size: 17px; + text-align: center; +} diff --git a/services/web/package-lock.json b/services/web/package-lock.json index d11f0967f2..0c85f3be48 100644 --- a/services/web/package-lock.json +++ b/services/web/package-lock.json @@ -1393,7 +1393,6 @@ "version": "7.5.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz", "integrity": "sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.2" }, @@ -1401,8 +1400,28 @@ "regenerator-runtime": { "version": "0.13.3", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", - "dev": true + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + } + } + }, + "@babel/runtime-corejs2": { + "version": "7.10.3", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.10.3.tgz", + "integrity": "sha512-enKvnR/kKFbZFgXYo165wtSA5OeiTlgsnU4jV3vpKRhfWUJjLS6dfVcjIPeRcgJbgEgdgu0I+UyBWqu6c0GumQ==", + "requires": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" } } }, @@ -3189,6 +3208,11 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==" }, + "@types/angular": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.7.0.tgz", + "integrity": "sha512-zneUmi5I6oSkGBqkRP9rxbWX1mi6Yj7gNV+WNffmJLf8x4cnV0MGqXFNSP90NZ1kRRLCOdKBf9RIVD1TMg4aog==" + }, "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", @@ -3282,6 +3306,19 @@ "@types/istanbul-lib-report": "*" } }, + "@types/lodash": { + "version": "4.14.155", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.155.tgz", + "integrity": "sha512-vEcX7S7aPhsBCivxMwAANQburHBtfN9RdyXFk84IJmu2Z4Hkg1tOFgaslRiEqqvoLtbCBi6ika1EMspE+NZ9Lg==" + }, + "@types/lodash.frompairs": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/lodash.frompairs/-/lodash.frompairs-4.0.6.tgz", + "integrity": "sha512-rwCUf4NMKhXpiVjL/RXP8YOk+rd02/J4tACADEgaMXRVnzDbSSlBMKFZoX/ARmHVLg3Qc98Um4PErGv8FbxU7w==", + "requires": { + "@types/lodash": "*" + } + }, "@types/long": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", @@ -3301,8 +3338,7 @@ "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", - "dev": true + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/q": { "version": "1.5.2", @@ -3314,7 +3350,6 @@ "version": "16.9.35", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.35.tgz", "integrity": "sha512-q0n0SsWcGc8nDqH2GJfWQWUOmZSJhXV64CjVN5SvcNti3TdEaA3AH0D8DwNmMdzjMAC/78tB8nAZIlV8yTz+zQ==", - "dev": true, "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" @@ -6042,7 +6077,6 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", "integrity": "sha512-OvfN8y1oAxxphzkl2SnCS+ztV/uVKTATtgLjWYg/7KwcNyf3rzpHxNQJZCKtsZd4+MteKczhWbSjtEX4bGgU9g==", "dev": true, - "optional": true, "requires": { "hoek": "0.9.x" } @@ -6816,6 +6850,11 @@ } } }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, "clean-css": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", @@ -8272,8 +8311,7 @@ "csstype": { "version": "2.6.10", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz", - "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==", - "dev": true + "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==" }, "csurf": { "version": "1.11.0", @@ -8792,6 +8830,14 @@ "integrity": "sha512-XBM62jdDc06IXSujkqw6BugEWiDkp6jphtzVJf1kgPQGvfzaU7/jRtRSF/mxc8DBCIm2LS3bN1dCa5Sfxx982A==", "dev": true }, + "dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + } + }, "dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", @@ -11019,8 +11065,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -11038,13 +11083,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11057,18 +11100,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -11171,8 +11211,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -11182,7 +11221,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -11195,20 +11233,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -11225,7 +11260,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -11304,8 +11338,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -11315,7 +11348,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -11391,8 +11423,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -11422,7 +11453,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -11440,7 +11470,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -11479,13 +11508,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -12457,8 +12484,7 @@ "version": "0.9.1", "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", "integrity": "sha512-ZZ6eGyzGjyMTmpSPYVECXy9uNfqBR7x5CavhUaLOeD6W0vWK1mp/b7O3f86XE0Mtfo9rZ6Bh3fnuw9Xr8MF9zA==", - "dev": true, - "optional": true + "dev": true }, "homedir-polyfill": { "version": "1.0.3", @@ -14271,6 +14297,11 @@ } } }, + "keycode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", + "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" + }, "keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", @@ -14751,6 +14782,11 @@ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" }, + "lodash.frompairs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.frompairs/-/lodash.frompairs-4.0.1.tgz", + "integrity": "sha1-vE5SB/onV8E25XNhTpZkUGsrG9I=" + }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -16330,6 +16366,17 @@ "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", "dev": true }, + "ngcomponent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ngcomponent/-/ngcomponent-4.1.0.tgz", + "integrity": "sha512-cGL3iVoqMWTpCfaIwgRKhdaGqiy2Z+CCG0cVfjlBvdqE8saj8xap9B4OTf+qwObxLVZmDTJPDgx3bN6Q/lZ7BQ==", + "requires": { + "@types/angular": "^1.6.39", + "@types/lodash": "^4.14.85", + "angular": ">=1.5.0", + "lodash": "^4.17.4" + } + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -19552,6 +19599,25 @@ } } }, + "prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "requires": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "dependencies": { + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, "propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", @@ -20070,6 +20136,25 @@ "prop-types": "^15.6.2" } }, + "react-bootstrap": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.33.1.tgz", + "integrity": "sha512-qWTRravSds87P8WC82tETy2yIso8qDqlIm0czsrduCaYAFtHuyLu0XDbUlfLXeRzqgwm5sRk2wRaTNoiVkk/YQ==", + "requires": { + "@babel/runtime-corejs2": "^7.0.0", + "classnames": "^2.2.5", + "dom-helpers": "^3.2.0", + "invariant": "^2.2.4", + "keycode": "^2.2.0", + "prop-types": "^15.6.1", + "prop-types-extra": "^1.0.1", + "react-overlays": "^0.9.0", + "react-prop-types": "^0.4.0", + "react-transition-group": "^2.0.0", + "uncontrollable": "^7.0.2", + "warning": "^3.0.0" + } + }, "react-dom": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", @@ -20086,6 +20171,64 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==" }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-overlays": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.9.1.tgz", + "integrity": "sha512-b0asy/zHtRd0i2+2/uNxe3YVprF3bRT1guyr791DORjCzE/HSBMog+ul83CdtKQ1kZ+pLnxWCu5W3BMysFhHdQ==", + "requires": { + "classnames": "^2.2.5", + "dom-helpers": "^3.2.1", + "prop-types": "^15.5.10", + "prop-types-extra": "^1.0.1", + "react-transition-group": "^2.2.1", + "warning": "^3.0.0" + } + }, + "react-prop-types": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/react-prop-types/-/react-prop-types-0.4.0.tgz", + "integrity": "sha1-+ZsL+0AGkpya8gUefBQUpcdbk9A=", + "requires": { + "warning": "^3.0.0" + } + }, + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + }, + "dependencies": { + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + } + } + }, + "react2angular": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/react2angular/-/react2angular-4.0.6.tgz", + "integrity": "sha512-MDl2WRoTyu7Gyh4+FAIlmsM2mxIa/DjSz6G/d90L1tK8ZRubqVEayKF6IPyAruC5DMhGDVJ7tlAIcu/gMNDjXg==", + "requires": { + "@types/lodash.frompairs": "^4.0.5", + "angular": ">=1.5", + "lodash.frompairs": "^4.0.1", + "ngcomponent": "^4.1.0" + } + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -21368,7 +21511,7 @@ "querystring": "^0.2.0", "saml": "^0.12.1", "xml-crypto": "^0.10.1", - "xmldom": "github:auth0/xmldom#v0.1.19-auth0_1", + "xmldom": "github:auth0/xmldom#3376bc7beb5551bf68e12b0cc6b0e3669f77d392", "xpath": "0.0.5", "xtend": "^1.0.3" }, @@ -24417,6 +24560,32 @@ "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, + "uncontrollable": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.1.1.tgz", + "integrity": "sha512-EcPYhot3uWTS3w00R32R2+vS8Vr53tttrvMj/yA1uYRhf8hbTG2GyugGqWDY0qIskxn0uTTojVd6wPYW9ZEf8Q==", + "requires": { + "@babel/runtime": "^7.6.3", + "@types/react": "^16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.10.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz", + "integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + } + } + }, "undefsafe": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", @@ -24976,6 +25145,14 @@ "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==" }, + "warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz", diff --git a/services/web/package.json b/services/web/package.json index 242b1ee9a8..ae0e64b640 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -54,6 +54,7 @@ "body-parser": "^1.19.0", "bufferedstream": "1.6.0", "celebrate": "^10.0.1", + "classnames": "^2.2.6", "codemirror": "^5.33.0", "connect-redis": "^3.1.0", "contentful": "^6.1.1", @@ -113,7 +114,9 @@ "pug": "^2.0.4", "qrcode": "^1.4.4", "react": "^16.13.1", + "react-bootstrap": "^0.33.1", "react-dom": "^16.13.1", + "react2angular": "^4.0.6", "redis-sharelatex": "^1.0.12", "request": "^2.88.2", "request-promise-native": "^1.0.8",