mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 10:53:35 -05:00
Merge pull request #10111 from overleaf/mj-lezer-file-outline
[web] Parser backed file outline GitOrigin-RevId: 0825f4887ba4dae24a14dd1880d07b791d0b4cd9
This commit is contained in:
parent
986d52b8d4
commit
a54b633726
10 changed files with 82 additions and 4 deletions
|
@ -34,4 +34,5 @@ aside.editor-sidebar.full-size(
|
||||||
on-toggle="onToggle"
|
on-toggle="onToggle"
|
||||||
event-tracking="eventTracking"
|
event-tracking="eventTracking"
|
||||||
highlighted-line="highlightedLine"
|
highlighted-line="highlightedLine"
|
||||||
|
show="show"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
window.i18n = { currentLangCode: 'en' }
|
window.i18n = { currentLangCode: 'en' }
|
||||||
window.ExposedSettings = {
|
window.ExposedSettings = {
|
||||||
appName: 'Overleaf',
|
appName: 'Overleaf',
|
||||||
|
validRootDocExtensions: ['tex', 'Rtex', 'ltx'],
|
||||||
} as typeof window.ExposedSettings
|
} as typeof window.ExposedSettings
|
||||||
|
|
|
@ -107,6 +107,9 @@ OutlineItem.propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
level: PropTypes.number,
|
level: PropTypes.number,
|
||||||
children: PropTypes.array,
|
children: PropTypes.array,
|
||||||
|
// Used for caching in CM6
|
||||||
|
from: PropTypes.number,
|
||||||
|
to: PropTypes.number,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
jumpToLine: PropTypes.func.isRequired,
|
jumpToLine: PropTypes.func.isRequired,
|
||||||
highlightedLine: PropTypes.number,
|
highlightedLine: PropTypes.number,
|
||||||
|
|
|
@ -16,6 +16,7 @@ const OutlinePane = React.memo(function OutlinePane({
|
||||||
onToggle,
|
onToggle,
|
||||||
eventTracking,
|
eventTracking,
|
||||||
highlightedLine,
|
highlightedLine,
|
||||||
|
show,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
@ -46,6 +47,12 @@ const OutlinePane = React.memo(function OutlinePane({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: This flag is for disabling the rendering of the component. Used while
|
||||||
|
// both an Angular and React-based file outline is present in the code base.
|
||||||
|
if (!show) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={headerClasses}>
|
<div className={headerClasses}>
|
||||||
<header className="outline-header">
|
<header className="outline-header">
|
||||||
|
@ -82,6 +89,7 @@ OutlinePane.propTypes = {
|
||||||
onToggle: PropTypes.func.isRequired,
|
onToggle: PropTypes.func.isRequired,
|
||||||
eventTracking: PropTypes.object.isRequired,
|
eventTracking: PropTypes.object.isRequired,
|
||||||
highlightedLine: PropTypes.number,
|
highlightedLine: PropTypes.number,
|
||||||
|
show: PropTypes.bool.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withErrorBoundary(OutlinePane)
|
export default withErrorBoundary(OutlinePane)
|
||||||
|
|
|
@ -8,6 +8,23 @@ App.controller('OutlineController', function ($scope, ide, eventTracking) {
|
||||||
$scope.outline = []
|
$scope.outline = []
|
||||||
$scope.eventTracking = eventTracking
|
$scope.eventTracking = eventTracking
|
||||||
|
|
||||||
|
function shouldShowOutline() {
|
||||||
|
if ($scope.editor.showRichText) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return !$scope.editor.newSourceEditor
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.show = shouldShowOutline()
|
||||||
|
|
||||||
|
$scope.$watch('editor.newSourceEditor', function () {
|
||||||
|
$scope.show = shouldShowOutline()
|
||||||
|
})
|
||||||
|
|
||||||
|
$scope.$watch('editor.showRichText', function () {
|
||||||
|
$scope.show = shouldShowOutline()
|
||||||
|
})
|
||||||
|
|
||||||
$scope.$on('outline-manager:outline-changed', onOutlineChange)
|
$scope.$on('outline-manager:outline-changed', onOutlineChange)
|
||||||
|
|
||||||
function onOutlineChange(e, outlineInfo) {
|
function onOutlineChange(e, outlineInfo) {
|
||||||
|
@ -40,5 +57,6 @@ App.component(
|
||||||
'eventTracking',
|
'eventTracking',
|
||||||
'onToggle',
|
'onToggle',
|
||||||
'isTexFile',
|
'isTexFile',
|
||||||
|
'show',
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,13 @@ class OutlineManager {
|
||||||
this.ignoreNextScroll = false
|
this.ignoreNextScroll = false
|
||||||
this.ignoreNextCursorUpdate = false
|
this.ignoreNextCursorUpdate = false
|
||||||
|
|
||||||
|
scope.$watch('editor.newSourceEditor', (now, before) => {
|
||||||
|
if (before && !now) {
|
||||||
|
this.updateOutline()
|
||||||
|
this.broadcastChangeEvent()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
scope.$on('doc:after-opened', (ev, { isNewDoc }) => {
|
scope.$on('doc:after-opened', (ev, { isNewDoc }) => {
|
||||||
if (isNewDoc) {
|
if (isNewDoc) {
|
||||||
// if a new doc is opened a cursor updates will be triggered before the
|
// if a new doc is opened a cursor updates will be triggered before the
|
||||||
|
@ -65,10 +72,25 @@ class OutlineManager {
|
||||||
scope.$watch('editor.showRichText', () => {
|
scope.$watch('editor.showRichText', () => {
|
||||||
this.ignoreNextScroll = true
|
this.ignoreNextScroll = true
|
||||||
this.ignoreNextCursorUpdate = true
|
this.ignoreNextCursorUpdate = true
|
||||||
|
if (this.shouldShowOutline()) {
|
||||||
|
this.updateOutline()
|
||||||
|
this.broadcastChangeEvent()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldShowOutline() {
|
||||||
|
if (this.scope.editor.showRichText) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return !this.scope.editor.newSourceEditor
|
||||||
|
}
|
||||||
|
|
||||||
updateOutline() {
|
updateOutline() {
|
||||||
|
// Disable if using CM6
|
||||||
|
if (!this.shouldShowOutline()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (this.isTexFile) {
|
if (this.isTexFile) {
|
||||||
const content = this.ide.editorManager.getCurrentDocValue()
|
const content = this.ide.editorManager.getCurrentDocValue()
|
||||||
if (content) {
|
if (content) {
|
||||||
|
@ -103,6 +125,10 @@ class OutlineManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcastChangeEvent() {
|
broadcastChangeEvent() {
|
||||||
|
// Disable if using CM6
|
||||||
|
if (!this.shouldShowOutline()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.scope.$broadcast('outline-manager:outline-changed', {
|
this.scope.$broadcast('outline-manager:outline-changed', {
|
||||||
isTexFile: this.isTexFile,
|
isTexFile: this.isTexFile,
|
||||||
outline: this.outline,
|
outline: this.outline,
|
||||||
|
|
|
@ -231,7 +231,7 @@ export default EditorManager = (function () {
|
||||||
const done = isNewDoc => {
|
const done = isNewDoc => {
|
||||||
const eventName = 'doc:after-opened'
|
const eventName = 'doc:after-opened'
|
||||||
this.$scope.$broadcast(eventName, { isNewDoc })
|
this.$scope.$broadcast(eventName, { isNewDoc })
|
||||||
window.dispatchEvent(new CustomEvent(eventName, { isNewDoc }))
|
window.dispatchEvent(new CustomEvent(eventName, { detail: isNewDoc }))
|
||||||
if (options.gotoLine != null) {
|
if (options.gotoLine != null) {
|
||||||
// allow Ace to display document before moving, delay until next tick
|
// allow Ace to display document before moving, delay until next tick
|
||||||
// added delay to make this happen later that gotoStoredPosition in
|
// added delay to make this happen later that gotoStoredPosition in
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { DependencyList, useMemo, useRef } from 'react'
|
||||||
|
import { isEqual } from 'lodash'
|
||||||
|
|
||||||
|
function useDeepCompare(dependencies: DependencyList) {
|
||||||
|
const ref = useRef<DependencyList>([])
|
||||||
|
if (!isEqual(ref.current, dependencies)) {
|
||||||
|
ref.current = dependencies
|
||||||
|
}
|
||||||
|
return ref.current
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useDeepCompareMemo<T>(
|
||||||
|
factory: () => T,
|
||||||
|
dependencies: DependencyList
|
||||||
|
) {
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
return useMemo(factory, useDeepCompare(dependencies))
|
||||||
|
}
|
|
@ -10,11 +10,11 @@ export default function useScopeEventEmitter(eventName, broadcast = true) {
|
||||||
const { $scope } = useIdeContext()
|
const { $scope } = useIdeContext()
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
detail => {
|
(...detail) => {
|
||||||
if (broadcast) {
|
if (broadcast) {
|
||||||
$scope.$broadcast(eventName, detail)
|
$scope.$broadcast(eventName, ...detail)
|
||||||
} else {
|
} else {
|
||||||
$scope.$emit(eventName, detail)
|
$scope.$emit(eventName, ...detail)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[$scope, eventName, broadcast]
|
[$scope, eventName, broadcast]
|
||||||
|
|
|
@ -55,6 +55,7 @@ describe('<OutlinePane />', function () {
|
||||||
jumpToLine={jumpToLine}
|
jumpToLine={jumpToLine}
|
||||||
onToggle={onToggle}
|
onToggle={onToggle}
|
||||||
eventTracking={eventTracking}
|
eventTracking={eventTracking}
|
||||||
|
show
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,6 +71,7 @@ describe('<OutlinePane />', function () {
|
||||||
jumpToLine={jumpToLine}
|
jumpToLine={jumpToLine}
|
||||||
onToggle={onToggle}
|
onToggle={onToggle}
|
||||||
eventTracking={eventTracking}
|
eventTracking={eventTracking}
|
||||||
|
show
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -98,6 +100,7 @@ describe('<OutlinePane />', function () {
|
||||||
jumpToLine={jumpToLine}
|
jumpToLine={jumpToLine}
|
||||||
onToggle={onToggle}
|
onToggle={onToggle}
|
||||||
eventTracking={eventTracking}
|
eventTracking={eventTracking}
|
||||||
|
show
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue