Merge pull request #3007 from overleaf/pr-file-outline-extras
File outline extras GitOrigin-RevId: c35c4f35dce280c9f44c02b567df9734943d0cb0
This commit is contained in:
committed by
Copybot
parent
79b6f6e473
commit
0e9771ac09
@@ -13,6 +13,7 @@
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import App from '../../base'
|
||||
import _ from 'lodash'
|
||||
import 'libs/jquery-layout'
|
||||
import 'libs/jquery.ui.touch-punch'
|
||||
|
||||
@@ -233,9 +234,16 @@ ng-click=\"handleClick()\">\
|
||||
}
|
||||
|
||||
// Save state when exiting
|
||||
$(window).unload(() =>
|
||||
ide.localStorage(`layout.${name}`, element.layout().readState())
|
||||
)
|
||||
$(window).unload(() => {
|
||||
// Save only the state properties for the current layout, ignoring sublayouts inside it.
|
||||
// If we save sublayouts state (`children`), the layout library will use it when
|
||||
// initializing. This raises errors when the sublayout elements aren't available (due to
|
||||
// being loaded at init or just not existing for the current project/user).
|
||||
const stateToSave = _.mapValues(element.layout().readState(), pane =>
|
||||
_.omit(pane, 'children')
|
||||
)
|
||||
ide.localStorage(`layout.${name}`, stateToSave)
|
||||
})
|
||||
|
||||
if (attrs.openEast != null) {
|
||||
scope.$watch(attrs.openEast, function(value, oldValue) {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import App from '../../base'
|
||||
|
||||
const layoutOptions = {
|
||||
center: {
|
||||
paneSelector: '[vertical-resizable-top]',
|
||||
paneClass: 'vertical-resizable-top',
|
||||
size: 'auto'
|
||||
},
|
||||
south: {
|
||||
paneSelector: '[vertical-resizable-bottom]',
|
||||
paneClass: 'vertical-resizable-bottom',
|
||||
resizerClass: 'vertical-resizable-resizer',
|
||||
resizerCursor: 'row-resize',
|
||||
size: 'auto',
|
||||
resizable: true,
|
||||
closable: false,
|
||||
slidable: false,
|
||||
spacing_open: 6,
|
||||
spacing_closed: 6,
|
||||
maxSize: '75%'
|
||||
}
|
||||
}
|
||||
|
||||
export default App.directive('verticalResizablePanes', localStorage => ({
|
||||
restrict: 'A',
|
||||
link(scope, element, attrs) {
|
||||
let name = attrs.verticalResizablePanes
|
||||
let storedSize = null
|
||||
let manualResizeIncoming = false
|
||||
|
||||
if (name) {
|
||||
const storageKey = `vertical-resizable:${name}:south-size`
|
||||
storedSize = localStorage(storageKey)
|
||||
$(window).unload(() => {
|
||||
if (storedSize) {
|
||||
localStorage(storageKey, storedSize)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const toggledExternally = attrs.verticalResizablePanesToggledExternallyOn
|
||||
const resizerDisabledClass = `${layoutOptions.south.resizerClass}-disabled`
|
||||
|
||||
function enableResizer() {
|
||||
if (layoutHandle.resizers && layoutHandle.resizers.south) {
|
||||
layoutHandle.resizers.south.removeClass(resizerDisabledClass)
|
||||
}
|
||||
}
|
||||
|
||||
function disableResizer() {
|
||||
if (layoutHandle.resizers && layoutHandle.resizers.south) {
|
||||
layoutHandle.resizers.south.addClass(resizerDisabledClass)
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragEnd() {
|
||||
manualResizeIncoming = true
|
||||
}
|
||||
|
||||
function handleResize(paneName, paneEl, paneState) {
|
||||
if (manualResizeIncoming) {
|
||||
storedSize = paneState.size
|
||||
}
|
||||
manualResizeIncoming = false
|
||||
}
|
||||
|
||||
if (toggledExternally) {
|
||||
scope.$on(toggledExternally, (e, open) => {
|
||||
let newSize = 'auto'
|
||||
if (open) {
|
||||
if (storedSize) {
|
||||
newSize = storedSize
|
||||
}
|
||||
enableResizer()
|
||||
} else {
|
||||
disableResizer()
|
||||
}
|
||||
layoutHandle.sizePane('south', newSize)
|
||||
})
|
||||
}
|
||||
|
||||
// The `drag` event fires only when the user manually resizes the panes; the `resize` event fires even when
|
||||
// the layout library internally resizes itself. In order to get explicit user-initiated resizes, we need to
|
||||
// listen to `drag` events. However, when the `drag` event fires, the panes aren't yet finished sizing so we
|
||||
// get the pane size *before* the resize happens. We do get the correct size in the next `resize` event.
|
||||
// The solution to work around this is to set up a flag in `drag` events which tells the next `resize` event
|
||||
// that it was user-initiated (therefore, storing the value).
|
||||
layoutOptions.south.ondrag_end = handleDragEnd
|
||||
layoutOptions.south.onresize = handleResize
|
||||
|
||||
const layoutHandle = element.layout(layoutOptions)
|
||||
}
|
||||
}))
|
||||
@@ -132,6 +132,7 @@ export default (EditorManager = (function() {
|
||||
this.$scope.ui.view = 'editor'
|
||||
|
||||
const done = () => {
|
||||
this.$scope.$broadcast('doc:after-opened')
|
||||
if (options.gotoLine != null) {
|
||||
// allow Ace to display document before moving, delay until next tick
|
||||
// added delay to make this happen later that gotoStoredPosition in
|
||||
|
||||
@@ -14,8 +14,8 @@ class OutlineManager {
|
||||
this.isTexFile = false
|
||||
this.outline = []
|
||||
|
||||
scope.$watch('editor.sharejs_doc', shareJsDoc => {
|
||||
this.shareJsDoc = shareJsDoc
|
||||
scope.$on('doc:after-opened', () => {
|
||||
this.shareJsDoc = scope.editor.shareJsDoc
|
||||
this.isTexFile = isValidTeXFile(scope.editor.open_doc_name)
|
||||
this.updateOutline()
|
||||
this.broadcastChangeEvent()
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
import React, { useState } from 'react'
|
||||
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'
|
||||
import localStorage from '../../../modules/localStorage'
|
||||
|
||||
function OutlinePane({ isTexFile, outline, jumpToLine }) {
|
||||
const [expanded, setExpanded] = useState(true)
|
||||
function OutlinePane({ isTexFile, outline, projectId, jumpToLine, onToggle }) {
|
||||
const storageKey = `file_outline.expanded.${projectId}`
|
||||
const [expanded, setExpanded] = useState(() => {
|
||||
const storedExpandedState = localStorage(storageKey) !== false
|
||||
return storedExpandedState
|
||||
})
|
||||
const isOpen = isTexFile && expanded
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
onToggle(isOpen)
|
||||
},
|
||||
[isOpen]
|
||||
)
|
||||
|
||||
const expandCollapseIconClasses = classNames('fa', 'outline-caret-icon', {
|
||||
'fa-angle-down': expanded,
|
||||
'fa-angle-right': !expanded
|
||||
'fa-angle-down': isOpen,
|
||||
'fa-angle-right': !isOpen
|
||||
})
|
||||
|
||||
const headerClasses = classNames('outline-pane', {
|
||||
@@ -18,6 +31,7 @@ function OutlinePane({ isTexFile, outline, jumpToLine }) {
|
||||
|
||||
function handleExpandCollapseClick() {
|
||||
if (isTexFile) {
|
||||
localStorage(storageKey, !expanded)
|
||||
setExpanded(!expanded)
|
||||
}
|
||||
}
|
||||
@@ -32,20 +46,20 @@ function OutlinePane({ isTexFile, outline, jumpToLine }) {
|
||||
>
|
||||
<i className={expandCollapseIconClasses} />
|
||||
<h4 className="outline-header-name">File outline</h4>
|
||||
<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>
|
||||
</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">
|
||||
@@ -65,7 +79,9 @@ const tooltip = (
|
||||
OutlinePane.propTypes = {
|
||||
isTexFile: PropTypes.bool.isRequired,
|
||||
outline: PropTypes.array.isRequired,
|
||||
jumpToLine: PropTypes.func.isRequired
|
||||
projectId: PropTypes.string.isRequired,
|
||||
jumpToLine: PropTypes.func.isRequired,
|
||||
onToggle: PropTypes.func
|
||||
}
|
||||
|
||||
export default OutlinePane
|
||||
|
||||
@@ -18,10 +18,22 @@ App.controller('OutlineController', function($scope, ide) {
|
||||
$scope.jumpToLine = lineNo => {
|
||||
ide.outlineManager.jumpToLine(lineNo)
|
||||
}
|
||||
|
||||
$scope.onToggle = isOpen => {
|
||||
$scope.$applyAsync(() => {
|
||||
$scope.$emit('outline-toggled', isOpen)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Wrap React component as Angular component. Only needed for "top-level" component
|
||||
App.component(
|
||||
'outlinePane',
|
||||
react2angular(OutlinePane, ['outline', 'jumpToLine', 'isTexFile'])
|
||||
react2angular(OutlinePane, [
|
||||
'outline',
|
||||
'jumpToLine',
|
||||
'projectId',
|
||||
'isTexFile',
|
||||
'onToggle'
|
||||
])
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user