Merge pull request #2961 from overleaf/ta-spike-outline

Document Outline Spike

GitOrigin-RevId: adc315a3546147eb10c7a40ae70f9cab1cbf7b8d
This commit is contained in:
Timothée Alby 2020-06-29 15:05:08 +02:00 committed by Copybot
parent 05abdd18d7
commit 1012dbc3c4
18 changed files with 786 additions and 56 deletions

View file

@ -762,6 +762,7 @@ const ProjectController = {
featureSwitches: user.featureSwitches,
features: user.features,
refProviders: user.refProviders,
alphaProgram: user.alphaProgram,
betaProgram: user.betaProgram,
isAdmin: user.isAdmin
},

View file

@ -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 }",

View file

@ -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() {

View file

@ -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

View file

@ -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(/\\(?<command>[sub]*section)\{(?<title>[^}]+)\}/)
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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 cant 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

View file

@ -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'])
)

View file

@ -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
)
}

View file

@ -1,2 +1,7 @@
import App from '../base'
App.constant('ExposedSettings', window.ExposedSettings)
const ExposedSettings = window.ExposedSettings
App.constant('ExposedSettings', ExposedSettings)
export default ExposedSettings

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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",

View file

@ -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",