mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-12 01:54:02 +00:00
Merge pull request #15211 from overleaf/em-remove-sl-history-frontend
Remove SL history frontend GitOrigin-RevId: f6f98db7792b47f365b46da14fc823ee58787cdb
This commit is contained in:
parent
3a3ec856c2
commit
5b08d76817
23 changed files with 5 additions and 1272 deletions
|
@ -99,27 +99,6 @@ module.exports = HistoryController = {
|
|||
)
|
||||
},
|
||||
|
||||
restoreDocFromDeletedDoc(req, res, next) {
|
||||
const { project_id: projectId, doc_id: docId } = req.params
|
||||
const { name } = req.body
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
if (name == null) {
|
||||
return res.sendStatus(400) // Malformed request
|
||||
}
|
||||
RestoreManager.restoreDocFromDeletedDoc(
|
||||
userId,
|
||||
projectId,
|
||||
docId,
|
||||
name,
|
||||
(err, doc) => {
|
||||
if (err) return next(err)
|
||||
res.json({
|
||||
doc_id: doc._id,
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
getLabels(req, res, next) {
|
||||
const projectId = req.params.Project_id
|
||||
HistoryController._makeRequest(
|
||||
|
|
|
@ -22,40 +22,6 @@ const Errors = require('../Errors/Errors')
|
|||
const moment = require('moment')
|
||||
|
||||
module.exports = RestoreManager = {
|
||||
restoreDocFromDeletedDoc(userId, projectId, docId, name, callback) {
|
||||
// This is the legacy method for restoring a doc from the SL track-changes/deletedDocs system.
|
||||
// It looks up the deleted doc's contents, and then creates a new doc with the same content.
|
||||
// We don't actually remove the deleted doc entry, just create a new one from its lines.
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return ProjectEntityHandler.getDoc(
|
||||
projectId,
|
||||
docId,
|
||||
{ include_deleted: true },
|
||||
function (error, lines) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
const addDocWithName = (name, callback) =>
|
||||
EditorController.addDoc(
|
||||
projectId,
|
||||
null,
|
||||
name,
|
||||
lines,
|
||||
'restore',
|
||||
userId,
|
||||
callback
|
||||
)
|
||||
return RestoreManager._addEntityWithUniqueName(
|
||||
addDocWithName,
|
||||
name,
|
||||
callback
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
restoreFileFromV2(userId, projectId, version, pathname, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
|
|
|
@ -725,16 +725,6 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
|||
AuthorizationMiddleware.ensureUserCanReadProject,
|
||||
HistoryController.proxyToHistoryApi
|
||||
)
|
||||
webRouter.post(
|
||||
'/project/:Project_id/doc/:doc_id/version/:version_id/restore',
|
||||
AuthorizationMiddleware.ensureUserCanWriteProjectContent,
|
||||
HistoryController.proxyToHistoryApi
|
||||
)
|
||||
webRouter.post(
|
||||
'/project/:project_id/doc/:doc_id/restore',
|
||||
AuthorizationMiddleware.ensureUserCanWriteProjectContent,
|
||||
HistoryController.restoreDocFromDeletedDoc
|
||||
)
|
||||
webRouter.post(
|
||||
'/project/:project_id/restore_file',
|
||||
AuthorizationMiddleware.ensureUserCanWriteProjectContent,
|
||||
|
|
|
@ -1,114 +0,0 @@
|
|||
aside.editor-sidebar.full-size(
|
||||
ng-controller="FileTreeController"
|
||||
ng-class="{ 'multi-selected': multiSelectedCount > 0 }"
|
||||
ng-show="ui.view == 'history' && !history.isV2"
|
||||
)
|
||||
.file-tree
|
||||
.file-tree-inner(
|
||||
ng-if="rootFolder",
|
||||
ng-class="no-toolbar"
|
||||
)
|
||||
ul.list-unstyled.file-tree-list
|
||||
|
||||
file-entity(
|
||||
entity="entity",
|
||||
ng-repeat="entity in rootFolder.children | orderBy:[orderByFoldersFirst, 'name']"
|
||||
)
|
||||
|
||||
li(ng-show="deletedDocs.length > 0 && ui.view == 'history'")
|
||||
h3 #{translate("deleted_files")}
|
||||
li(
|
||||
ng-class="{ 'selected': entity.selected }",
|
||||
ng-repeat="entity in deletedDocs | orderBy:'name'",
|
||||
ng-controller="FileTreeEntityController",
|
||||
ng-show="ui.view == 'history'"
|
||||
)
|
||||
.entity
|
||||
.entity-name(
|
||||
ng-click="select($event)"
|
||||
)
|
||||
//- Just a spacer to align with folders
|
||||
i.fa.fa-fw.toggle
|
||||
i.fa.fa-fw.fa-file
|
||||
|
||||
span {{ entity.name }}
|
||||
|
||||
|
||||
script(type='text/ng-template', id='entityListItemTemplate')
|
||||
li(
|
||||
ng-class="{ 'selected': entity.selected, 'multi-selected': entity.multiSelected }",
|
||||
ng-controller="FileTreeEntityController"
|
||||
)
|
||||
.entity(ng-if="entity.type != 'folder'")
|
||||
.entity-name(
|
||||
ng-click="select($event)"
|
||||
context-menu
|
||||
data-target="context-menu-{{ entity.id }}"
|
||||
context-menu-container="body"
|
||||
context-menu-disabled="true"
|
||||
)
|
||||
//- Just a spacer to align with folders
|
||||
i.fa.fa-fw.toggle(ng-if="entity.type != 'folder'")
|
||||
|
||||
i.fa.fa-fw(ng-if="entity.type != 'folder'", ng-class="'fa-' + iconTypeFromName(entity.name)")
|
||||
i.fa.fa-external-link-square.fa-rotate-180.linked-file-highlight(
|
||||
ng-if="entity.linkedFileData.provider"
|
||||
)
|
||||
span(
|
||||
ng-hide="entity.renaming"
|
||||
) {{ entity.renamingToName || entity.name }}
|
||||
|
||||
.entity(ng-if="entity.type == 'folder'", ng-controller="FileTreeFolderController")
|
||||
.entity-name(
|
||||
ng-click="select($event)"
|
||||
)
|
||||
div(
|
||||
context-menu
|
||||
data-target="context-menu-{{ entity.id }}"
|
||||
context-menu-container="body"
|
||||
context-menu-disabled="true"
|
||||
)
|
||||
i.fa.fa-fw.toggle(
|
||||
ng-if="entity.type == 'folder'"
|
||||
ng-class="{'fa-angle-right': !expanded, 'fa-angle-down': expanded}"
|
||||
ng-click="toggleExpanded()"
|
||||
)
|
||||
|
||||
i.fa.fa-fw(
|
||||
ng-if="entity.type == 'folder'"
|
||||
ng-class="{\
|
||||
'fa-folder': !expanded, \
|
||||
'fa-folder-open': expanded \
|
||||
}"
|
||||
ng-click="select($event)"
|
||||
)
|
||||
|
||||
span(
|
||||
ng-hide="entity.renaming"
|
||||
) {{ entity.renamingToName || entity.name }}
|
||||
|
||||
ul.list-unstyled(
|
||||
ng-if="entity.type == 'folder' && (depth == null || depth < MAX_DEPTH)"
|
||||
ng-show="expanded"
|
||||
)
|
||||
file-entity(
|
||||
entity="child",
|
||||
ng-repeat="child in entity.children | orderBy:[orderByFoldersFirst, 'name']"
|
||||
depth="(depth || 0) + 1"
|
||||
)
|
||||
|
||||
.entity-limit-hit(
|
||||
ng-if="depth === MAX_DEPTH"
|
||||
ng-show="expanded"
|
||||
)
|
||||
i.fa.fa-fw
|
||||
span.entity-limit-hit-message
|
||||
| Some files might be hidden
|
||||
|
|
||||
i.fa.fa-question-circle.entity-limit-hit-tooltip-trigger(
|
||||
tooltip="Your project has hit Overleaf's maximum file depth limit. Files within this folder won't be visible."
|
||||
tooltip-append-to-body="true"
|
||||
aria-hidden="true"
|
||||
)
|
||||
span.sr-only
|
||||
| Your project has hit Overleaf's maximum file depth limit. Files within this folder won't be visible.
|
|
@ -1,8 +1,5 @@
|
|||
div#history(ng-show="ui.view == 'history' && history.updates.length > 0")
|
||||
include ./history/entriesListV1
|
||||
include ./history/entriesListV2
|
||||
|
||||
include ./history/diffPanelV1
|
||||
include ./history/previewPanelV2
|
||||
|
||||
.full-size(ng-if="ui.view == 'history' && history.updates.length === 0 && !isHistoryLoading()")
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
.diff-panel.full-size(ng-if="!history.isV2", ng-controller="HistoryDiffController")
|
||||
div(
|
||||
ng-controller="FileViewController"
|
||||
ng-if="ui.view == 'history' && openFile"
|
||||
)
|
||||
file-view(
|
||||
file="file"
|
||||
store-references-keys="storeReferencesKeys"
|
||||
)
|
||||
|
||||
.diff(
|
||||
ng-if="!!history.diff && !history.diff.loading && !history.diff.deleted && !history.diff.error && !openFile"
|
||||
)
|
||||
.toolbar.toolbar-alt
|
||||
span.name
|
||||
| <strong>{{history.diff.highlights.length}} </strong>
|
||||
ng-pluralize(
|
||||
count="history.diff.highlights.length",
|
||||
when="{\
|
||||
'one': 'change',\
|
||||
'other': 'changes'\
|
||||
}"
|
||||
)
|
||||
| in <strong>{{history.diff.pathname}}</strong>
|
||||
.toolbar-right(ng-if="permissions.write")
|
||||
a.btn.btn-danger.btn-xs(
|
||||
href,
|
||||
ng-click="openRestoreDiffModal()"
|
||||
) #{translate("restore_to_before_these_changes")}
|
||||
.diff-editor.hide-ace-cursor(
|
||||
ace-editor="history",
|
||||
theme="settings.editorTheme",
|
||||
font-size="settings.fontSize",
|
||||
text="history.diff.text",
|
||||
highlights="history.diff.highlights",
|
||||
read-only="true",
|
||||
resize-on="layout:main:resize",
|
||||
navigate-highlights="true"
|
||||
)
|
||||
|
||||
.diff-deleted.text-centered(
|
||||
ng-show="history.diff.deleted && !history.diff.restoreDeletedSuccess"
|
||||
)
|
||||
p.text-serif #{translate("file_has_been_deleted", {filename:"{{ history.diff.doc.name }} "})}
|
||||
p
|
||||
a.btn.btn-primary.btn-lg(
|
||||
href,
|
||||
ng-click="restoreDeletedDoc()",
|
||||
ng-disabled="history.diff.restoreInProgress"
|
||||
) #{translate("restore")}
|
||||
|
||||
.diff-deleted.text-centered(
|
||||
ng-show="history.diff.deleted && history.diff.restoreDeletedSuccess"
|
||||
)
|
||||
p.text-serif #{translate("file_restored", {filename:"{{ history.diff.doc.name }} "})}
|
||||
p.text-serif #{translate("file_restored_back_to_editor")}
|
||||
p
|
||||
a.btn.btn-default(
|
||||
href,
|
||||
ng-click="backToEditorAfterRestore()",
|
||||
) #{translate("file_restored_back_to_editor_btn")}
|
||||
|
||||
.loading-panel(ng-show="history.diff.loading")
|
||||
i.fa.fa-spin.fa-refresh
|
||||
| #{translate("loading")}…
|
||||
.error-panel(ng-show="history.diff.error")
|
||||
.alert.alert-danger #{translate("generic_something_went_wrong")}
|
|
@ -1,82 +0,0 @@
|
|||
aside.change-list(
|
||||
ng-if="!history.isV2"
|
||||
ng-controller="HistoryListController"
|
||||
infinite-scroll="loadMore()"
|
||||
infinite-scroll-disabled="history.loading || history.atEnd"
|
||||
infinite-scroll-initialize="ui.view == 'history'"
|
||||
)
|
||||
.infinite-scroll-inner
|
||||
ul.list-unstyled(
|
||||
ng-class="{\
|
||||
'hover-state': history.hoveringOverListSelectors\
|
||||
}"
|
||||
)
|
||||
li.change(
|
||||
ng-repeat="update in history.updates"
|
||||
ng-class="{\
|
||||
'first-in-day': update.meta.first_in_day,\
|
||||
'selected': update.inSelection,\
|
||||
'selected-to': update.selectedTo,\
|
||||
'selected-from': update.selectedFrom,\
|
||||
'hover-selected': update.inHoverSelection,\
|
||||
'hover-selected-to': update.hoverSelectedTo,\
|
||||
'hover-selected-from': update.hoverSelectedFrom,\
|
||||
}"
|
||||
ng-controller="HistoryListItemController"
|
||||
)
|
||||
|
||||
div.day(ng-show="update.meta.first_in_day") {{ update.meta.end_ts | relativeDate }}
|
||||
|
||||
div.selectors
|
||||
div.range
|
||||
form
|
||||
input.selector-from(
|
||||
type="radio"
|
||||
name="fromVersion"
|
||||
ng-model="update.selectedFrom"
|
||||
ng-value="true"
|
||||
ng-mouseover="mouseOverSelectedFrom()"
|
||||
ng-mouseout="mouseOutSelectedFrom()"
|
||||
ng-show="update.afterSelection || update.inSelection"
|
||||
)
|
||||
form
|
||||
input.selector-to(
|
||||
type="radio"
|
||||
name="toVersion"
|
||||
ng-model="update.selectedTo"
|
||||
ng-value="true"
|
||||
ng-mouseover="mouseOverSelectedTo()"
|
||||
ng-mouseout="mouseOutSelectedTo()"
|
||||
ng-show="update.beforeSelection || update.inSelection"
|
||||
)
|
||||
|
||||
div.description(ng-click="select()")
|
||||
div.time {{ update.meta.end_ts | formatDate:'h:mm a' }}
|
||||
div.action.action-edited(ng-if="history.isV2 && update.pathnames.length > 0")
|
||||
| #{translate("file_action_edited")}
|
||||
div.docs(ng-repeat="pathname in update.pathnames")
|
||||
.doc {{ pathname }}
|
||||
div.docs(ng-repeat="project_op in update.project_ops")
|
||||
div(ng-if="project_op.rename")
|
||||
.action #{translate("file_action_renamed")}
|
||||
.doc {{ project_op.rename.pathname }} → {{ project_op.rename.newPathname }}
|
||||
div(ng-if="project_op.add")
|
||||
.action #{translate("file_action_created")}
|
||||
.doc {{ project_op.add.pathname }}
|
||||
div(ng-if="project_op.remove")
|
||||
.action #{translate("file_action_deleted")}
|
||||
.doc {{ project_op.remove.pathname }}
|
||||
div.users
|
||||
div.user(ng-repeat="update_user in update.meta.users")
|
||||
.color-square(ng-if="update_user != null", ng-style="{'background-color': 'hsl({{ update_user.hue }}, 70%, 50%)'}")
|
||||
.color-square(ng-if="update_user == null", ng-style="{'background-color': 'hsl(100, 70%, 50%)'}")
|
||||
.name(ng-if="update_user && update_user.id != user.id" ng-bind="displayName(update_user)")
|
||||
.name(ng-if="update_user && update_user.id == user.id") You
|
||||
.name(ng-if="update_user == null") #{translate("anonymous")}
|
||||
div.user(ng-if="update.meta.users.length == 0")
|
||||
.color-square(style="background-color: hsl(100, 100%, 50%)")
|
||||
span #{translate("anonymous")}
|
||||
|
||||
.loading(ng-show="history.loading")
|
||||
i.fa.fa-spin.fa-refresh
|
||||
| #{translate("loading")}…
|
|
@ -1,5 +1,4 @@
|
|||
aside.change-list(
|
||||
ng-if="history.isV2"
|
||||
ng-controller="HistoryV2ListController"
|
||||
)
|
||||
history-entries-list(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
aside.editor-sidebar.full-size(
|
||||
ng-controller="HistoryV2FileTreeController"
|
||||
ng-if="ui.view == 'history' && history.isV2"
|
||||
ng-if="ui.view == 'history'"
|
||||
)
|
||||
.history-file-tree-inner
|
||||
history-file-tree(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.diff-panel.full-size(
|
||||
ng-if="history.isV2 && history.viewMode === HistoryViewModes.COMPARE && history.updates.length !== 0"
|
||||
ng-if="history.viewMode === HistoryViewModes.COMPARE && history.updates.length !== 0"
|
||||
)
|
||||
.diff(
|
||||
ng-show="!!history.selection.diff && !isHistoryLoading() && !history.selection.diff.error",
|
||||
|
@ -26,7 +26,7 @@
|
|||
.alert.alert-danger #{translate("generic_something_went_wrong")}
|
||||
|
||||
.point-in-time-panel.full-size(
|
||||
ng-if="history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME && history.updates.length !== 0"
|
||||
ng-if="history.viewMode === HistoryViewModes.POINT_IN_TIME && history.updates.length !== 0"
|
||||
)
|
||||
.point-in-time-editor-container(
|
||||
ng-if="!!history.selection.file && !history.selection.file.loading && !history.selection.file.error"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.history-toolbar(
|
||||
ng-controller="HistoryV2ToolbarController"
|
||||
ng-if="ui.view == 'history' && history.isV2"
|
||||
ng-if="ui.view == 'history'"
|
||||
)
|
||||
span.history-toolbar-selected-version(ng-show="history.loadingFileTree")
|
||||
i.fa.fa-spin.fa-refresh
|
||||
|
|
|
@ -16,7 +16,7 @@ include ./left-menu-react
|
|||
main#ide-body(
|
||||
ng-cloak,
|
||||
role="main",
|
||||
ng-class="{ 'ide-history-open' : (ui.view == 'history' && history.isV2 && !history.isReact) }",
|
||||
ng-class="{ 'ide-history-open' : (ui.view == 'history' && !history.isReact) }",
|
||||
layout="main",
|
||||
ng-hide="state.loading",
|
||||
resize-on="layout:chat:resize,history:toggle,layout:flat-screen:toggle,south-pane-toggled",
|
||||
|
@ -34,7 +34,6 @@ include ./left-menu-react
|
|||
if (historyViewReact)
|
||||
include ./file-tree-history-react
|
||||
else
|
||||
include ./file-tree-history
|
||||
include ./history/fileTreeV2
|
||||
|
||||
.ui-layout-center
|
||||
|
|
|
@ -18,10 +18,7 @@
|
|||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import './directives/fileEntity'
|
||||
import './controllers/FileTreeController'
|
||||
import './controllers/FileTreeEntityController'
|
||||
import './controllers/FileTreeFolderController'
|
||||
import '../../features/file-tree/controllers/file-tree-controller'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
let FileTreeManager
|
||||
|
@ -144,22 +141,6 @@ export default FileTreeManager = class FileTreeManager {
|
|||
return (entity.selected = true)
|
||||
}
|
||||
|
||||
toggleMultiSelectEntity(entity) {
|
||||
entity.multiSelected = !entity.multiSelected
|
||||
this.$scope.multiSelectedCount = this.multiSelectedCount()
|
||||
this.$scope.editor.multiSelectedCount = this.$scope.multiSelectedCount
|
||||
}
|
||||
|
||||
multiSelectedCount() {
|
||||
let count = 0
|
||||
this.forEachEntity(function (entity) {
|
||||
if (entity.multiSelected) {
|
||||
return count++
|
||||
}
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
getMultiSelectedEntities() {
|
||||
const entities = []
|
||||
this.forEachEntity(function (e) {
|
||||
|
@ -223,14 +204,6 @@ export default FileTreeManager = class FileTreeManager {
|
|||
return (this.$scope.multiSelectedCount = 0)
|
||||
}
|
||||
|
||||
multiSelectSelectedEntity() {
|
||||
const entity = this.findSelectedEntity()
|
||||
if (entity) {
|
||||
entity.multiSelected = true
|
||||
}
|
||||
this.$scope.multiSelectedCount = this.multiSelectedCount()
|
||||
}
|
||||
|
||||
existsInFolder(folder_id, name) {
|
||||
const folder = this.findEntityById(folder_id)
|
||||
if (folder == null) {
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
import App from '../../../base'
|
||||
import iconTypeFromName from '../util/iconTypeFromName'
|
||||
App.controller(
|
||||
'FileTreeEntityController',
|
||||
function ($scope, ide, $modal, $element) {
|
||||
$scope.MAX_DEPTH = 8
|
||||
|
||||
$scope.select = function (e) {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.stopPropagation()
|
||||
const initialMultiSelectCount = ide.fileTreeManager.multiSelectedCount()
|
||||
ide.fileTreeManager.toggleMultiSelectEntity($scope.entity)
|
||||
if (initialMultiSelectCount === 0) {
|
||||
// On first multi selection, also include the current active/open file.
|
||||
return ide.fileTreeManager.multiSelectSelectedEntity()
|
||||
}
|
||||
} else {
|
||||
ide.fileTreeManager.selectEntity($scope.entity)
|
||||
return $scope.$emit('entity:selected', $scope.entity)
|
||||
}
|
||||
}
|
||||
|
||||
if ($scope.entity.type === 'doc') {
|
||||
$scope.$watch('entity.selected', function (isSelected) {
|
||||
if (isSelected) {
|
||||
$scope.$emit('entity-file:selected', $scope.entity)
|
||||
if (!_isEntryElVisible($element)) {
|
||||
$scope.$applyAsync(function () {
|
||||
$element[0].scrollIntoView()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function _isEntryElVisible($entryEl) {
|
||||
const viewportEl = $('.file-tree-list')
|
||||
const entryElTop = $entryEl.offset().top
|
||||
const entryElBottom = entryElTop + $entryEl.outerHeight()
|
||||
const entryListViewportElTop = viewportEl.offset().top
|
||||
const entryListViewportElBottom =
|
||||
entryListViewportElTop + viewportEl.height()
|
||||
|
||||
return (
|
||||
entryElTop >= entryListViewportElTop &&
|
||||
entryElBottom <= entryListViewportElBottom
|
||||
)
|
||||
}
|
||||
|
||||
$scope.iconTypeFromName = iconTypeFromName
|
||||
}
|
||||
)
|
|
@ -1,23 +0,0 @@
|
|||
import App from '../../../base'
|
||||
|
||||
export default App.controller(
|
||||
'FileTreeFolderController',
|
||||
function ($scope, ide, $modal, localStorage) {
|
||||
$scope.expanded =
|
||||
localStorage(`folder.${$scope.entity.id}.expanded`) || false
|
||||
|
||||
$scope.toggleExpanded = function () {
|
||||
$scope.expanded = !$scope.expanded
|
||||
$scope._storeCurrentStateInLocalStorage()
|
||||
}
|
||||
|
||||
$scope.$on('entity-file:selected', function () {
|
||||
$scope.expanded = true
|
||||
$scope._storeCurrentStateInLocalStorage()
|
||||
})
|
||||
|
||||
$scope._storeCurrentStateInLocalStorage = function () {
|
||||
localStorage(`folder.${$scope.entity.id}.expanded`, $scope.expanded)
|
||||
}
|
||||
}
|
||||
)
|
|
@ -1,43 +0,0 @@
|
|||
/* eslint-disable
|
||||
max-len,
|
||||
no-return-assign,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import App from '../../../base'
|
||||
|
||||
export default App.directive('fileEntity', RecursionHelper => ({
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
entity: '=',
|
||||
permissions: '=',
|
||||
depth: '=?',
|
||||
},
|
||||
templateUrl: 'entityListItemTemplate',
|
||||
compile(element) {
|
||||
return RecursionHelper.compile(
|
||||
element,
|
||||
function (scope, element, attrs, ctrl) {
|
||||
// Don't freak out if we're already in an apply callback
|
||||
scope.$originalApply = scope.$apply
|
||||
return (scope.$apply = function (fn) {
|
||||
if (fn == null) {
|
||||
fn = function () {}
|
||||
}
|
||||
const phase = this.$root.$$phase
|
||||
if (phase === '$apply' || phase === '$digest') {
|
||||
return fn()
|
||||
} else {
|
||||
return this.$originalApply(fn)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
}))
|
|
@ -1,385 +0,0 @@
|
|||
/* eslint-disable
|
||||
camelcase,
|
||||
max-len,
|
||||
no-return-assign,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS206: Consider reworking classes to avoid initClass
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import moment from 'moment'
|
||||
import ColorManager from '../colors/ColorManager'
|
||||
import displayNameForUser from './util/displayNameForUser'
|
||||
import './controllers/HistoryListController'
|
||||
import './controllers/HistoryDiffController'
|
||||
import './directives/infiniteScroll'
|
||||
let HistoryManager
|
||||
|
||||
export default HistoryManager = (function () {
|
||||
HistoryManager = class HistoryManager {
|
||||
static initClass() {
|
||||
this.prototype.BATCH_SIZE = 10
|
||||
}
|
||||
|
||||
constructor(ide, $scope) {
|
||||
this.ide = ide
|
||||
this.$scope = $scope
|
||||
this.reset()
|
||||
|
||||
this.$scope.toggleHistory = () => {
|
||||
if (this.$scope.ui.view === 'history') {
|
||||
return this.hide()
|
||||
} else {
|
||||
return this.show()
|
||||
}
|
||||
}
|
||||
|
||||
this.$scope.$watch('history.selection.updates', updates => {
|
||||
if (updates != null && updates.length > 0) {
|
||||
this._selectDocFromUpdates()
|
||||
return this.reloadDiff()
|
||||
}
|
||||
})
|
||||
|
||||
this.$scope.$on('entity:selected', (event, entity) => {
|
||||
if (this.$scope.ui.view === 'history' && entity.type === 'doc') {
|
||||
this.$scope.history.selection.doc = entity
|
||||
return this.reloadDiff()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
show() {
|
||||
this.$scope.ui.view = 'history'
|
||||
return this.reset()
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.$scope.ui.view = 'editor'
|
||||
// Make sure we run the 'open' logic for whatever is currently selected
|
||||
return this.$scope.$emit(
|
||||
'entity:selected',
|
||||
this.ide.fileTreeManager.findSelectedEntity()
|
||||
)
|
||||
}
|
||||
|
||||
reset() {
|
||||
return (this.$scope.history = {
|
||||
updates: [],
|
||||
nextBeforeTimestamp: null,
|
||||
atEnd: false,
|
||||
selection: {
|
||||
updates: [],
|
||||
doc: null,
|
||||
range: {
|
||||
fromV: null,
|
||||
toV: null,
|
||||
start_ts: null,
|
||||
end_ts: null,
|
||||
},
|
||||
},
|
||||
diff: null,
|
||||
})
|
||||
}
|
||||
|
||||
autoSelectRecentUpdates() {
|
||||
if (this.$scope.history.updates.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$scope.history.updates[0].selectedTo = true
|
||||
|
||||
let indexOfLastUpdateNotByMe = 0
|
||||
for (let i = 0; i < this.$scope.history.updates.length; i++) {
|
||||
const update = this.$scope.history.updates[i]
|
||||
if (this._updateContainsUserId(update, this.$scope.user.id)) {
|
||||
break
|
||||
}
|
||||
indexOfLastUpdateNotByMe = i
|
||||
}
|
||||
|
||||
return (this.$scope.history.updates[
|
||||
indexOfLastUpdateNotByMe
|
||||
].selectedFrom = true)
|
||||
}
|
||||
|
||||
fetchNextBatchOfUpdates() {
|
||||
let url = `/project/${this.ide.project_id}/updates?min_count=${this.BATCH_SIZE}`
|
||||
if (this.$scope.history.nextBeforeTimestamp != null) {
|
||||
url += `&before=${this.$scope.history.nextBeforeTimestamp}`
|
||||
}
|
||||
this.$scope.history.loading = true
|
||||
return this.ide.$http.get(url).then(response => {
|
||||
const { data } = response
|
||||
this._loadUpdates(data.updates)
|
||||
this.$scope.history.nextBeforeTimestamp = data.nextBeforeTimestamp
|
||||
if (data.nextBeforeTimestamp == null) {
|
||||
this.$scope.history.atEnd = true
|
||||
}
|
||||
return (this.$scope.history.loading = false)
|
||||
})
|
||||
}
|
||||
|
||||
reloadDiff() {
|
||||
let { diff } = this.$scope.history
|
||||
const { updates, doc } = this.$scope.history.selection
|
||||
const { fromV, toV, start_ts, end_ts } =
|
||||
this._calculateRangeFromSelection()
|
||||
|
||||
if (doc == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
diff != null &&
|
||||
diff.doc === doc &&
|
||||
diff.fromV === fromV &&
|
||||
diff.toV === toV
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$scope.history.diff = diff = {
|
||||
fromV,
|
||||
toV,
|
||||
start_ts,
|
||||
end_ts,
|
||||
doc,
|
||||
error: false,
|
||||
pathname: doc.name,
|
||||
}
|
||||
|
||||
if (!doc.deleted) {
|
||||
diff.loading = true
|
||||
let url = `/project/${this.$scope.project_id}/doc/${diff.doc.id}/diff`
|
||||
if (diff.fromV != null && diff.toV != null) {
|
||||
url += `?from=${diff.fromV}&to=${diff.toV}`
|
||||
}
|
||||
|
||||
return this.ide.$http
|
||||
.get(url)
|
||||
.then(response => {
|
||||
const { data } = response
|
||||
diff.loading = false
|
||||
const { text, highlights } = this._parseDiff(data)
|
||||
diff.text = text
|
||||
return (diff.highlights = highlights)
|
||||
})
|
||||
.catch(function () {
|
||||
diff.loading = false
|
||||
return (diff.error = true)
|
||||
})
|
||||
} else {
|
||||
diff.deleted = true
|
||||
diff.restoreInProgress = false
|
||||
diff.restoreDeletedSuccess = false
|
||||
return (diff.restoredDocNewId = null)
|
||||
}
|
||||
}
|
||||
|
||||
restoreDeletedDoc(doc) {
|
||||
const url = `/project/${this.$scope.project_id}/doc/${doc.id}/restore`
|
||||
return this.ide.$http.post(url, {
|
||||
name: doc.name,
|
||||
_csrf: window.csrfToken,
|
||||
})
|
||||
}
|
||||
|
||||
restoreDiff(diff) {
|
||||
const url = `/project/${this.$scope.project_id}/doc/${diff.doc.id}/version/${diff.fromV}/restore`
|
||||
return this.ide.$http.post(url, { _csrf: window.csrfToken })
|
||||
}
|
||||
|
||||
_parseDiff(diff) {
|
||||
let row = 0
|
||||
let column = 0
|
||||
const highlights = []
|
||||
let text = ''
|
||||
const iterable = diff.diff || []
|
||||
for (let i = 0; i < iterable.length; i++) {
|
||||
let endColumn, endRow
|
||||
const entry = iterable[i]
|
||||
let content = entry.u || entry.i || entry.d
|
||||
if (!content) {
|
||||
content = ''
|
||||
}
|
||||
text += content
|
||||
const lines = content.split('\n')
|
||||
const startRow = row
|
||||
const startColumn = column
|
||||
if (lines.length > 1) {
|
||||
endRow = startRow + lines.length - 1
|
||||
endColumn = lines[lines.length - 1].length
|
||||
} else {
|
||||
endRow = startRow
|
||||
endColumn = startColumn + lines[0].length
|
||||
}
|
||||
row = endRow
|
||||
column = endColumn
|
||||
|
||||
const range = {
|
||||
start: {
|
||||
row: startRow,
|
||||
column: startColumn,
|
||||
},
|
||||
end: {
|
||||
row: endRow,
|
||||
column: endColumn,
|
||||
},
|
||||
}
|
||||
|
||||
if (entry.i != null || entry.d != null) {
|
||||
const name = displayNameForUser(entry.meta.user)
|
||||
const date = moment(entry.meta.end_ts).format('Do MMM YYYY, h:mm a')
|
||||
if (entry.i != null) {
|
||||
highlights.push({
|
||||
label: `Added by ${name} on ${date}`,
|
||||
highlight: range,
|
||||
hue: ColorManager.getHueForUserId(
|
||||
entry.meta.user != null ? entry.meta.user.id : undefined
|
||||
),
|
||||
})
|
||||
} else if (entry.d != null) {
|
||||
highlights.push({
|
||||
label: `Deleted by ${name} on ${date}`,
|
||||
strikeThrough: range,
|
||||
hue: ColorManager.getHueForUserId(
|
||||
entry.meta.user != null ? entry.meta.user.id : undefined
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { text, highlights }
|
||||
}
|
||||
|
||||
_loadUpdates(updates) {
|
||||
if (updates == null) {
|
||||
updates = []
|
||||
}
|
||||
let previousUpdate =
|
||||
this.$scope.history.updates[this.$scope.history.updates.length - 1]
|
||||
|
||||
for (const update of Array.from(updates)) {
|
||||
update.pathnames = [] // Used for display
|
||||
const object = update.docs || {}
|
||||
for (const doc_id in object) {
|
||||
const doc = object[doc_id]
|
||||
doc.entity = this.ide.fileTreeManager.findEntityById(doc_id, {
|
||||
includeDeleted: true,
|
||||
})
|
||||
update.pathnames.push(doc.entity.name)
|
||||
}
|
||||
|
||||
for (const user of Array.from(update.meta.users || [])) {
|
||||
if (user != null) {
|
||||
user.hue = ColorManager.getHueForUserId(user.id)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
previousUpdate == null ||
|
||||
!moment(previousUpdate.meta.end_ts).isSame(update.meta.end_ts, 'day')
|
||||
) {
|
||||
update.meta.first_in_day = true
|
||||
}
|
||||
|
||||
update.selectedFrom = false
|
||||
update.selectedTo = false
|
||||
update.inSelection = false
|
||||
|
||||
previousUpdate = update
|
||||
}
|
||||
|
||||
const firstLoad = this.$scope.history.updates.length === 0
|
||||
|
||||
this.$scope.history.updates = this.$scope.history.updates.concat(updates)
|
||||
|
||||
if (firstLoad) {
|
||||
return this.autoSelectRecentUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
_calculateRangeFromSelection() {
|
||||
let end_ts, start_ts, toV
|
||||
let fromV = (toV = start_ts = end_ts = null)
|
||||
|
||||
const selected_doc_id =
|
||||
this.$scope.history.selection.doc != null
|
||||
? this.$scope.history.selection.doc.id
|
||||
: undefined
|
||||
|
||||
for (const update of Array.from(
|
||||
this.$scope.history.selection.updates || []
|
||||
)) {
|
||||
for (const doc_id in update.docs) {
|
||||
const doc = update.docs[doc_id]
|
||||
if (doc_id === selected_doc_id) {
|
||||
if (fromV != null && toV != null) {
|
||||
fromV = Math.min(fromV, doc.fromV)
|
||||
toV = Math.max(toV, doc.toV)
|
||||
start_ts = Math.min(start_ts, update.meta.start_ts)
|
||||
end_ts = Math.max(end_ts, update.meta.end_ts)
|
||||
} else {
|
||||
;({ fromV } = doc)
|
||||
;({ toV } = doc)
|
||||
;({ start_ts } = update.meta)
|
||||
;({ end_ts } = update.meta)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { fromV, toV, start_ts, end_ts }
|
||||
}
|
||||
|
||||
// Set the track changes selected doc to one of the docs in the range
|
||||
// of currently selected updates. If we already have a selected doc
|
||||
// then prefer this one if present.
|
||||
_selectDocFromUpdates() {
|
||||
let doc, doc_id
|
||||
const affected_docs = {}
|
||||
for (const update of Array.from(this.$scope.history.selection.updates)) {
|
||||
for (doc_id in update.docs) {
|
||||
doc = update.docs[doc_id]
|
||||
affected_docs[doc_id] = doc.entity
|
||||
}
|
||||
}
|
||||
|
||||
let selected_doc = this.$scope.history.selection.doc
|
||||
if (selected_doc != null && affected_docs[selected_doc.id] != null) {
|
||||
// Selected doc is already open
|
||||
} else {
|
||||
const doc_ids = Object.keys(affected_docs)
|
||||
if (doc_ids.length > 0) {
|
||||
const doc_id = doc_ids[0]
|
||||
doc = affected_docs[doc_id]
|
||||
selected_doc = doc
|
||||
}
|
||||
}
|
||||
|
||||
this.$scope.history.selection.doc = selected_doc
|
||||
return this.ide.fileTreeManager.selectEntity(selected_doc)
|
||||
}
|
||||
|
||||
_updateContainsUserId(update, user_id) {
|
||||
for (const user of Array.from(update.meta.users)) {
|
||||
if ((user != null ? user.id : undefined) === user_id) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
HistoryManager.initClass()
|
||||
return HistoryManager
|
||||
})()
|
|
@ -133,7 +133,6 @@ export default HistoryManager = (function () {
|
|||
|
||||
hardReset() {
|
||||
this.$scope.history = {
|
||||
isV2: true,
|
||||
updates: [],
|
||||
viewMode: this._getViewModeUserPref(),
|
||||
nextBeforeTimestamp: null,
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
/* eslint-disable
|
||||
camelcase,
|
||||
max-len,
|
||||
no-return-assign,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import App from '../../../base'
|
||||
App.controller(
|
||||
'HistoryDiffController',
|
||||
function ($scope, $modal, ide, eventTracking) {
|
||||
$scope.restoreDeletedDoc = function () {
|
||||
eventTracking.sendMB('history-restore-deleted')
|
||||
$scope.history.diff.restoreInProgress = true
|
||||
return ide.historyManager
|
||||
.restoreDeletedDoc($scope.history.diff.doc)
|
||||
.then(function (response) {
|
||||
const { data } = response
|
||||
$scope.history.diff.restoredDocNewId = data.doc_id
|
||||
$scope.history.diff.restoreInProgress = false
|
||||
return ($scope.history.diff.restoreDeletedSuccess = true)
|
||||
})
|
||||
}
|
||||
|
||||
$scope.openRestoreDiffModal = function () {
|
||||
eventTracking.sendMB('history-restore-modal')
|
||||
return $modal.open({
|
||||
templateUrl: 'historyRestoreDiffModalTemplate',
|
||||
controller: 'HistoryRestoreDiffModalController',
|
||||
resolve: {
|
||||
diff() {
|
||||
return $scope.history.diff
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return ($scope.backToEditorAfterRestore = () =>
|
||||
ide.editorManager.openDoc({ id: $scope.history.diff.restoredDocNewId }))
|
||||
}
|
||||
)
|
||||
|
||||
export default App.controller(
|
||||
'HistoryRestoreDiffModalController',
|
||||
function ($scope, $modalInstance, diff, ide, eventTracking) {
|
||||
$scope.state = { inflight: false }
|
||||
|
||||
$scope.diff = diff
|
||||
|
||||
$scope.restore = function () {
|
||||
eventTracking.sendMB('history-restored')
|
||||
$scope.state.inflight = true
|
||||
return ide.historyManager.restoreDiff(diff).then(function () {
|
||||
$scope.state.inflight = false
|
||||
$modalInstance.close()
|
||||
return ide.editorManager.openDoc(diff.doc)
|
||||
})
|
||||
}
|
||||
|
||||
return ($scope.cancel = () => $modalInstance.dismiss())
|
||||
}
|
||||
)
|
|
@ -1,218 +0,0 @@
|
|||
import _ from 'lodash'
|
||||
/* eslint-disable
|
||||
camelcase,
|
||||
max-len,
|
||||
no-return-assign,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS205: Consider reworking code to avoid use of IIFEs
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import App from '../../../base'
|
||||
import displayNameForUser from '../util/displayNameForUser'
|
||||
App.controller('HistoryListController', function ($scope, $modal, ide) {
|
||||
$scope.hoveringOverListSelectors = false
|
||||
|
||||
$scope.projectUsers = []
|
||||
|
||||
$scope.$watch('project.members', function (newVal) {
|
||||
if (newVal != null) {
|
||||
return ($scope.projectUsers = newVal.concat($scope.project.owner))
|
||||
}
|
||||
})
|
||||
|
||||
// This method (and maybe the one below) will be removed soon. User details data will be
|
||||
// injected into the history API responses, so we won't need to fetch user data from other
|
||||
// local data structures.
|
||||
const _getUserById = id =>
|
||||
_.find($scope.projectUsers, function (user) {
|
||||
const curUserId =
|
||||
(user != null ? user._id : undefined) ||
|
||||
(user != null ? user.id : undefined)
|
||||
return curUserId === id
|
||||
})
|
||||
|
||||
$scope.getDisplayNameById = id => displayNameForUser(_getUserById(id))
|
||||
|
||||
$scope.deleteLabel = labelDetails =>
|
||||
$modal.open({
|
||||
templateUrl: 'historyV2DeleteLabelModalTemplate',
|
||||
controller: 'HistoryV2DeleteLabelModalController',
|
||||
resolve: {
|
||||
labelDetails() {
|
||||
return labelDetails
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
$scope.loadMore = () => {
|
||||
return ide.historyManager.fetchNextBatchOfUpdates()
|
||||
}
|
||||
|
||||
$scope.recalculateSelectedUpdates = function () {
|
||||
let beforeSelection = true
|
||||
let afterSelection = false
|
||||
$scope.history.selection.updates = []
|
||||
return (() => {
|
||||
const result = []
|
||||
for (const update of Array.from($scope.history.updates)) {
|
||||
// replacing this declaration with `let` introduces a bug in history point selection:
|
||||
// https://github.com/overleaf/overleaf/issues/1035
|
||||
// eslint-disable-next-line no-var
|
||||
var inSelection
|
||||
|
||||
if (update.selectedTo) {
|
||||
inSelection = true
|
||||
beforeSelection = false
|
||||
}
|
||||
|
||||
update.beforeSelection = beforeSelection
|
||||
update.inSelection = inSelection
|
||||
update.afterSelection = afterSelection
|
||||
|
||||
if (inSelection) {
|
||||
$scope.history.selection.updates.push(update)
|
||||
}
|
||||
|
||||
if (update.selectedFrom) {
|
||||
inSelection = false
|
||||
result.push((afterSelection = true))
|
||||
} else {
|
||||
result.push(undefined)
|
||||
}
|
||||
}
|
||||
return result
|
||||
})()
|
||||
}
|
||||
|
||||
$scope.recalculateHoveredUpdates = function () {
|
||||
let inHoverSelection
|
||||
let hoverSelectedFrom = false
|
||||
let hoverSelectedTo = false
|
||||
for (const update of Array.from($scope.history.updates)) {
|
||||
// Figure out whether the to or from selector is hovered over
|
||||
if (update.hoverSelectedFrom) {
|
||||
hoverSelectedFrom = true
|
||||
}
|
||||
if (update.hoverSelectedTo) {
|
||||
hoverSelectedTo = true
|
||||
}
|
||||
}
|
||||
|
||||
if (hoverSelectedFrom) {
|
||||
// We want to 'hover select' everything between hoverSelectedFrom and selectedTo
|
||||
inHoverSelection = false
|
||||
for (const update of Array.from($scope.history.updates)) {
|
||||
if (update.selectedTo) {
|
||||
update.hoverSelectedTo = true
|
||||
inHoverSelection = true
|
||||
}
|
||||
update.inHoverSelection = inHoverSelection
|
||||
if (update.hoverSelectedFrom) {
|
||||
inHoverSelection = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hoverSelectedTo) {
|
||||
// We want to 'hover select' everything between hoverSelectedTo and selectedFrom
|
||||
inHoverSelection = false
|
||||
return (() => {
|
||||
const result = []
|
||||
for (const update of Array.from($scope.history.updates)) {
|
||||
if (update.hoverSelectedTo) {
|
||||
inHoverSelection = true
|
||||
}
|
||||
update.inHoverSelection = inHoverSelection
|
||||
if (update.selectedFrom) {
|
||||
update.hoverSelectedFrom = true
|
||||
result.push((inHoverSelection = false))
|
||||
} else {
|
||||
result.push(undefined)
|
||||
}
|
||||
}
|
||||
return result
|
||||
})()
|
||||
}
|
||||
}
|
||||
|
||||
$scope.resetHoverState = () =>
|
||||
(() => {
|
||||
const result = []
|
||||
for (const update of Array.from($scope.history.updates)) {
|
||||
delete update.hoverSelectedFrom
|
||||
delete update.hoverSelectedTo
|
||||
result.push(delete update.inHoverSelection)
|
||||
}
|
||||
return result
|
||||
})()
|
||||
|
||||
return $scope.$watch('history.updates.length', () =>
|
||||
$scope.recalculateSelectedUpdates()
|
||||
)
|
||||
})
|
||||
|
||||
export default App.controller(
|
||||
'HistoryListItemController',
|
||||
function ($scope, eventTracking) {
|
||||
$scope.$watch(
|
||||
'update.selectedFrom',
|
||||
function (selectedFrom, oldSelectedFrom) {
|
||||
if (selectedFrom) {
|
||||
for (const update of Array.from($scope.history.updates)) {
|
||||
if (update !== $scope.update) {
|
||||
update.selectedFrom = false
|
||||
}
|
||||
}
|
||||
return $scope.recalculateSelectedUpdates()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
$scope.$watch('update.selectedTo', function (selectedTo, oldSelectedTo) {
|
||||
if (selectedTo) {
|
||||
for (const update of Array.from($scope.history.updates)) {
|
||||
if (update !== $scope.update) {
|
||||
update.selectedTo = false
|
||||
}
|
||||
}
|
||||
return $scope.recalculateSelectedUpdates()
|
||||
}
|
||||
})
|
||||
|
||||
$scope.select = function () {
|
||||
eventTracking.sendMB('history-view-change')
|
||||
$scope.update.selectedTo = true
|
||||
return ($scope.update.selectedFrom = true)
|
||||
}
|
||||
|
||||
$scope.mouseOverSelectedFrom = function () {
|
||||
$scope.history.hoveringOverListSelectors = true
|
||||
$scope.update.hoverSelectedFrom = true
|
||||
return $scope.recalculateHoveredUpdates()
|
||||
}
|
||||
|
||||
$scope.mouseOutSelectedFrom = function () {
|
||||
$scope.history.hoveringOverListSelectors = false
|
||||
return $scope.resetHoverState()
|
||||
}
|
||||
|
||||
$scope.mouseOverSelectedTo = function () {
|
||||
$scope.history.hoveringOverListSelectors = true
|
||||
$scope.update.hoverSelectedTo = true
|
||||
return $scope.recalculateHoveredUpdates()
|
||||
}
|
||||
|
||||
$scope.mouseOutSelectedTo = function () {
|
||||
$scope.history.hoveringOverListSelectors = false
|
||||
return $scope.resetHoverState()
|
||||
}
|
||||
|
||||
return ($scope.displayName = displayNameForUser)
|
||||
}
|
||||
)
|
|
@ -389,7 +389,6 @@
|
|||
"delete_your_account": "Delete your account",
|
||||
"deleted_at": "Deleted At",
|
||||
"deleted_by_on": "Deleted by __name__ on __date__",
|
||||
"deleted_files": "Deleted Files",
|
||||
"deleting": "Deleting",
|
||||
"demonstrating_git_integration": "Demonstrating Git integration",
|
||||
"demonstrating_track_changes_feature": "Demonstrating Track Changes feature",
|
||||
|
@ -569,15 +568,11 @@
|
|||
"file_action_renamed": "Renamed",
|
||||
"file_already_exists": "A file or folder with this name already exists",
|
||||
"file_already_exists_in_this_location": "An item named <0>__fileName__</0> already exists in this location. If you wish to move this file, rename or remove the conflicting file and try again.",
|
||||
"file_has_been_deleted": "__filename__ has been deleted",
|
||||
"file_name": "File Name",
|
||||
"file_name_figure_modal": "File name",
|
||||
"file_name_in_this_project": "File Name In This Project",
|
||||
"file_name_in_this_project_figure_modal": "File name in this project",
|
||||
"file_outline": "File outline",
|
||||
"file_restored": "Your file (__filename__) has been recovered.",
|
||||
"file_restored_back_to_editor": "You can go back to the editor and work on it again.",
|
||||
"file_restored_back_to_editor_btn": "Back to editor",
|
||||
"file_size": "File size",
|
||||
"file_too_large": "File too large",
|
||||
"files_cannot_include_invalid_characters": "File name is empty or contains invalid characters",
|
||||
|
@ -1477,7 +1472,6 @@
|
|||
"restore": "Restore",
|
||||
"restore_file": "Restore file",
|
||||
"restore_to_any_older_version": "Restore to any older version",
|
||||
"restore_to_before_these_changes": "Restore to before these changes",
|
||||
"restoring": "Restoring",
|
||||
"restricted": "Restricted",
|
||||
"restricted_no_permission": "Restricted, sorry you don’t have permission to load this page.",
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
/* eslint-disable
|
||||
max-len,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const { expect } = require('chai')
|
||||
const _ = require('underscore')
|
||||
|
||||
const User = require('./helpers/User')
|
||||
const MockDocstoreApiClass = require('./mocks/MockDocstoreApi')
|
||||
const MockFilestoreApiClass = require('./mocks/MockFilestoreApi')
|
||||
|
||||
let MockDocstoreApi, MockFilestoreApi
|
||||
|
||||
before(function () {
|
||||
MockDocstoreApi = MockDocstoreApiClass.instance()
|
||||
MockFilestoreApi = MockFilestoreApiClass.instance()
|
||||
})
|
||||
|
||||
describe('RestoringFiles', function () {
|
||||
beforeEach(function (done) {
|
||||
this.owner = new User()
|
||||
return this.owner.login(error => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
return this.owner.createProject(
|
||||
'example-project',
|
||||
{ template: 'example' },
|
||||
(error, projectId) => {
|
||||
this.project_id = projectId
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('restoring a deleted doc', function () {
|
||||
beforeEach(function (done) {
|
||||
return this.owner.getProject(this.project_id, (error, project) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
this.doc = _.find(
|
||||
project.rootFolder[0].docs,
|
||||
doc => doc.name === 'main.tex'
|
||||
)
|
||||
return this.owner.request(
|
||||
{
|
||||
method: 'DELETE',
|
||||
url: `/project/${this.project_id}/doc/${this.doc._id}`,
|
||||
},
|
||||
(error, response, body) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
expect(response.statusCode).to.equal(204)
|
||||
return this.owner.request(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/project/${this.project_id}/doc/${this.doc._id}/restore`,
|
||||
json: {
|
||||
name: 'main.tex',
|
||||
},
|
||||
},
|
||||
(error, response, body) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body.doc_id).to.exist
|
||||
this.restored_doc_id = body.doc_id
|
||||
return done()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('should have restored the doc', function (done) {
|
||||
return this.owner.getProject(this.project_id, (error, project) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
const restoredDoc = _.find(
|
||||
project.rootFolder[0].docs,
|
||||
doc => doc.name === 'main.tex'
|
||||
)
|
||||
expect(restoredDoc._id.toString()).to.equal(this.restored_doc_id)
|
||||
expect(this.doc._id).to.not.equal(this.restored_doc_id)
|
||||
expect(
|
||||
MockDocstoreApi.docs[this.project_id][this.restored_doc_id].lines
|
||||
).to.deep.equal(
|
||||
MockDocstoreApi.docs[this.project_id][this.doc._id].lines
|
||||
)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -15,7 +15,6 @@ import HistoryV2Manager from '../../../../frontend/js/ide/history/HistoryV2Manag
|
|||
export default describe('HistoryV2Manager', function () {
|
||||
beforeEach(function (done) {
|
||||
this.defaultHistoryScope = {
|
||||
isV2: true,
|
||||
updates: [],
|
||||
viewMode: 'point_in_time',
|
||||
nextBeforeTimestamp: null,
|
||||
|
|
Loading…
Add table
Reference in a new issue