mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-11 13:34:00 +00:00
Merge pull request #612 from sharelatex/pr-v2-history-ui
V2 history point-in-time-view
This commit is contained in:
commit
aeefd0752b
27 changed files with 1258 additions and 139 deletions
|
@ -207,6 +207,7 @@ module.exports = class Router
|
|||
webRouter.get "/project/:Project_id/updates", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApiAndInjectUserDetails
|
||||
webRouter.get "/project/:Project_id/doc/:doc_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
||||
webRouter.get "/project/:Project_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApiAndInjectUserDetails
|
||||
webRouter.get "/project/:Project_id/filetree/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
||||
webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
||||
webRouter.post '/project/:project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreDocFromDeletedDoc
|
||||
webRouter.post "/project/:project_id/restore_file", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreFileFromV2
|
||||
|
|
|
@ -57,9 +57,12 @@ block content
|
|||
include ./editor/share
|
||||
!= moduleIncludes("publish:body", locals)
|
||||
|
||||
include ./editor/history/toolbarV2.pug
|
||||
|
||||
main#ide-body(
|
||||
ng-cloak,
|
||||
role="main",
|
||||
ng-class="{ 'ide-history-open' : (ui.view == 'history' && history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME) }",
|
||||
layout="main",
|
||||
ng-hide="state.loading",
|
||||
resize-on="layout:chat:resize",
|
||||
|
@ -70,7 +73,7 @@ block content
|
|||
)
|
||||
.ui-layout-west
|
||||
include ./editor/file-tree
|
||||
include ./editor/history-file-tree
|
||||
include ./editor/history/fileTreeV2
|
||||
|
||||
.ui-layout-center
|
||||
include ./editor/editor
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
aside.file-tree.file-tree-history(ng-controller="FileTreeController", ng-class="{ 'multi-selected': multiSelectedCount > 0 }", ng-show="ui.view == 'history' && history.isV2").full-size
|
||||
.toolbar.toolbar-filetree
|
||||
span Modified files
|
||||
|
||||
.file-tree-inner
|
||||
ul.list-unstyled.file-tree-list
|
||||
li(
|
||||
ng-repeat="(pathname, doc) in history.selection.docs"
|
||||
ng-class="{ 'selected': history.selection.pathname == pathname }"
|
||||
)
|
||||
.entity
|
||||
.entity-name.entity-name-history(
|
||||
ng-click="history.selection.pathname = pathname",
|
||||
ng-class="{ 'deleted': !!doc.deletedAtV }"
|
||||
)
|
||||
i.fa.fa-fw.fa-pencil
|
||||
span {{ pathname }}
|
|
@ -40,90 +40,11 @@ div#history(ng-show="ui.view == 'history'")
|
|||
p
|
||||
a.small(href, ng-click="toggleHistory()") #{translate("cancel")}
|
||||
|
||||
aside.change-list(
|
||||
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")
|
||||
| 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 Renamed
|
||||
.doc {{ project_op.rename.pathname }} → {{ project_op.rename.newPathname }}
|
||||
div(ng-if="project_op.add")
|
||||
.action Created
|
||||
.doc {{ project_op.add.pathname }}
|
||||
div(ng-if="project_op.remove")
|
||||
.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")}...
|
||||
include ./history/entriesListV1
|
||||
include ./history/entriesListV2
|
||||
|
||||
include ./history/diffPanelV1
|
||||
include ./history/diffPanelV2
|
||||
include ./history/previewPanelV2
|
||||
|
||||
script(type="text/ng-template", id="historyRestoreDiffModalTemplate")
|
||||
.modal-header
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
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")}...
|
174
services/web/app/views/project/editor/history/entriesListV2.pug
Normal file
174
services/web/app/views/project/editor/history/entriesListV2.pug
Normal file
|
@ -0,0 +1,174 @@
|
|||
aside.change-list(
|
||||
ng-if="history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME"
|
||||
ng-controller="HistoryV2ListController"
|
||||
)
|
||||
history-entries-list(
|
||||
entries="history.updates"
|
||||
current-user="user"
|
||||
load-entries="loadMore()"
|
||||
load-disabled="history.loading || history.atEnd"
|
||||
load-initialize="ui.view == 'history'"
|
||||
is-loading="history.loading"
|
||||
on-entry-select="handleEntrySelect(selectedEntry)"
|
||||
)
|
||||
|
||||
aside.change-list(
|
||||
ng-if="history.isV2 && history.viewMode === HistoryViewModes.COMPARE"
|
||||
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")}...
|
||||
|
||||
script(type="text/ng-template", id="historyEntriesListTpl")
|
||||
.history-entries(
|
||||
infinite-scroll="$ctrl.loadEntries()"
|
||||
infinite-scroll-disabled="$ctrl.loadDisabled"
|
||||
infinite-scroll-initialize="$ctrl.loadInitialize"
|
||||
)
|
||||
.infinite-scroll-inner
|
||||
history-entry(
|
||||
ng-repeat="entry in $ctrl.entries"
|
||||
entry="entry"
|
||||
current-user="$ctrl.currentUser"
|
||||
on-select="$ctrl.onEntrySelect({ selectedEntry: selectedEntry })"
|
||||
ng-show="!$ctrl.isLoading"
|
||||
)
|
||||
.loading(ng-show="$ctrl.isLoading")
|
||||
i.fa.fa-spin.fa-refresh
|
||||
| #{translate("loading")}...
|
||||
|
||||
script(type="text/ng-template", id="historyEntryTpl")
|
||||
.history-entry(
|
||||
ng-class="{\
|
||||
'history-entry-first-in-day': $ctrl.entry.meta.first_in_day,\
|
||||
'history-entry-selected': $ctrl.entry.inSelection,\
|
||||
'history-entry-selected-to': $ctrl.entry.selectedTo,\
|
||||
'history-entry-selected-from': $ctrl.entry.selectedFrom,\
|
||||
'history-entry-hover-selected': $ctrl.entry.inHoverSelection,\
|
||||
'history-entry-hover-selected-to': $ctrl.entry.hoverSelectedTo,\
|
||||
'history-entry-hover-selected-from': $ctrl.entry.hoverSelectedFrom,\
|
||||
}"
|
||||
)
|
||||
|
||||
time.history-entry-day(ng-if="::$ctrl.entry.meta.first_in_day") {{ ::$ctrl.entry.meta.end_ts | relativeDate }}
|
||||
|
||||
.history-entry-details(ng-click="$ctrl.onSelect({ selectedEntry: $ctrl.entry })")
|
||||
ol.history-entry-changes
|
||||
li.history-entry-change(
|
||||
ng-repeat="pathname in ::$ctrl.entry.pathnames"
|
||||
)
|
||||
span.history-entry-change-action #{translate("file_action_edited")}
|
||||
span.history-entry-change-doc {{ ::pathname }}
|
||||
li.history-entry-change(
|
||||
ng-repeat="project_op in ::$ctrl.entry.project_ops"
|
||||
)
|
||||
span.history-entry-change-action(
|
||||
ng-if="::project_op.rename"
|
||||
) #{translate("file_action_renamed")}
|
||||
span.history-entry-change-action(
|
||||
ng-if="::project_op.add"
|
||||
) #{translate("file_action_created")}
|
||||
span.history-entry-change-action(
|
||||
ng-if="::project_op.remove"
|
||||
) #{translate("file_action_deleted")}
|
||||
span.history-entry-change-doc {{ ::$ctrl.getProjectOpDoc(project_op) }}
|
||||
.history-entry-metadata
|
||||
time.history-entry-metadata-time {{ ::$ctrl.entry.meta.end_ts | formatDate:'h:mm a' }}
|
||||
span
|
||||
|
|
||||
| •
|
||||
|
|
||||
ol.history-entry-metadata-users
|
||||
li.history-entry-metadata-user(ng-repeat="update_user in ::$ctrl.entry.meta.users")
|
||||
span.name(
|
||||
ng-if="::update_user && update_user.id != $ctrl.currentUser.id"
|
||||
ng-style="$ctrl.getUserCSSStyle(update_user);"
|
||||
) {{ ::$ctrl.displayName(update_user) }}
|
||||
span.name(
|
||||
ng-if="::update_user && update_user.id == $ctrl.currentUser.id"
|
||||
ng-style="$ctrl.getUserCSSStyle(update_user);"
|
||||
) You
|
||||
span.name(
|
||||
ng-if="::update_user == null"
|
||||
ng-style="$ctrl.getUserCSSStyle(update_user);"
|
||||
) #{translate("anonymous")}
|
||||
li.history-entry-metadata-user(ng-if="::$ctrl.entry.meta.users.length == 0")
|
||||
span.name(
|
||||
ng-style="$ctrl.getUserCSSStyle();"
|
||||
) #{translate("anonymous")}
|
70
services/web/app/views/project/editor/history/fileTreeV2.pug
Normal file
70
services/web/app/views/project/editor/history/fileTreeV2.pug
Normal file
|
@ -0,0 +1,70 @@
|
|||
aside.file-tree.full-size(
|
||||
ng-controller="HistoryV2FileTreeController"
|
||||
ng-if="ui.view == 'history' && history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME"
|
||||
)
|
||||
.history-file-tree-inner
|
||||
history-file-tree(
|
||||
file-tree="currentFileTree"
|
||||
selected-pathname="history.selection.pathname"
|
||||
on-selected-file-change="handleFileSelection(file)"
|
||||
is-loading="history.loadingFileTree"
|
||||
)
|
||||
|
||||
aside.file-tree.file-tree-history.full-size(
|
||||
ng-controller="FileTreeController"
|
||||
ng-class="{ 'multi-selected': multiSelectedCount > 0 }"
|
||||
ng-show="ui.view == 'history' && history.isV2 && history.viewMode === HistoryViewModes.COMPARE")
|
||||
.toolbar.toolbar-filetree
|
||||
span Modified files
|
||||
|
||||
.file-tree-inner
|
||||
ul.list-unstyled.file-tree-list
|
||||
li(
|
||||
ng-repeat="(pathname, doc) in history.selection.docs"
|
||||
ng-class="{ 'selected': history.selection.pathname == pathname }"
|
||||
)
|
||||
.entity
|
||||
.entity-name.entity-name-history(
|
||||
ng-click="history.selection.pathname = pathname",
|
||||
ng-class="{ 'deleted': !!doc.deletedAtV }"
|
||||
)
|
||||
i.fa.fa-fw.fa-pencil
|
||||
span {{ pathname }}
|
||||
|
||||
|
||||
|
||||
|
||||
script(type="text/ng-template", id="historyFileTreeTpl")
|
||||
.history-file-tree
|
||||
history-file-entity(
|
||||
ng-repeat="fileEntity in $ctrl.fileTree"
|
||||
file-entity="fileEntity"
|
||||
ng-show="!$ctrl.isLoading"
|
||||
)
|
||||
|
||||
|
||||
script(type="text/ng-template", id="historyFileEntityTpl")
|
||||
.history-file-entity-wrapper
|
||||
a.history-file-entity-link(
|
||||
href
|
||||
ng-click="$ctrl.handleClick()"
|
||||
ng-class="{ 'history-file-entity-link-selected': $ctrl.isSelected }"
|
||||
)
|
||||
span.history-file-entity-name
|
||||
i.history-file-entity-icon.history-file-entity-icon-folder-state.fa.fa-fw(
|
||||
ng-class="{\
|
||||
'fa-chevron-down': ($ctrl.fileEntity.type === 'folder' && $ctrl.isOpen),\
|
||||
'fa-chevron-right': ($ctrl.fileEntity.type === 'folder' && !$ctrl.isOpen)\
|
||||
}"
|
||||
)
|
||||
i.history-file-entity-icon.fa(
|
||||
ng-class="$ctrl.iconClass"
|
||||
)
|
||||
| {{ ::$ctrl.fileEntity.name }}
|
||||
div(
|
||||
ng-show="$ctrl.isOpen"
|
||||
)
|
||||
history-file-entity(
|
||||
ng-repeat="childEntity in $ctrl.fileEntity.children"
|
||||
file-entity="childEntity"
|
||||
)
|
|
@ -1,4 +1,7 @@
|
|||
.diff-panel.full-size(ng-if="history.isV2", ng-controller="HistoryV2DiffController")
|
||||
.diff-panel.full-size(
|
||||
ng-if="history.isV2 && history.viewMode === HistoryViewModes.COMPARE"
|
||||
ng-controller="HistoryV2DiffController"
|
||||
)
|
||||
.diff(
|
||||
ng-if="!!history.diff && !history.diff.loading && !history.diff.error",
|
||||
ng-class="{ 'diff-binary': history.diff.binary }"
|
||||
|
@ -16,8 +19,13 @@
|
|||
}"
|
||||
)
|
||||
| in <strong>{{history.diff.pathname}}</strong>
|
||||
.history-toolbar-btn(
|
||||
ng-click="toggleHistoryViewMode();"
|
||||
)
|
||||
i.fa
|
||||
| #{translate("view_single_version")}
|
||||
.toolbar-right(ng-if="history.selection.docs[history.selection.pathname].deletedAtV")
|
||||
button.btn.btn-danger.btn-sm(
|
||||
button.btn.btn-danger.btn-xs(
|
||||
ng-click="restoreDeletedFile()"
|
||||
ng-show="!restoreState.error"
|
||||
ng-disabled="restoreState.inflight"
|
||||
|
@ -47,4 +55,27 @@
|
|||
i.fa.fa-spin.fa-refresh
|
||||
| #{translate("loading")}...
|
||||
.error-panel(ng-show="history.diff.error")
|
||||
.alert.alert-danger #{translate("generic_something_went_wrong")}
|
||||
.alert.alert-danger #{translate("generic_something_went_wrong")}
|
||||
|
||||
.point-in-time-panel.full-size(
|
||||
ng-if="history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME"
|
||||
)
|
||||
.point-in-time-editor-container(
|
||||
ng-if="!!history.selectedFile && !history.selectedFile.loading && !history.selectedFile.error"
|
||||
)
|
||||
.hide-ace-cursor(
|
||||
ng-if="!history.selectedFile.binary"
|
||||
ace-editor="history-pointintime",
|
||||
theme="settings.theme",
|
||||
font-size="settings.fontSize",
|
||||
text="history.selectedFile.text",
|
||||
read-only="true",
|
||||
resize-on="layout:main:resize",
|
||||
)
|
||||
.alert.alert-info(ng-if="history.selectedFile.binary")
|
||||
| We're still working on showing image and binary changes, sorry. Stay tuned!
|
||||
.loading-panel(ng-show="history.selectedFile.loading")
|
||||
i.fa.fa-spin.fa-refresh
|
||||
| #{translate("loading")}...
|
||||
.error-panel(ng-show="history.selectedFile.error")
|
||||
.alert.alert-danger #{translate("generic_something_went_wrong")}
|
13
services/web/app/views/project/editor/history/toolbarV2.pug
Normal file
13
services/web/app/views/project/editor/history/toolbarV2.pug
Normal file
|
@ -0,0 +1,13 @@
|
|||
.history-toolbar(
|
||||
ng-if="ui.view == 'history' && history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME"
|
||||
)
|
||||
span(ng-show="history.loadingFileTree")
|
||||
i.fa.fa-spin.fa-refresh
|
||||
| #{translate("loading")}...
|
||||
span(ng-show="!history.loadingFileTree") #{translate("browsing_project_as_of")}
|
||||
time.history-toolbar-time {{ history.selection.updates[0].meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }}
|
||||
.history-toolbar-btn(
|
||||
ng-click="toggleHistoryViewMode();"
|
||||
)
|
||||
i.fa
|
||||
| #{translate("compare_to_another_version")}
|
|
@ -1,6 +1,7 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
"ide/file-tree/util/iconTypeFromName"
|
||||
], (App, iconTypeFromName) ->
|
||||
App.controller "FileTreeEntityController", ["$scope", "ide", "$modal", ($scope, ide, $modal) ->
|
||||
$scope.select = (e) ->
|
||||
if e.ctrlKey or e.metaKey
|
||||
|
@ -70,18 +71,7 @@ define [
|
|||
$scope.$on "delete:selected", () ->
|
||||
$scope.openDeleteModal() if $scope.entity.selected
|
||||
|
||||
$scope.iconTypeFromName = (name) ->
|
||||
ext = name.split(".").pop()?.toLowerCase()
|
||||
if ext in ["png", "pdf", "jpg", "jpeg", "gif"]
|
||||
return "image"
|
||||
else if ext in ["csv", "xls", "xlsx"]
|
||||
return "table"
|
||||
else if ext in ["py", "r"]
|
||||
return "file-text"
|
||||
else if ext in ['bib']
|
||||
return 'book'
|
||||
else
|
||||
return "file"
|
||||
$scope.iconTypeFromName = iconTypeFromName
|
||||
]
|
||||
|
||||
App.controller "DeleteEntityModalController", [
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
define [], () ->
|
||||
return iconTypeFromName = (name) ->
|
||||
ext = name.split(".").pop()?.toLowerCase()
|
||||
if ext in ["png", "pdf", "jpg", "jpeg", "gif"]
|
||||
return "image"
|
||||
else if ext in ["csv", "xls", "xlsx"]
|
||||
return "table"
|
||||
else if ext in ["py", "r"]
|
||||
return "file-text"
|
||||
else if ext in ['bib']
|
||||
return 'book'
|
||||
else
|
||||
return "file"
|
|
@ -4,7 +4,6 @@ define [
|
|||
"ide/history/util/displayNameForUser"
|
||||
"ide/history/controllers/HistoryListController"
|
||||
"ide/history/controllers/HistoryDiffController"
|
||||
"ide/history/controllers/HistoryV2DiffController"
|
||||
"ide/history/directives/infiniteScroll"
|
||||
], (moment, ColorManager, displayNameForUser) ->
|
||||
class HistoryManager
|
||||
|
|
|
@ -2,13 +2,20 @@ define [
|
|||
"moment"
|
||||
"ide/colors/ColorManager"
|
||||
"ide/history/util/displayNameForUser"
|
||||
"ide/history/controllers/HistoryListController"
|
||||
"ide/history/controllers/HistoryDiffController"
|
||||
"ide/history/util/HistoryViewModes"
|
||||
"ide/history/controllers/HistoryV2ListController"
|
||||
"ide/history/controllers/HistoryV2DiffController"
|
||||
"ide/history/controllers/HistoryV2FileTreeController"
|
||||
"ide/history/directives/infiniteScroll"
|
||||
], (moment, ColorManager, displayNameForUser) ->
|
||||
"ide/history/components/historyEntriesList"
|
||||
"ide/history/components/historyEntry"
|
||||
"ide/history/components/historyFileTree"
|
||||
"ide/history/components/historyFileEntity"
|
||||
], (moment, ColorManager, displayNameForUser, HistoryViewModes) ->
|
||||
class HistoryManager
|
||||
constructor: (@ide, @$scope) ->
|
||||
@reset()
|
||||
@$scope.HistoryViewModes = HistoryViewModes
|
||||
|
||||
@$scope.toggleHistory = () =>
|
||||
if @$scope.ui.view == "history"
|
||||
|
@ -16,17 +23,31 @@ define [
|
|||
else
|
||||
@show()
|
||||
|
||||
@$scope.$watch "history.selection.updates", (updates) =>
|
||||
if updates? and updates.length > 0
|
||||
@_selectDocFromUpdates()
|
||||
@reloadDiff()
|
||||
@$scope.toggleHistoryViewMode = () =>
|
||||
if @$scope.history.viewMode == HistoryViewModes.COMPARE
|
||||
@reset()
|
||||
@$scope.history.viewMode = HistoryViewModes.POINT_IN_TIME
|
||||
else
|
||||
@reset()
|
||||
@$scope.history.viewMode = HistoryViewModes.COMPARE
|
||||
|
||||
@$scope.$watch "history.selection.pathname", () =>
|
||||
@reloadDiff()
|
||||
@$scope.$watch "history.selection.updates", (updates) =>
|
||||
if @$scope.history.viewMode == HistoryViewModes.COMPARE
|
||||
if updates? and updates.length > 0
|
||||
@_selectDocFromUpdates()
|
||||
@reloadDiff()
|
||||
|
||||
@$scope.$watch "history.selection.pathname", (pathname) =>
|
||||
if @$scope.history.viewMode == HistoryViewModes.POINT_IN_TIME
|
||||
if pathname?
|
||||
@loadFileAtPointInTime()
|
||||
else
|
||||
@reloadDiff()
|
||||
|
||||
show: () ->
|
||||
@$scope.ui.view = "history"
|
||||
@reset()
|
||||
@$scope.history.viewMode = HistoryViewModes.POINT_IN_TIME
|
||||
|
||||
hide: () ->
|
||||
@$scope.ui.view = "editor"
|
||||
|
@ -35,6 +56,7 @@ define [
|
|||
@$scope.history = {
|
||||
isV2: true
|
||||
updates: []
|
||||
viewMode: null
|
||||
nextBeforeTimestamp: null
|
||||
atEnd: false
|
||||
selection: {
|
||||
|
@ -46,16 +68,33 @@ define [
|
|||
toV: null
|
||||
}
|
||||
}
|
||||
diff: null
|
||||
files: []
|
||||
diff: null # When history.viewMode == HistoryViewModes.COMPARE
|
||||
selectedFile: null # When history.viewMode == HistoryViewModes.POINT_IN_TIME
|
||||
}
|
||||
|
||||
restoreFile: (version, pathname) ->
|
||||
url = "/project/#{@$scope.project_id}/restore_file"
|
||||
|
||||
@ide.$http.post(url, {
|
||||
version, pathname,
|
||||
_csrf: window.csrfToken
|
||||
})
|
||||
|
||||
loadFileTreeForUpdate: (update) ->
|
||||
{fromV, toV} = update
|
||||
url = "/project/#{@$scope.project_id}/filetree/diff"
|
||||
query = [ "from=#{toV}", "to=#{toV}" ]
|
||||
url += "?" + query.join("&")
|
||||
@$scope.history.loadingFileTree = true
|
||||
@$scope.history.selectedFile = null
|
||||
@$scope.history.selection.pathname = null
|
||||
@ide.$http
|
||||
.get(url)
|
||||
.then (response) =>
|
||||
@$scope.history.files = response.data.diff
|
||||
@$scope.history.loadingFileTree = false
|
||||
|
||||
MAX_RECENT_UPDATES_TO_SELECT: 5
|
||||
autoSelectRecentUpdates: () ->
|
||||
return if @$scope.history.updates.length == 0
|
||||
|
@ -70,12 +109,28 @@ define [
|
|||
|
||||
@$scope.history.updates[indexOfLastUpdateNotByMe].selectedFrom = true
|
||||
|
||||
autoSelectLastUpdate: () ->
|
||||
return if @$scope.history.updates.length == 0
|
||||
@selectUpdate @$scope.history.updates[0]
|
||||
|
||||
selectUpdate: (update) ->
|
||||
selectedUpdateIndex = @$scope.history.updates.indexOf update
|
||||
if selectedUpdateIndex == -1
|
||||
selectedUpdateIndex = 0
|
||||
for update in @$scope.history.updates
|
||||
update.selectedTo = false
|
||||
update.selectedFrom = false
|
||||
@$scope.history.updates[selectedUpdateIndex].selectedTo = true
|
||||
@$scope.history.updates[selectedUpdateIndex].selectedFrom = true
|
||||
@loadFileTreeForUpdate @$scope.history.updates[selectedUpdateIndex]
|
||||
|
||||
BATCH_SIZE: 10
|
||||
fetchNextBatchOfUpdates: () ->
|
||||
url = "/project/#{@ide.project_id}/updates?min_count=#{@BATCH_SIZE}"
|
||||
if @$scope.history.nextBeforeTimestamp?
|
||||
url += "&before=#{@$scope.history.nextBeforeTimestamp}"
|
||||
@$scope.history.loading = true
|
||||
@$scope.history.loadingFileTree = true
|
||||
@ide.$http
|
||||
.get(url)
|
||||
.then (response) =>
|
||||
|
@ -86,6 +141,23 @@ define [
|
|||
@$scope.history.atEnd = true
|
||||
@$scope.history.loading = false
|
||||
|
||||
loadFileAtPointInTime: () ->
|
||||
pathname = @$scope.history.selection.pathname
|
||||
toV = @$scope.history.selection.updates[0].toV
|
||||
url = "/project/#{@$scope.project_id}/diff"
|
||||
query = ["pathname=#{encodeURIComponent(pathname)}", "from=#{toV}", "to=#{toV}"]
|
||||
url += "?" + query.join("&")
|
||||
@$scope.history.selectedFile =
|
||||
loading: true
|
||||
@ide.$http
|
||||
.get(url)
|
||||
.then (response) =>
|
||||
{text, binary} = @_parseDiff(response.data.diff)
|
||||
@$scope.history.selectedFile.binary = binary
|
||||
@$scope.history.selectedFile.text = text
|
||||
@$scope.history.selectedFile.loading = false
|
||||
.catch () ->
|
||||
|
||||
reloadDiff: () ->
|
||||
diff = @$scope.history.diff
|
||||
{updates} = @$scope.history.selection
|
||||
|
@ -200,7 +272,11 @@ define [
|
|||
@$scope.history.updates =
|
||||
@$scope.history.updates.concat(updates)
|
||||
|
||||
@autoSelectRecentUpdates() if firstLoad
|
||||
if firstLoad
|
||||
if @$scope.history.viewMode == HistoryViewModes.COMPARE
|
||||
@autoSelectRecentUpdates()
|
||||
else
|
||||
@autoSelectLastUpdate()
|
||||
|
||||
_perDocSummaryOfUpdates: (updates) ->
|
||||
# Track current_pathname -> original_pathname
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
historyEntriesListController = ($scope, $element, $attrs) ->
|
||||
ctrl = @
|
||||
return
|
||||
|
||||
App.component "historyEntriesList", {
|
||||
bindings:
|
||||
entries: "<"
|
||||
loadEntries: "&"
|
||||
loadDisabled: "<"
|
||||
loadInitialize: "<"
|
||||
isLoading: "<"
|
||||
currentUser: "<"
|
||||
onEntrySelect: "&"
|
||||
controller: historyEntriesListController
|
||||
templateUrl: "historyEntriesListTpl"
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
define [
|
||||
"base"
|
||||
"ide/history/util/displayNameForUser"
|
||||
], (App, displayNameForUser) ->
|
||||
historyEntryController = ($scope, $element, $attrs) ->
|
||||
ctrl = @
|
||||
ctrl.displayName = displayNameForUser
|
||||
ctrl.getProjectOpDoc = (projectOp) ->
|
||||
if projectOp.rename? then "#{ projectOp.rename.pathname} → #{ projectOp.rename.newPathname }"
|
||||
else if projectOp.add? then "#{ projectOp.add.pathname}"
|
||||
else if projectOp.remove? then "#{ projectOp.remove.pathname}"
|
||||
ctrl.getUserCSSStyle = (user) ->
|
||||
hue = user?.hue or 100
|
||||
if ctrl.entry.inSelection
|
||||
color : "#FFF"
|
||||
else
|
||||
color: "hsl(#{ hue }, 70%, 50%)"
|
||||
return
|
||||
|
||||
App.component "historyEntry", {
|
||||
bindings:
|
||||
entry: "<"
|
||||
currentUser: "<"
|
||||
onSelect: "&"
|
||||
controller: historyEntryController
|
||||
templateUrl: "historyEntryTpl"
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
define [
|
||||
"base"
|
||||
"ide/file-tree/util/iconTypeFromName"
|
||||
], (App, iconTypeFromName) ->
|
||||
# TODO Add arrows in folders
|
||||
historyFileEntityController = ($scope, $element, $attrs) ->
|
||||
ctrl = @
|
||||
_handleFolderClick = () ->
|
||||
ctrl.isOpen = !ctrl.isOpen
|
||||
ctrl.iconClass = _getFolderIcon()
|
||||
_handleFileClick = () ->
|
||||
ctrl.historyFileTreeController.handleEntityClick ctrl.fileEntity
|
||||
_getFolderIcon = () ->
|
||||
if ctrl.isOpen then "fa-folder-open" else "fa-folder"
|
||||
ctrl.$onInit = () ->
|
||||
if ctrl.fileEntity.type == "folder"
|
||||
ctrl.isOpen = true
|
||||
ctrl.iconClass = _getFolderIcon()
|
||||
ctrl.handleClick = _handleFolderClick
|
||||
else
|
||||
ctrl.iconClass = "fa-#{ iconTypeFromName(ctrl.fileEntity.name) }"
|
||||
ctrl.handleClick = _handleFileClick
|
||||
$scope.$watch (() -> ctrl.historyFileTreeController.selectedPathname), (newPathname) ->
|
||||
ctrl.isSelected = ctrl.fileEntity.pathname == newPathname
|
||||
return
|
||||
|
||||
App.component "historyFileEntity", {
|
||||
require:
|
||||
historyFileTreeController: "^historyFileTree"
|
||||
bindings:
|
||||
fileEntity: "<"
|
||||
controller: historyFileEntityController
|
||||
templateUrl: "historyFileEntityTpl"
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
historyFileTreeController = ($scope, $element, $attrs) ->
|
||||
ctrl = @
|
||||
ctrl.handleEntityClick = (file) ->
|
||||
ctrl.onSelectedFileChange file: file
|
||||
return
|
||||
|
||||
App.component "historyFileTree", {
|
||||
bindings:
|
||||
fileTree: "<"
|
||||
selectedPathname: "<"
|
||||
onSelectedFileChange: "&"
|
||||
isLoading: "<"
|
||||
controller: historyFileTreeController
|
||||
templateUrl: "historyFileTreeTpl"
|
||||
}
|
|
@ -5,7 +5,7 @@ define [
|
|||
|
||||
App.controller "HistoryListController", ["$scope", "ide", ($scope, ide) ->
|
||||
$scope.hoveringOverListSelectors = false
|
||||
|
||||
|
||||
$scope.loadMore = () =>
|
||||
ide.historyManager.fetchNextBatchOfUpdates()
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
|
||||
App.controller "HistoryV2FileTreeController", ["$scope", "ide", "_", ($scope, ide, _) ->
|
||||
_previouslySelectedPathname = null
|
||||
$scope.currentFileTree = []
|
||||
|
||||
_pathnameExistsInFiles = (pathname, files) ->
|
||||
_.any files, (file) -> file.pathname == pathname
|
||||
|
||||
_getSelectedDefaultPathname = (files) ->
|
||||
selectedPathname = null
|
||||
if _previouslySelectedPathname? and _pathnameExistsInFiles _previouslySelectedPathname, files
|
||||
selectedPathname = _previouslySelectedPathname
|
||||
else
|
||||
mainFile = _.find files, (file) -> /main\.tex$/.test file.pathname
|
||||
if mainFile?
|
||||
selectedPathname = _previouslySelectedPathname = mainFile.pathname
|
||||
else
|
||||
selectedPathname = _previouslySelectedPathname = files[0].pathname
|
||||
return selectedPathname
|
||||
|
||||
$scope.handleFileSelection = (file) ->
|
||||
$scope.history.selection.pathname = _previouslySelectedPathname = file.pathname
|
||||
|
||||
$scope.$watch 'history.files', (files) ->
|
||||
if files? and files.length > 0
|
||||
$scope.currentFileTree = _.reduce files, _reducePathsToTree, []
|
||||
$scope.history.selection.pathname = _getSelectedDefaultPathname(files)
|
||||
|
||||
_reducePathsToTree = (currentFileTree, fileObject) ->
|
||||
filePathParts = fileObject.pathname.split "/"
|
||||
currentFileTreeLocation = currentFileTree
|
||||
for pathPart, index in filePathParts
|
||||
isFile = index == filePathParts.length - 1
|
||||
if isFile
|
||||
fileTreeEntity =
|
||||
name: pathPart
|
||||
pathname: fileObject.pathname
|
||||
type: "file"
|
||||
operation: fileObject.operation || "edited"
|
||||
currentFileTreeLocation.push fileTreeEntity
|
||||
else
|
||||
fileTreeEntity = _.find currentFileTreeLocation, (entity) => entity.name == pathPart
|
||||
if !fileTreeEntity?
|
||||
fileTreeEntity =
|
||||
name: pathPart
|
||||
type: "folder"
|
||||
children: []
|
||||
currentFileTreeLocation.push fileTreeEntity
|
||||
currentFileTreeLocation = fileTreeEntity.children
|
||||
return currentFileTree
|
||||
]
|
|
@ -0,0 +1,76 @@
|
|||
define [
|
||||
"base",
|
||||
"ide/history/util/displayNameForUser"
|
||||
], (App, displayNameForUser) ->
|
||||
|
||||
App.controller "HistoryV2ListController", ["$scope", "ide", ($scope, ide) ->
|
||||
$scope.hoveringOverListSelectors = false
|
||||
|
||||
$scope.loadMore = () =>
|
||||
ide.historyManager.fetchNextBatchOfUpdates()
|
||||
|
||||
$scope.handleEntrySelect = (entry) ->
|
||||
# $scope.$applyAsync () ->
|
||||
ide.historyManager.selectUpdate(entry)
|
||||
$scope.recalculateSelectedUpdates()
|
||||
|
||||
$scope.recalculateSelectedUpdates = () ->
|
||||
beforeSelection = true
|
||||
afterSelection = false
|
||||
$scope.history.selection.updates = []
|
||||
for update in $scope.history.updates
|
||||
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
|
||||
afterSelection = true
|
||||
|
||||
$scope.recalculateHoveredUpdates = () ->
|
||||
hoverSelectedFrom = false
|
||||
hoverSelectedTo = false
|
||||
for update in $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 update in $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
|
||||
for update in $scope.history.updates
|
||||
if update.hoverSelectedTo
|
||||
inHoverSelection = true
|
||||
update.inHoverSelection = inHoverSelection
|
||||
if update.selectedFrom
|
||||
update.hoverSelectedFrom = true
|
||||
inHoverSelection = false
|
||||
|
||||
$scope.resetHoverState = () ->
|
||||
for update in $scope.history.updates
|
||||
delete update.hoverSelectedFrom
|
||||
delete update.hoverSelectedTo
|
||||
delete update.inHoverSelection
|
||||
|
||||
$scope.$watch "history.updates.length", () ->
|
||||
$scope.recalculateSelectedUpdates()
|
||||
]
|
|
@ -0,0 +1,4 @@
|
|||
define [], () ->
|
||||
HistoryViewModes =
|
||||
POINT_IN_TIME : 'point_in_time'
|
||||
COMPARE : 'compare'
|
|
@ -80,6 +80,7 @@
|
|||
@import "app/review-features-page.less";
|
||||
@import "app/error-pages.less";
|
||||
@import "app/v1-badge.less";
|
||||
@import "app/editor/history-v2.less";
|
||||
@import "app/metrics.less";
|
||||
|
||||
// Vendor CSS
|
||||
|
|
|
@ -74,9 +74,13 @@
|
|||
|
||||
#ide-body {
|
||||
.full-size;
|
||||
top: 40px;
|
||||
top: @ide-body-top-offset;
|
||||
&.ide-history-open {
|
||||
top: @ide-body-top-offset + @editor-toolbar-height;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#editor, #editor-rich-text {
|
||||
.full-size;
|
||||
}
|
||||
|
|
498
services/web/public/stylesheets/app/editor/history-v2.less
Normal file
498
services/web/public/stylesheets/app/editor/history-v2.less
Normal file
|
@ -0,0 +1,498 @@
|
|||
.history-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: @ide-body-top-offset;
|
||||
height: @editor-toolbar-height;
|
||||
line-height: 1;
|
||||
font-size: @font-size-small;
|
||||
background-color: @history-toolbar-bg-color;
|
||||
z-index: 1;
|
||||
color: @history-toolbar-color;
|
||||
padding-left: (@line-height-computed / 2);
|
||||
}
|
||||
.history-toolbar when (@is-overleaf = false) {
|
||||
border-bottom: @toolbar-border-bottom;
|
||||
}
|
||||
.history-toolbar-time {
|
||||
font-weight: bold;
|
||||
}
|
||||
.history-toolbar-btn {
|
||||
.btn;
|
||||
.btn-info;
|
||||
.btn-xs;
|
||||
padding-left: @padding-small-horizontal;
|
||||
padding-right: @padding-small-horizontal;
|
||||
margin-left: (@line-height-computed / 2);
|
||||
}
|
||||
|
||||
.history-entries {
|
||||
font-size: @history-base-font-size;
|
||||
color: @history-base-color;
|
||||
height: 100%;
|
||||
background-color: @history-base-bg;
|
||||
}
|
||||
|
||||
.history-entry-day {
|
||||
display: block;
|
||||
background-color: @history-entry-day-bg;
|
||||
color: #FFF;
|
||||
padding: 5px 10px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.history-entry-details {
|
||||
background-color: #FFF;
|
||||
margin-bottom: 2px;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
|
||||
.history-entry-selected & {
|
||||
background-color: @history-entry-selected-bg;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
.history-entry-changes {
|
||||
.list-unstyled;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.history-entry-change {
|
||||
|
||||
}
|
||||
.history-entry-change-action {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.history-entry-change-doc {
|
||||
color: @history-highlight-color;
|
||||
font-weight: bold;
|
||||
word-break: break-all;
|
||||
.history-entry-selected & {
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
.history-entry-metadata {
|
||||
|
||||
}
|
||||
.history-entry-metadata-time {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.history-entry-metadata-users {
|
||||
display: inline;
|
||||
padding: 0;
|
||||
}
|
||||
.history-entry-metadata-user {
|
||||
display: inline;
|
||||
&::after {
|
||||
content: ', ';
|
||||
}
|
||||
&:last-of-type::after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
.history-file-tree-inner {
|
||||
.full-size;
|
||||
overflow-y: auto;
|
||||
background-color: @file-tree-bg;
|
||||
|
||||
.loading {
|
||||
color: #FFF;
|
||||
font-size: @history-base-font-size;
|
||||
text-align: center;
|
||||
font-family: @font-family-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.history-file-tree-inner when (@is-overleaf = false) {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.history-file-entity-wrapper {
|
||||
color: #FFF;
|
||||
margin-left: (@line-height-computed / 2);
|
||||
}
|
||||
.history-file-entity-link {
|
||||
display: block;
|
||||
position: relative;
|
||||
color: @file-tree-item-color;
|
||||
line-height: @file-tree-line-height;
|
||||
&:hover {
|
||||
background-color: @file-tree-item-hover-bg;
|
||||
color: @file-tree-item-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
&:focus {
|
||||
color: @file-tree-item-color;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
&:hover when (@is-overleaf = true) {
|
||||
.fake-full-width-bg(@file-tree-item-hover-bg);
|
||||
}
|
||||
}
|
||||
.history-file-entity-link-selected {
|
||||
background-color: @file-tree-item-selected-bg;
|
||||
font-weight: bold;
|
||||
padding-right: 32px;
|
||||
color: #FFF;
|
||||
.fake-full-width-bg(@file-tree-item-selected-bg);
|
||||
&:hover {
|
||||
background-color: @file-tree-item-hover-bg;
|
||||
}
|
||||
}
|
||||
.history-file-entity-icon {
|
||||
color: @file-tree-item-icon-color;
|
||||
font-size: 14px;
|
||||
margin-right: .5em;
|
||||
.history-file-entity-link-selected & {
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
.history-file-entity-name {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.history-file-entity-link-selected when (@is-overleaf = false) {
|
||||
color: @brand-primary;
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: @brand-primary;
|
||||
}
|
||||
.history-file-entity-icon {
|
||||
color: @brand-primary;
|
||||
}
|
||||
}
|
||||
// @changesListWidth: 250px;
|
||||
// @changesListPadding: @line-height-computed / 2;
|
||||
|
||||
// @selector-padding-vertical: 10px;
|
||||
// @selector-padding-horizontal: @line-height-computed / 2;
|
||||
// @day-header-height: 24px;
|
||||
|
||||
// @range-bar-color: @link-color;
|
||||
// @range-bar-selected-offset: 14px;
|
||||
|
||||
// #history {
|
||||
// .upgrade-prompt {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// bottom: 0;
|
||||
// left: 0;
|
||||
// right: 0;
|
||||
// z-index: 100;
|
||||
// background-color: rgba(128,128,128,0.4);
|
||||
// .message {
|
||||
// margin: auto;
|
||||
// margin-top: 100px;
|
||||
// padding: (@line-height-computed / 2) @line-height-computed;
|
||||
// width: 400px;
|
||||
// background-color: white;
|
||||
// border-radius: 8px;
|
||||
// }
|
||||
// .message-wider {
|
||||
// width: 650px;
|
||||
// margin-top: 60px;
|
||||
// padding: 0;
|
||||
// }
|
||||
|
||||
// .message-header {
|
||||
// .modal-header;
|
||||
// }
|
||||
|
||||
// .message-body {
|
||||
// .modal-body;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .diff-panel {
|
||||
// .full-size;
|
||||
// margin-right: @changesListWidth;
|
||||
// }
|
||||
|
||||
// .diff {
|
||||
// .full-size;
|
||||
// .toolbar {
|
||||
// padding: 3px;
|
||||
// .name {
|
||||
// float: left;
|
||||
// padding: 3px @line-height-computed / 4;
|
||||
// display: inline-block;
|
||||
// }
|
||||
// }
|
||||
// .diff-editor {
|
||||
// .full-size;
|
||||
// top: 40px;
|
||||
// }
|
||||
// .hide-ace-cursor {
|
||||
// .ace_active-line, .ace_cursor-layer, .ace_gutter-active-line {
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
// .diff-deleted {
|
||||
// padding: @line-height-computed;
|
||||
// }
|
||||
// .deleted-warning {
|
||||
// background-color: @brand-danger;
|
||||
// color: white;
|
||||
// padding: @line-height-computed / 2;
|
||||
// margin-right: @line-height-computed / 4;
|
||||
// }
|
||||
// &-binary {
|
||||
// .alert {
|
||||
// margin: @line-height-computed / 2;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// aside.change-list {
|
||||
// border-left: 1px solid @editor-border-color;
|
||||
// height: 100%;
|
||||
// width: @changesListWidth;
|
||||
// position: absolute;
|
||||
// right: 0;
|
||||
|
||||
// .loading {
|
||||
// text-align: center;
|
||||
// font-family: @font-family-serif;
|
||||
// }
|
||||
|
||||
// ul {
|
||||
// li.change {
|
||||
// position: relative;
|
||||
// user-select: none;
|
||||
// -ms-user-select: none;
|
||||
// -moz-user-select: none;
|
||||
// -webkit-user-select: none;
|
||||
|
||||
// .day {
|
||||
// background-color: #fafafa;
|
||||
// border-bottom: 1px solid @editor-border-color;
|
||||
// padding: 4px;
|
||||
// font-weight: bold;
|
||||
// text-align: center;
|
||||
// height: @day-header-height;
|
||||
// font-size: 14px;
|
||||
// line-height: 1;
|
||||
// }
|
||||
// .selectors {
|
||||
// input {
|
||||
// margin: 0;
|
||||
// }
|
||||
// position: absolute;
|
||||
// left: @selector-padding-horizontal;
|
||||
// top: 0;
|
||||
// bottom: 0;
|
||||
// width: 24px;
|
||||
// .selector-from {
|
||||
// position: absolute;
|
||||
// bottom: @selector-padding-vertical;
|
||||
// left: 0;
|
||||
// opacity: 0.8;
|
||||
// }
|
||||
// .selector-to {
|
||||
// position: absolute;
|
||||
// top: @selector-padding-vertical;
|
||||
// left: 0;
|
||||
// opacity: 0.8;
|
||||
// }
|
||||
// .range {
|
||||
// position: absolute;
|
||||
// left: 5px;
|
||||
// width: 4px;
|
||||
// top: 0;
|
||||
// bottom: 0;
|
||||
// }
|
||||
// }
|
||||
// .description {
|
||||
// padding: (@line-height-computed / 4);
|
||||
// padding-left: 38px;
|
||||
// min-height: 38px;
|
||||
// border-bottom: 1px solid @editor-border-color;
|
||||
// cursor: pointer;
|
||||
// &:hover {
|
||||
// background-color: @gray-lightest;
|
||||
// }
|
||||
// }
|
||||
// .users {
|
||||
// .user {
|
||||
// font-size: 0.8rem;
|
||||
// color: @gray;
|
||||
// text-transform: capitalize;
|
||||
// position: relative;
|
||||
// padding-left: 16px;
|
||||
// .color-square {
|
||||
// height: 12px;
|
||||
// width: 12px;
|
||||
// border-radius: 3px;
|
||||
// position: absolute;
|
||||
// left: 0;
|
||||
// bottom: 3px;
|
||||
// }
|
||||
// .name {
|
||||
// width: 94%;
|
||||
// white-space: nowrap;
|
||||
// overflow: hidden;
|
||||
// text-overflow: ellipsis;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .time {
|
||||
// float: right;
|
||||
// color: @gray;
|
||||
// display: inline-block;
|
||||
// padding-right: (@line-height-computed / 2);
|
||||
// font-size: 0.8rem;
|
||||
// line-height: @line-height-computed;
|
||||
// }
|
||||
// .doc {
|
||||
// font-size: 0.9rem;
|
||||
// font-weight: bold;
|
||||
// }
|
||||
// .action {
|
||||
// color: @gray;
|
||||
// text-transform: uppercase;
|
||||
// font-size: 0.7em;
|
||||
// margin-bottom: -2px;
|
||||
// margin-top: 2px;
|
||||
// &-edited {
|
||||
// margin-top: 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// li.loading-changes, li.empty-message {
|
||||
// padding: 6px;
|
||||
// cursor: default;
|
||||
// &:hover {
|
||||
// background-color: inherit;
|
||||
// }
|
||||
// }
|
||||
// li.selected {
|
||||
// border-left: 4px solid @range-bar-color;
|
||||
// .day {
|
||||
// padding-left: 0;
|
||||
// }
|
||||
// .description {
|
||||
// padding-left: 34px;
|
||||
// }
|
||||
// .selectors {
|
||||
// left: @selector-padding-horizontal - 4px;
|
||||
// .range {
|
||||
// background-color: @range-bar-color;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// li.selected-to {
|
||||
// .selectors {
|
||||
// .range {
|
||||
// top: @range-bar-selected-offset;
|
||||
// }
|
||||
// .selector-to {
|
||||
// opacity: 1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// li.selected-from {
|
||||
// .selectors {
|
||||
// .range {
|
||||
// bottom: @range-bar-selected-offset;
|
||||
// }
|
||||
// .selector-from {
|
||||
// opacity: 1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// li.first-in-day {
|
||||
// .selectors {
|
||||
// .selector-to {
|
||||
// top: @day-header-height + @selector-padding-vertical;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// li.first-in-day.selected-to {
|
||||
// .selectors {
|
||||
// .range {
|
||||
// top: @day-header-height + @range-bar-selected-offset;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ul.hover-state {
|
||||
// li {
|
||||
// .selectors {
|
||||
// .range {
|
||||
// background-color: transparent;
|
||||
// top: 0;
|
||||
// bottom: 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// li.hover-selected {
|
||||
// .selectors {
|
||||
// .range {
|
||||
// top: 0;
|
||||
// background-color: @gray-light;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// li.hover-selected-to {
|
||||
// .selectors {
|
||||
// .range {
|
||||
// top: @range-bar-selected-offset;
|
||||
// }
|
||||
// .selector-to {
|
||||
// opacity: 1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// li.hover-selected-from {
|
||||
// .selectors {
|
||||
// .range {
|
||||
// bottom: @range-bar-selected-offset;
|
||||
// }
|
||||
// .selector-from {
|
||||
// opacity: 1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// li.first-in-day.hover-selected-to {
|
||||
// .selectors {
|
||||
// .range {
|
||||
// top: @day-header-height + @range-bar-selected-offset;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// .diff-deleted {
|
||||
// padding-top: 15px;
|
||||
// }
|
||||
|
||||
// .editor-dark {
|
||||
// #history {
|
||||
// aside.change-list {
|
||||
// border-color: @editor-dark-toolbar-border-color;
|
||||
|
||||
// ul li.change {
|
||||
// .day {
|
||||
// background-color: darken(@editor-dark-background-color, 10%);
|
||||
// border-bottom: 1px solid @editor-dark-toolbar-border-color;
|
||||
// }
|
||||
// .description {
|
||||
// border-bottom: 1px solid @editor-dark-toolbar-border-color;
|
||||
// &:hover {
|
||||
// background-color: black;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
|
@ -1,4 +1,4 @@
|
|||
@changesListWidth: 250px;
|
||||
@changesListWidth: 250px;
|
||||
@changesListPadding: @line-height-computed / 2;
|
||||
|
||||
@selector-padding-vertical: 10px;
|
||||
|
@ -40,7 +40,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.diff-panel {
|
||||
.diff-panel,
|
||||
.point-in-time-panel {
|
||||
.full-size;
|
||||
margin-right: @changesListWidth;
|
||||
}
|
||||
|
@ -49,6 +50,7 @@
|
|||
.full-size;
|
||||
.toolbar {
|
||||
padding: 3px;
|
||||
height: 32px;
|
||||
.name {
|
||||
float: left;
|
||||
padding: 3px @line-height-computed / 4;
|
||||
|
@ -57,13 +59,9 @@
|
|||
}
|
||||
.diff-editor {
|
||||
.full-size;
|
||||
top: 40px;
|
||||
}
|
||||
.hide-ace-cursor {
|
||||
.ace_active-line, .ace_cursor-layer, .ace_gutter-active-line {
|
||||
display: none;
|
||||
}
|
||||
top: 32px;
|
||||
}
|
||||
|
||||
.diff-deleted {
|
||||
padding: @line-height-computed;
|
||||
}
|
||||
|
@ -90,6 +88,7 @@
|
|||
.loading {
|
||||
text-align: center;
|
||||
font-family: @font-family-serif;
|
||||
margin-top: (@line-height-computed / 2);
|
||||
}
|
||||
|
||||
ul {
|
||||
|
@ -305,6 +304,12 @@
|
|||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.hide-ace-cursor {
|
||||
.ace_active-line, .ace_cursor-layer, .ace_gutter-active-line {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-dark {
|
||||
#history {
|
||||
aside.change-list {
|
||||
|
|
|
@ -888,6 +888,7 @@
|
|||
@footer-padding : 2em;
|
||||
|
||||
// Editor header
|
||||
@ide-body-top-offset : 40px;
|
||||
@toolbar-header-bg-color : transparent;
|
||||
@toolbar-header-shadow : 0 0 2px #ccc;
|
||||
@toolbar-btn-color : @link-color;
|
||||
|
@ -972,4 +973,15 @@
|
|||
// System messages
|
||||
@sys-msg-background : @state-warning-bg;
|
||||
@sys-msg-color : #333;
|
||||
@sys-msg-border : 1px solid @common-border-color;
|
||||
@sys-msg-border : 1px solid @common-border-color;
|
||||
|
||||
// v2 History
|
||||
@history-base-font-size : @font-size-small;
|
||||
@history-base-bg : @gray-lightest;
|
||||
@history-entry-day-bg : @gray;
|
||||
@history-entry-selected-bg : @red;
|
||||
@history-base-color : @gray-light;
|
||||
@history-highlight-color : @gray;
|
||||
@history-toolbar-bg-color : @toolbar-alt-bg-color;
|
||||
@history-toolbar-color : @text-color;
|
||||
|
||||
|
|
|
@ -265,6 +265,17 @@
|
|||
@log-line-no-color : #FFF;
|
||||
@log-hints-color : @ol-blue-gray-4;
|
||||
|
||||
|
||||
// v2 History
|
||||
@history-base-font-size : @font-size-small;
|
||||
@history-base-bg : @ol-blue-gray-1;
|
||||
@history-entry-day-bg : @ol-blue-gray-2;
|
||||
@history-entry-selected-bg : @ol-green;
|
||||
@history-base-color : @ol-blue-gray-2;
|
||||
@history-highlight-color : @ol-type-color;
|
||||
@history-toolbar-bg-color : @editor-toolbar-bg;
|
||||
@history-toolbar-color : #FFF;
|
||||
|
||||
// System messages
|
||||
@sys-msg-background : @ol-blue;
|
||||
@sys-msg-color : #FFF;
|
||||
|
|
Loading…
Add table
Reference in a new issue