diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug
index 5c48bb87f8..4571dbc643 100644
--- a/services/web/app/views/project/editor.pug
+++ b/services/web/app/views/project/editor.pug
@@ -63,7 +63,7 @@ block content
main#ide-body(
ng-cloak,
role="main",
- ng-class="{ 'ide-history-open' : (ui.view == 'history' && history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME) }",
+ ng-class="{ 'ide-history-open' : (ui.view == 'history' && history.isV2) }",
layout="main",
ng-hide="state.loading",
resize-on="layout:chat:resize,history:toggle",
diff --git a/services/web/app/views/project/editor/history.pug b/services/web/app/views/project/editor/history.pug
index f968639cca..bcc184fc6d 100644
--- a/services/web/app/views/project/editor/history.pug
+++ b/services/web/app/views/project/editor/history.pug
@@ -30,7 +30,10 @@ script(type="text/ng-template", id="historyRestoreDiffModalTemplate")
script(type="text/ng-template", id="historyLabelTpl")
.history-label(
- ng-class="{ 'history-label-own' : $ctrl.isOwnedByCurrentUser }"
+ ng-class="{\
+ 'history-label-own' : $ctrl.isOwnedByCurrentUser,\
+ 'history-label-pseudo-current-state': $ctrl.isPseudoCurrentStateLabel,\
+ }"
)
span.history-label-comment(
tooltip-append-to-body="true"
@@ -39,9 +42,9 @@ script(type="text/ng-template", id="historyLabelTpl")
tooltip-enable="$ctrl.showTooltip"
)
i.fa.fa-tag
- | {{ $ctrl.labelText }}
+ | {{ ::$ctrl.isPseudoCurrentStateLabel ? '#{translate("history_label_project_current_state")}' : $ctrl.labelText }}
button.history-label-delete-btn(
- ng-if="$ctrl.isOwnedByCurrentUser"
+ ng-if="$ctrl.isOwnedByCurrentUser && !$ctrl.isPseudoCurrentStateLabel"
stop-propagation="click"
ng-click="$ctrl.onLabelDelete()"
) ×
diff --git a/services/web/app/views/project/editor/history/entriesListV1.pug b/services/web/app/views/project/editor/history/entriesListV1.pug
index 27f9e66fe1..b810b8cbdd 100644
--- a/services/web/app/views/project/editor/history/entriesListV1.pug
+++ b/services/web/app/views/project/editor/history/entriesListV1.pug
@@ -1,6 +1,6 @@
aside.change-list(
ng-if="!history.isV2"
- ng-controller="HistoryListController"
+ ng-controller="HistoryCompareListController"
infinite-scroll="loadMore()"
infinite-scroll-disabled="history.loading || history.atEnd"
infinite-scroll-initialize="ui.view == 'history'"
@@ -22,7 +22,7 @@ aside.change-list(
'hover-selected-to': update.hoverSelectedTo,\
'hover-selected-from': update.hoverSelectedFrom,\
}"
- ng-controller="HistoryListItemController"
+ ng-controller="HistoryCompareListItemController"
)
div.day(ng-show="update.meta.first_in_day") {{ update.meta.end_ts | relativeDate }}
diff --git a/services/web/app/views/project/editor/history/entriesListV2.pug b/services/web/app/views/project/editor/history/entriesListV2.pug
index 640a761296..bc97ee5b35 100644
--- a/services/web/app/views/project/editor/history/entriesListV2.pug
+++ b/services/web/app/views/project/editor/history/entriesListV2.pug
@@ -5,6 +5,7 @@ aside.change-list(
history-entries-list(
ng-if="!history.showOnlyLabels && !history.error"
entries="history.updates"
+ selected-history-version="history.selection.range.toV"
current-user="user"
current-user-is-owner="project.owner._id === user.id"
users="projectUsers"
@@ -26,98 +27,220 @@ aside.change-list(
on-label-select="handleLabelSelect(label)"
on-label-delete="handleLabelDelete(label)"
)
-
-aside.change-list(
+aside.change-list.change-list-compare(
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'"
+ ng-controller="HistoryCompareController"
)
- .infinite-scroll-inner
+ div(
+ ng-if="!history.showOnlyLabels && !history.error"
+ )
+ aside.change-list(
+ infinite-scroll="loadMore()"
+ infinite-scroll-disabled="history.loading || history.atEnd"
+ infinite-scroll-initialize="ui.view == 'history' && history.viewMode === HistoryViewModes.COMPARE"
+ )
+ .infinite-scroll-inner
+ ul.list-unstyled(
+ ng-class="{\
+ 'hover-state': history.hoveringOverListSelectors\
+ }"
+ )
+ li.change(
+ ng-repeat="update in history.updates track by update.fromV"
+ ng-class="{\
+ 'first-in-day': update.meta.first_in_day,\
+ 'selected': update.toV <= history.selection.range.toV && update.fromV >= history.selection.range.fromV,\
+ 'selected-to': update.toV === history.selection.range.toV,\
+ 'selected-from': update.fromV === history.selection.range.fromV,\
+ 'hover-selected': update.toV <= history.selection.hoveredRange.toV && update.fromV >= history.selection.hoveredRange.fromV,\
+ 'hover-selected-to': update.toV === history.selection.hoveredRange.toV,\
+ 'hover-selected-from': update.fromV === history.selection.hoveredRange.fromV,\
+ }"
+ )
+
+ div.day(ng-if="::update.meta.first_in_day") {{ ::update.meta.end_ts | relativeDate }}
+
+ div.selectors
+ div.range
+ form
+ input.selector-from(
+ type="radio"
+ name="fromVersion"
+ ng-model="history.selection.range.fromV"
+ ng-value="update.fromV"
+ ng-mouseover="setHoverFrom(update.fromV)"
+ ng-mouseout="resetHover()"
+ ng-show="update.fromV < history.selection.range.fromV || update.toV <= history.selection.range.toV && update.fromV >= history.selection.range.fromV"
+ )
+ form
+ input.selector-to(
+ type="radio"
+ name="toVersion"
+ ng-model="history.selection.range.toV"
+ ng-value="update.toV"
+ ng-mouseover="setHoverTo(update.toV)"
+ ng-mouseout="resetHover()"
+ ng-show="update.toV > history.selection.range.toV || update.toV <= history.selection.range.toV && update.fromV >= history.selection.range.fromV"
+ )
+
+ div.description(ng-click="select(update.toV, update.fromV)")
+ history-label(
+ ng-repeat="label in update.labels track by label.id"
+ label-text="label.comment"
+ label-owner-name="getDisplayNameById(label.user_id)"
+ label-creation-date-time="label.created_at"
+ is-owned-by-current-user="label.user_id === user.id"
+ on-label-delete="deleteLabel(label)"
+ )
+ 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")}...
+ .history-entries-list-upgrade-prompt(
+ ng-if="history.freeHistoryLimitHit && project.owner._id === user.id"
+ ng-controller="FreeTrialModalController"
+ )
+ p #{translate("currently_seeing_only_24_hrs_history")}
+ p: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})}
+ ul.list-unstyled
+ li
+ i.fa.fa-check
+ | #{translate("unlimited_projects")}
+
+ li
+ i.fa.fa-check
+ | #{translate("collabs_per_proj", {collabcount:'Multiple'})}
+
+ li
+ i.fa.fa-check
+ | #{translate("full_doc_history")}
+
+ li
+ i.fa.fa-check
+ | #{translate("sync_to_dropbox")}
+
+ li
+ i.fa.fa-check
+ | #{translate("sync_to_github")}
+
+ li
+ i.fa.fa-check
+ |#{translate("compile_larger_projects")}
+ p.text-center
+ a.btn.btn-success(
+ href
+ ng-class="buttonClass"
+ ng-click="startFreeTrial('history')"
+ ) #{translate("start_free_trial")}
+ p.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")}
+ .history-entries-list-upgrade-prompt(
+ ng-if="history.freeHistoryLimitHit && !project.owner._id === user.id"
+ )
+ p #{translate("currently_seeing_only_24_hrs_history")}
+ strong #{translate("ask_proj_owner_to_upgrade_for_full_history")}
+ .history-labels-list-compare(
+ ng-if="history.showOnlyLabels && !history.error"
+ )
ul.list-unstyled(
ng-class="{\
'hover-state': history.hoveringOverListSelectors\
}"
)
li.change(
- ng-repeat="update in history.updates"
+ ng-repeat="versionWithLabel in versionsWithLabels | orderBy:'-version' track by versionWithLabel.version"
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,\
+ 'selected': versionWithLabel.version <= history.selection.range.toV && versionWithLabel.version >= history.selection.range.fromV,\
+ 'selected-to': versionWithLabel.version === history.selection.range.toV,\
+ 'selected-from': versionWithLabel.version === history.selection.range.fromV,\
+ 'hover-selected': versionWithLabel.version <= history.selection.hoveredRange.toV && versionWithLabel.version >= history.selection.hoveredRange.fromV,\
+ 'hover-selected-to': versionWithLabel.version === history.selection.hoveredRange.toV,\
+ 'hover-selected-from': versionWithLabel.version === history.selection.hoveredRange.fromV,\
}"
- 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"
+ name="fromVersionForLabel"
+ ng-model="history.selection.range.fromV"
+ ng-value="versionWithLabel.version"
+ ng-mouseover="setHoverFrom(versionWithLabel.version)"
+ ng-mouseout="resetHover()"
+ ng-show="versionWithLabel.version < history.selection.range.fromV || versionWithLabel.version <= history.selection.range.toV && versionWithLabel.version >= history.selection.range.fromV"
)
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"
+ name="toVersionForLabel"
+ ng-model="history.selection.range.toV"
+ ng-value="versionWithLabel.version"
+ ng-mouseover="setHoverTo(versionWithLabel.version)"
+ ng-mouseout="resetHover()"
+ ng-show="versionWithLabel.version > history.selection.range.toV || versionWithLabel.version <= history.selection.range.toV && versionWithLabel.version >= history.selection.range.fromV"
)
- div.description(ng-click="select()")
- history-label(
- ng-repeat="label in update.labels"
- label-text="label.comment"
- label-owner-name="getDisplayNameById(label.user_id)"
- label-creation-date-time="label.created_at"
- is-owned-by-current-user="label.user_id === user.id"
- on-label-delete="deleteLabel(label)"
+
+ .description(ng-click="addLabelVersionToSelection(versionWithLabel.version)")
+ div(
+ ng-repeat="label in versionWithLabel.labels track by label.id"
)
- 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")}
+ history-label(
+ show-tooltip="false"
+ label-text="label.comment"
+ is-owned-by-current-user="label.user_id === user.id"
+ is-pseudo-current-state-label="label.isPseudoCurrentStateLabel"
+ on-label-delete="deleteLabel(label)"
+ )
+ .history-entry-label-metadata
+ .history-entry-label-metadata-user(
+ ng-if="!label.isPseudoCurrentStateLabel"
+ ng-init="labelOwner = getUserById(label.user_id)"
+ )
+ | Saved by
+ span.name(
+ ng-if="::labelOwner && labelOwner._id !== user.id"
+ ng-style="::{'color': 'hsl({{ hueForUser(label.user_id) }}, 70%, 50%)'}"
+ ) {{ ::getDisplayNameById(label.user_id) }}
+ span.name(
+ ng-if="labelOwner && labelOwner._id == user.id"
+ ng-style="::{'color': 'hsl({{ hueForUser(label.user_id) }}, 70%, 50%)'}"
+ ) You
+ span.name(
+ ng-if="::labelOwner == null"
+ ng-style="::{'color': 'hsl(100, 70%, 50%)'}"
+ ) #{translate("anonymous")}
+ time.history-entry-label-metadata-time {{ ::label.created_at | formatDate }}
.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()"
@@ -127,6 +250,7 @@ script(type="text/ng-template", id="historyEntriesListTpl")
.infinite-scroll-inner
history-entry(
ng-repeat="entry in $ctrl.entries"
+ selected-history-version="$ctrl.selectedHistoryVersion"
entry="entry"
current-user="$ctrl.currentUser"
users="$ctrl.users"
@@ -183,12 +307,7 @@ 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,\
+ 'history-entry-selected': $ctrl.entry.toV === $ctrl.selectedHistoryVersion,\
}"
)
@@ -261,9 +380,13 @@ script(type="text/ng-template", id="historyLabelsListTpl")
label-text="label.comment"
is-owned-by-current-user="label.user_id === $ctrl.currentUser.id"
on-label-delete="$ctrl.onLabelDelete({ label: label })"
+ is-pseudo-current-state-label="label.isPseudoCurrentStateLabel"
)
.history-entry-label-metadata
- .history-entry-label-metadata-user(ng-init="user = $ctrl.getUserById(label.user_id)")
+ .history-entry-label-metadata-user(
+ ng-if="!label.isPseudoCurrentStateLabel"
+ ng-init="user = $ctrl.getUserById(label.user_id)"
+ )
| Saved by
span.name(
ng-if="user && user._id !== $ctrl.currentUser.id"
diff --git a/services/web/app/views/project/editor/history/fileTreeV2.pug b/services/web/app/views/project/editor/history/fileTreeV2.pug
index 0f3a2c1203..84fb813005 100644
--- a/services/web/app/views/project/editor/history/fileTreeV2.pug
+++ b/services/web/app/views/project/editor/history/fileTreeV2.pug
@@ -1,6 +1,6 @@
aside.file-tree.full-size(
ng-controller="HistoryV2FileTreeController"
- ng-if="ui.view == 'history' && history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME"
+ ng-if="ui.view == 'history' && history.isV2"
)
.history-file-tree-inner
history-file-tree(
@@ -10,39 +10,14 @@ aside.file-tree.full-size(
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"
+ ng-repeat="fileEntity in $ctrl.fileTree | orderBy : [ '-type', 'operation', 'name' ]"
file-entity="fileEntity"
ng-show="!$ctrl.isLoading"
)
-
script(type="text/ng-template", id="historyFileEntityTpl")
.history-file-entity-wrapper
a.history-file-entity-link(
@@ -50,7 +25,7 @@ script(type="text/ng-template", id="historyFileEntityTpl")
ng-click="$ctrl.handleClick()"
ng-class="{ 'history-file-entity-link-selected': $ctrl.isSelected }"
)
- span.history-file-entity-name
+ span.history-file-entity-name-container
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),\
@@ -58,9 +33,21 @@ script(type="text/ng-template", id="historyFileEntityTpl")
}"
)
i.history-file-entity-icon.fa(
- ng-class="$ctrl.iconClass"
+ ng-class="::$ctrl.entityTypeIconClass"
)
- | {{ ::$ctrl.fileEntity.name }}
+ span.history-file-entity-name(
+ ng-class="::$ctrl.entityOpTextClass"
+ ) {{ ::$ctrl.fileEntity.name }}
+ span.history-file-entity-operation-badge(
+ ng-if="::$ctrl.hasOperation && $ctrl.fileEntity.operation !== 'renamed' && $ctrl.fileEntity.operation !== 'removed'"
+ ) {{ ::$ctrl.getFileOperationName() }}
+ span.history-file-entity-operation-badge(
+ ng-if="::$ctrl.hasOperation && $ctrl.fileEntity.operation === 'renamed'"
+ tooltip-append-to-body="true"
+ tooltip-placement="right"
+ tooltip-class="tooltip-history-file-tree"
+ tooltip-html=`::$ctrl.getRenameTooltip()`
+ ) {{ ::$ctrl.getFileOperationName() }}
div(
ng-show="$ctrl.isOpen"
)
@@ -68,3 +55,8 @@ script(type="text/ng-template", id="historyFileEntityTpl")
ng-repeat="childEntity in $ctrl.fileEntity.children"
file-entity="childEntity"
)
+
+script(type="template", id="file_action_edited_str") #{ translate('file_action_edited') }
+script(type="template", id="file_action_renamed_str") #{ translate('file_action_renamed') }
+script(type="template", id="file_action_created_str") #{ translate('file_action_created') }
+script(type="template", id="file_action_deleted_str") #{ translate('file_action_deleted') }
diff --git a/services/web/app/views/project/editor/history/previewPanelV2.pug b/services/web/app/views/project/editor/history/previewPanelV2.pug
index 31f08956a6..6542594d6b 100644
--- a/services/web/app/views/project/editor/history/previewPanelV2.pug
+++ b/services/web/app/views/project/editor/history/previewPanelV2.pug
@@ -1,80 +1,48 @@
.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 }"
+ ng-if="!!history.selection.diff && !history.selection.diff.loading && !history.selection.diff.error",
+ ng-class="{ 'diff-binary': history.selection.diff.binary }"
)
- .toolbar.toolbar-alt
- span.name(ng-if="history.diff.binary")
- strong {{history.diff.pathname}}
- span.name(ng-if="!history.diff.binary")
- | {{history.diff.highlights.length}}
- ng-pluralize(
- count="history.diff.highlights.length",
- when="{\
- 'one': 'change',\
- 'other': 'changes'\
- }"
- )
- | in {{history.diff.pathname}}
- .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-xs(
- ng-click="restoreDeletedFile()"
- ng-show="!restoreState.error"
- ng-disabled="restoreState.inflight"
- )
- i.fa.fa-fw.fa-step-backward
- span(ng-show="!restoreState.inflight")
- | Restore this deleted file
- span(ng-show="restoreState.inflight")
- | Restoring...
- span.text-danger(ng-show="restoreState.error")
- | Error restoring, sorry
.diff-editor.hide-ace-cursor(
- ng-if="!history.diff.binary"
+ ng-if="!history.selection.diff.binary"
ace-editor="history",
theme="settings.editorTheme",
font-size="settings.fontSize",
- text="history.diff.text",
- highlights="history.diff.highlights",
+ text="history.selection.diff.text",
+ highlights="history.selection.diff.highlights",
read-only="true",
resize-on="layout:main:resize,history:toggle",
navigate-highlights="true"
)
- .alert.alert-info(ng-if="history.diff.binary")
+ .alert.alert-info(ng-if="history.selection.diff.binary")
| We're still working on showing image and binary changes, sorry. Stay tuned!
- .loading-panel(ng-show="history.diff.loading")
+ .loading-panel(ng-show="history.selection.diff.loading")
i.fa.fa-spin.fa-refresh
| #{translate("loading")}...
- .error-panel(ng-show="history.diff.error")
+ .error-panel(ng-show="history.selection.diff.error")
.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"
+ ng-if="!!history.selection.file && !history.selection.file.loading && !history.selection.file.error"
)
.hide-ace-cursor(
- ng-if="!history.selectedFile.binary"
+ ng-if="!history.selection.file.binary"
ace-editor="history-pointintime",
theme="settings.editorTheme",
font-size="settings.fontSize",
- text="history.selectedFile.text",
+ text="history.selection.file.text",
read-only="true",
resize-on="layout:main:resize,history:toggle",
)
- .alert.alert-info(ng-if="history.selectedFile.binary")
+ .alert.alert-info(ng-if="history.selection.file.binary")
| We're still working on showing image and binary changes, sorry. Stay tuned!
- .loading-panel(ng-show="history.selectedFile.loading")
+ .loading-panel(ng-show="history.selection.file.loading")
i.fa.fa-spin.fa-refresh
| #{translate("loading")}...
.error-panel(ng-show="history.error")
@@ -89,5 +57,5 @@
href
ng-click="toggleHistory()"
) #{translate("back_to_editor")}
- .error-panel(ng-show="history.selectedFile.error")
+ .error-panel(ng-show="history.selection.file.error")
.alert.alert-danger #{translate("generic_something_went_wrong")}
diff --git a/services/web/app/views/project/editor/history/toolbarV2.pug b/services/web/app/views/project/editor/history/toolbarV2.pug
index a7733fbf3c..09c81f9722 100644
--- a/services/web/app/views/project/editor/history/toolbarV2.pug
+++ b/services/web/app/views/project/editor/history/toolbarV2.pug
@@ -1,48 +1,95 @@
.history-toolbar(
ng-controller="HistoryV2ToolbarController"
- ng-if="ui.view == 'history' && history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME"
+ ng-if="ui.view == 'history' && history.isV2"
)
- span.history-toolbar-selected-version(ng-show="history.loadingFileTree")
+ span.history-toolbar-selected-version(ng-show="history.loadingFileTree || history.selection.diff.loading")
i.fa.fa-spin.fa-refresh
| #{translate("loading")}...
+
+ //- point-in-time mode info
span.history-toolbar-selected-version(
- ng-show="!history.loadingFileTree && !history.showOnlyLabels && history.selection.updates.length && !history.error"
+ ng-show="!history.loadingFileTree && !history.showOnlyLabels && history.selection.update && !history.error"
) #{translate("browsing_project_as_of")}
- time.history-toolbar-time {{ history.selection.updates[0].meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }}
+ time.history-toolbar-time {{ history.selection.update.meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }}
span.history-toolbar-selected-version(
ng-show="!history.loadingFileTree && history.showOnlyLabels && history.selection.label && !history.error"
- ) #{translate("browsing_project_labelled")}
- span.history-toolbar-selected-label "{{ history.selection.label.comment }}"
+ )
+ span(ng-if="history.selection.label.comment && !history.selection.label.isPseudoCurrentStateLabel")
+ | #{translate("browsing_project_labelled")}
+ span.history-toolbar-selected-label "{{ history.selection.label.comment }}"
+ span.history-toolbar-selected-label(ng-if="history.selection.label.isPseudoCurrentStateLabel")
+ | #{translate("browsing_project_latest_for_pseudo_label")}
+
+
+
+ //- compare mode info
+ span.history-toolbar-selected-version(ng-show="history.viewMode === HistoryViewModes.COMPARE && history.selection.diff && !history.selection.diff.binary && !history.selection.diff.loading && !history.selection.diff.error && !history.loadingFileTree")
+ | {{history.selection.diff.highlights.length}}
+ ng-pluralize(
+ count="history.selection.diff.highlights.length",
+ when="{\
+ 'one': 'change',\
+ 'other': 'changes'\
+ }"
+ )
+ | in {{history.selection.diff.pathname}}
+
+ //- point-in-time mode actions
div.history-toolbar-actions(
- ng-if="!history.error"
+ ng-if="history.viewMode === HistoryViewModes.POINT_IN_TIME && !history.error"
)
button.history-toolbar-btn(
ng-click="showAddLabelDialog();"
ng-if="!history.showOnlyLabels"
- ng-disabled="history.loadingFileTree || history.selection.updates.length == 0"
+ ng-disabled="history.loadingFileTree || history.selection.range.toV == null || history.selection.range.fromV == null"
)
i.fa.fa-tag
| #{translate("history_label_this_version")}
button.history-toolbar-btn(
ng-click="toggleHistoryViewMode();"
- ng-disabled="history.loadingFileTree || history.selection.updates.length == 0"
+ ng-disabled="history.loadingFileTree"
)
i.fa.fa-exchange
| #{translate("compare_to_another_version")}
a.history-toolbar-btn-danger.pull-right(
- ng-hide="history.loadingFileTree || history.selection.updates.length == 0"
- ng-href="/project/{{ project_id }}/version/{{ history.selection.updates[0].toV }}/zip"
+ ng-hide="history.loadingFileTree || history.selection.range.toV == null"
+ ng-href="/project/{{ project_id }}/version/{{ history.selection.range.toV }}/zip"
target="_blank"
)
i.fa.fa-download
| #{translate("download_project_at_this_version")}
+
+
+ //- compare mode actions
+ div.history-toolbar-actions(
+ ng-if="history.viewMode === HistoryViewModes.COMPARE && !history.error"
+ )
+ button.history-toolbar-btn(
+ ng-click="toggleHistoryViewMode();"
+ )
+ i.fa
+ | #{translate("view_single_version")}
+ button.history-toolbar-btn-danger.pull-right(
+ ng-if="history.selection.file.deletedAtV"
+ ng-click="restoreDeletedFile()"
+ ng-show="!restoreState.error"
+ ng-disabled="restoreState.inflight"
+ )
+ i.fa.fa-fw.fa-step-backward
+ span(ng-show="!restoreState.inflight")
+ | Restore this deleted file
+ span(ng-show="restoreState.inflight")
+ | Restoring...
+ span.text-danger(ng-show="restoreState.error")
+ | Error restoring, sorry
+
.history-toolbar-entries-list(
ng-if="!history.error"
)
toggle-switch(
- ng-model="history.showOnlyLabels"
+ ng-model="toolbarUIConfig.showOnlyLabels"
label-true=translate("history_view_labels")
label-false=translate("history_view_all")
description=translate("history_view_a11y_description")
diff --git a/services/web/public/src/ide.js b/services/web/public/src/ide.js
index bb4bddbaa5..3522026ed6 100644
--- a/services/web/public/src/ide.js
+++ b/services/web/public/src/ide.js
@@ -193,7 +193,7 @@ define([
ide.editorManager = new EditorManager(ide, $scope, localStorage)
ide.onlineUsersManager = new OnlineUsersManager(ide, $scope)
if (window.data.useV2History) {
- ide.historyManager = new HistoryV2Manager(ide, $scope)
+ ide.historyManager = new HistoryV2Manager(ide, $scope, localStorage)
} else {
ide.historyManager = new HistoryManager(ide, $scope)
}
diff --git a/services/web/public/src/ide/file-tree/util/fileOperationI18nNames.js b/services/web/public/src/ide/file-tree/util/fileOperationI18nNames.js
new file mode 100644
index 0000000000..f3d9be6f1c
--- /dev/null
+++ b/services/web/public/src/ide/file-tree/util/fileOperationI18nNames.js
@@ -0,0 +1,8 @@
+define([], function() {
+ return {
+ edited: document.getElementById('file_action_edited_str').innerText,
+ renamed: document.getElementById('file_action_renamed_str').innerText,
+ created: document.getElementById('file_action_created_str').innerText,
+ deleted: document.getElementById('file_action_deleted_str').innerText
+ }
+})
diff --git a/services/web/public/src/ide/history/HistoryManager.js b/services/web/public/src/ide/history/HistoryManager.js
index 03f84216c7..ff17520969 100644
--- a/services/web/public/src/ide/history/HistoryManager.js
+++ b/services/web/public/src/ide/history/HistoryManager.js
@@ -19,7 +19,7 @@ define([
'moment',
'ide/colors/ColorManager',
'ide/history/util/displayNameForUser',
- 'ide/history/controllers/HistoryListController',
+ 'ide/history/controllers/HistoryCompareController',
'ide/history/controllers/HistoryDiffController',
'ide/history/directives/infiniteScroll'
], function(moment, ColorManager, displayNameForUser) {
diff --git a/services/web/public/src/ide/history/HistoryV2Manager.js b/services/web/public/src/ide/history/HistoryV2Manager.js
index 15162de80c..3037e34490 100644
--- a/services/web/public/src/ide/history/HistoryV2Manager.js
+++ b/services/web/public/src/ide/history/HistoryV2Manager.js
@@ -23,7 +23,6 @@ define([
'ide/history/util/displayNameForUser',
'ide/history/util/HistoryViewModes',
'ide/history/controllers/HistoryV2ListController',
- 'ide/history/controllers/HistoryV2DiffController',
'ide/history/controllers/HistoryV2FileTreeController',
'ide/history/controllers/HistoryV2ToolbarController',
'ide/history/controllers/HistoryV2AddLabelModalController',
@@ -41,124 +40,229 @@ define([
HistoryManager = class HistoryManager {
static initClass() {
this.prototype.MAX_RECENT_UPDATES_TO_SELECT = 5
-
this.prototype.BATCH_SIZE = 10
}
- constructor(ide, $scope) {
+ constructor(ide, $scope, localStorage) {
this.labelCurrentVersion = this.labelCurrentVersion.bind(this)
this.deleteLabel = this.deleteLabel.bind(this)
- this._addLabelToLocalUpdate = this._addLabelToLocalUpdate.bind(this)
+ this._addLabelLocally = this._addLabelLocally.bind(this)
this.ide = ide
this.$scope = $scope
- this.reset()
+ this.localStorage = localStorage
this.$scope.HistoryViewModes = HistoryViewModes
+ this._localStorageViewModeProjKey = `history.userPrefs.viewMode.${
+ $scope.project_id
+ }`
+ this._localStorageShowOnlyLabelsProjKey = `history.userPrefs.showOnlyLabels.${
+ $scope.project_id
+ }`
+
+ this.hardReset()
this.$scope.toggleHistory = () => {
if (this.$scope.ui.view === 'history') {
this.hide()
} else {
this.show()
+ this._handleHistoryUIStateChange()
}
- return this.ide.$timeout(() => {
- return this.$scope.$broadcast('history:toggle')
+ this.ide.$timeout(() => {
+ this.$scope.$broadcast('history:toggle')
}, 0)
}
- this.$scope.toggleHistoryViewMode = () => {
- if (this.$scope.history.viewMode === HistoryViewModes.COMPARE) {
- this.reset()
- this.$scope.history.viewMode = HistoryViewModes.POINT_IN_TIME
- } else {
- this.reset()
- this.$scope.history.viewMode = HistoryViewModes.COMPARE
- }
- return this.ide.$timeout(() => {
- return this.$scope.$broadcast('history:toggle')
- }, 0)
- }
-
- this.$scope.$watch('history.selection.updates', updates => {
- if (this.$scope.history.viewMode === HistoryViewModes.COMPARE) {
- if (updates != null && updates.length > 0) {
- this._selectDocFromUpdates()
- return this.reloadDiff()
- }
- }
- })
-
- this.$scope.$watch('history.selection.pathname', pathname => {
- if (this.$scope.history.viewMode === HistoryViewModes.POINT_IN_TIME) {
- if (pathname != null) {
- return this.loadFileAtPointInTime()
- }
- } else {
- return this.reloadDiff()
- }
- })
-
- this.$scope.$watch(
- 'history.showOnlyLabels',
- (showOnlyLabels, prevVal) => {
- if (showOnlyLabels != null && showOnlyLabels !== prevVal) {
- if (showOnlyLabels) {
- return this.selectedLabelFromUpdatesSelection()
- } else {
- this.$scope.history.selection.label = null
- if (this.$scope.history.selection.updates.length === 0) {
- return this.autoSelectLastUpdate()
- }
+ this.$scope.$watchGroup(
+ ['history.selection.range.toV', 'history.selection.range.fromV'],
+ (newRange, prevRange) => {
+ if (this.$scope.history.viewMode === HistoryViewModes.COMPARE) {
+ let [newTo, newFrom] = newRange
+ let [prevTo, prevFrom] = prevRange
+ if (
+ newTo &&
+ newFrom &&
+ newTo !== prevTo &&
+ newFrom !== prevFrom
+ ) {
+ this.loadFileTreeDiff(newTo, newFrom)
}
}
}
)
-
- this.$scope.$watch('history.updates.length', () => {
- return this.recalculateSelectedUpdates()
- })
}
show() {
this.$scope.ui.view = 'history'
- this.reset()
- return (this.$scope.history.viewMode = HistoryViewModes.POINT_IN_TIME)
+ this.hardReset()
+ if (this.$scope.history.showOnlyLabels) {
+ this.fetchNextBatchOfUpdates()
+ }
}
hide() {
- return (this.$scope.ui.view = 'editor')
+ this.$scope.ui.view = 'editor'
}
- reset() {
- return (this.$scope.history = {
+ _getViewModeUserPref() {
+ return (
+ this.localStorage(this._localStorageViewModeProjKey) ||
+ HistoryViewModes.POINT_IN_TIME
+ )
+ }
+ _getShowOnlyLabelsUserPref() {
+ return (
+ this.localStorage(this._localStorageShowOnlyLabelsProjKey) || false
+ )
+ }
+
+ _setViewModeUserPref(viewModeUserPref) {
+ if (
+ viewModeUserPref === HistoryViewModes.POINT_IN_TIME ||
+ viewModeUserPref === HistoryViewModes.COMPARE
+ ) {
+ this.localStorage(this._localStorageViewModeProjKey, viewModeUserPref)
+ }
+ }
+ _setShowOnlyLabelsUserPref(showOnlyLabelsUserPref) {
+ this.localStorage(
+ this._localStorageShowOnlyLabelsProjKey,
+ !!showOnlyLabelsUserPref
+ )
+ }
+
+ hardReset() {
+ this.$scope.history = {
isV2: true,
updates: [],
- viewMode: null,
+ viewMode: this._getViewModeUserPref(),
nextBeforeTimestamp: null,
atEnd: false,
- userHasFullFeature:
- __guard__(
- this.$scope.project != null
- ? this.$scope.project.features
- : undefined,
- x => x.versioning
- ) || false,
+ userHasFullFeature: undefined,
freeHistoryLimitHit: false,
selection: {
- label: null,
- updates: [],
docs: {},
pathname: null,
range: {
fromV: null,
toV: null
- }
+ },
+ hoveredRange: {
+ fromV: null,
+ toV: null
+ },
+ diff: null, // When history.viewMode == HistoryViewModes.COMPARE
+ files: [], // When history.viewMode == HistoryViewModes.COMPARE
+ update: null, // When history.viewMode == HistoryViewModes.POINT_IN_TIME
+ label: null, // When history.viewMode == HistoryViewModes.POINT_IN_TIME
+ file: null
},
error: null,
- showOnlyLabels: false,
+ showOnlyLabels: this._getShowOnlyLabelsUserPref(),
labels: null,
- files: [],
+ loadingFileTree: true
+ }
+ let _deregisterFeatureWatcher = this.$scope.$watch(
+ 'project.features.versioning',
+ hasVersioning => {
+ if (hasVersioning != null) {
+ this.$scope.history.userHasFullFeature = hasVersioning
+ _deregisterFeatureWatcher()
+ }
+ }
+ )
+ }
+
+ softReset() {
+ this.$scope.history.viewMode = this._getViewModeUserPref()
+ this.$scope.history.selection = {
+ docs: {},
+ pathname: null,
+ range: {
+ fromV: null,
+ toV: null
+ },
+ hoveredRange: {
+ fromV: null,
+ toV: null
+ },
diff: null, // When history.viewMode == HistoryViewModes.COMPARE
- selectedFile: null // When history.viewMode == HistoryViewModes.POINT_IN_TIME
- })
+ files: [], // When history.viewMode == HistoryViewModes.COMPARE
+ update: null, // When history.viewMode == HistoryViewModes.POINT_IN_TIME
+ label: null, // When history.viewMode == HistoryViewModes.POINT_IN_TIME
+ file: null
+ }
+ this.$scope.history.error = null
+ this.$scope.history.showOnlyLabels = this._getShowOnlyLabelsUserPref()
+ this.$scope.history.loadingFileTree = true
+ }
+
+ toggleHistoryViewMode() {
+ if (this.$scope.history.viewMode === HistoryViewModes.COMPARE) {
+ this.softReset()
+ this.$scope.history.viewMode = HistoryViewModes.POINT_IN_TIME
+ this._setViewModeUserPref(HistoryViewModes.POINT_IN_TIME)
+ } else {
+ this.softReset()
+ this.$scope.history.viewMode = HistoryViewModes.COMPARE
+ this._setViewModeUserPref(HistoryViewModes.COMPARE)
+ }
+ this._handleHistoryUIStateChange()
+ this.ide.$timeout(() => {
+ this.$scope.$broadcast('history:toggle')
+ }, 0)
+ }
+
+ _handleHistoryUIStateChange() {
+ if (this.$scope.history.viewMode === HistoryViewModes.COMPARE) {
+ if (this.$scope.history.showOnlyLabels) {
+ this.autoSelectLabelsForComparison()
+ } else {
+ this.autoSelectRecentUpdates()
+ }
+ } else {
+ // Point-in-time mode
+ if (this.$scope.history.showOnlyLabels) {
+ this.selectLabelFromUpdatesSelection()
+ } else {
+ this.autoSelectLastVersionForPointInTime()
+ }
+ }
+ }
+
+ setHoverFrom(fromV) {
+ let selection = this.$scope.history.selection
+ selection.hoveredRange.fromV = fromV
+ selection.hoveredRange.toV = selection.range.toV
+ this.$scope.history.hoveringOverListSelectors = true
+ }
+
+ setHoverTo(toV) {
+ let selection = this.$scope.history.selection
+ selection.hoveredRange.toV = toV
+ selection.hoveredRange.fromV = selection.range.fromV
+ this.$scope.history.hoveringOverListSelectors = true
+ }
+
+ resetHover() {
+ let selection = this.$scope.history.selection
+ selection.hoveredRange.toV = null
+ selection.hoveredRange.fromV = null
+ this.$scope.history.hoveringOverListSelectors = false
+ }
+
+ showAllUpdates() {
+ if (this.$scope.history.showOnlyLabels) {
+ this.$scope.history.showOnlyLabels = false
+ this._setShowOnlyLabelsUserPref(false)
+ this._handleHistoryUIStateChange()
+ }
+ }
+
+ showOnlyLabels() {
+ if (!this.$scope.history.showOnlyLabels) {
+ this.$scope.history.showOnlyLabels = true
+ this._setShowOnlyLabelsUserPref(true)
+ this._handleHistoryUIStateChange()
+ }
}
restoreFile(version, pathname) {
@@ -172,23 +276,55 @@ define([
}
loadFileTreeForVersion(version) {
+ return this._loadFileTree(version, version)
+ }
+
+ loadFileTreeDiff(toV, fromV) {
+ return this._loadFileTree(toV, fromV)
+ }
+
+ _loadFileTree(toV, fromV) {
let url = `/project/${this.$scope.project_id}/filetree/diff`
- const query = [`from=${version}`, `to=${version}`]
+ let selection = this.$scope.history.selection
+ const query = [`from=${fromV}`, `to=${toV}`]
url += `?${query.join('&')}`
this.$scope.history.loadingFileTree = true
- this.$scope.history.selectedFile = null
- this.$scope.history.selection.pathname = null
- return this.ide.$http.get(url).then(response => {
- this.$scope.history.files = response.data.diff
- return (this.$scope.history.loadingFileTree = false)
- })
+ selection.file = null
+ selection.pathname = null
+ if (selection.diff) {
+ selection.diff.loading = true
+ }
+ return this.ide.$http
+ .get(url)
+ .then(response => {
+ this.$scope.history.selection.files = response.data.diff
+ })
+ .finally(() => {
+ this.$scope.history.loadingFileTree = false
+ if (selection.diff) {
+ selection.diff.loading = true
+ }
+ })
}
+
+ selectFile(file) {
+ if (file != null && file.pathname != null) {
+ this.$scope.history.selection.pathname = file.pathname
+ this.$scope.history.selection.file = file
+ if (this.$scope.history.viewMode === HistoryViewModes.POINT_IN_TIME) {
+ this.loadFileAtPointInTime()
+ } else {
+ this.reloadDiff()
+ }
+ }
+ }
+
autoSelectRecentUpdates() {
if (this.$scope.history.updates.length === 0) {
return
}
- this.$scope.history.updates[0].selectedTo = true
+ this.$scope.history.selection.range.toV = this.$scope.history.updates[0].toV
let indexOfLastUpdateNotByMe = 0
for (let i = 0; i < this.$scope.history.updates.length; i++) {
@@ -202,73 +338,77 @@ define([
indexOfLastUpdateNotByMe = i
}
- return (this.$scope.history.updates[
+ this.$scope.history.selection.range.fromV = this.$scope.history.updates[
indexOfLastUpdateNotByMe
- ].selectedFrom = true)
+ ].fromV
}
- autoSelectLastUpdate() {
+ autoSelectLastVersionForPointInTime() {
+ this.$scope.history.selection.label = null
if (this.$scope.history.updates.length === 0) {
return
}
- return this.selectUpdate(this.$scope.history.updates[0])
+ return this.selectVersionForPointInTime(
+ this.$scope.history.updates[0].toV
+ )
}
autoSelectLastLabel() {
- if (this.$scope.history.labels.length === 0) {
+ if (
+ this.$scope.history.labels == null ||
+ this.$scope.history.labels.length === 0
+ ) {
return
}
- return this.selectLabel(this.$scope.history.labels[0])
+ return this.selectLabelForPointInTime(this.$scope.history.labels[0])
}
- selectUpdate(update) {
- let selectedUpdateIndex = this.$scope.history.updates.indexOf(update)
- if (selectedUpdateIndex === -1) {
- selectedUpdateIndex = 0
+ expandSelectionToVersion(version) {
+ if (version > this.$scope.history.selection.range.toV) {
+ this.$scope.history.selection.range.toV = version
+ } else if (version < this.$scope.history.selection.range.fromV) {
+ this.$scope.history.selection.range.fromV = version
}
- for (update of Array.from(this.$scope.history.updates)) {
- update.selectedTo = false
- update.selectedFrom = false
- }
- this.$scope.history.updates[selectedUpdateIndex].selectedTo = true
- this.$scope.history.updates[selectedUpdateIndex].selectedFrom = true
- this.recalculateSelectedUpdates()
- return this.loadFileTreeForVersion(
- this.$scope.history.updates[selectedUpdateIndex].toV
- )
}
- selectedLabelFromUpdatesSelection() {
- // Get the number of labels associated with the currently selected update
- const nSelectedLabels = __guard__(
- __guard__(
- this.$scope.history.selection.updates != null
- ? this.$scope.history.selection.updates[0]
- : undefined,
- x1 => x1.labels
- ),
- x => x.length
+ selectVersionForPointInTime(version) {
+ let selection = this.$scope.history.selection
+ selection.range.toV = version
+ selection.range.fromV = version
+ selection.update = this._getUpdateForVersion(version)
+ this.loadFileTreeForVersion(version)
+ }
+
+ selectLabelFromUpdatesSelection() {
+ const selectedUpdate = this._getUpdateForVersion(
+ this.$scope.history.selection.range.toV
)
+ let nSelectedLabels = 0
+
+ if (selectedUpdate != null && selectedUpdate.labels != null) {
+ nSelectedLabels = selectedUpdate.labels.length
+ }
+
// If the currently selected update has no labels, select the last one (version-wise)
if (nSelectedLabels === 0) {
- return this.autoSelectLastLabel()
+ this.autoSelectLastLabel()
// If the update has one label, select it
} else if (nSelectedLabels === 1) {
- return this.selectLabel(
- this.$scope.history.selection.updates[0].labels[0]
+ this.selectLabelForPointInTime(
+ this.$scope.history.selection.update.labels[0]
)
// If there are multiple labels for the update, select the latest
} else if (nSelectedLabels > 1) {
const sortedLabels = this.ide.$filter('orderBy')(
- this.$scope.history.selection.updates[0].labels,
+ selectedUpdate.labels,
'-created_at'
)
const lastLabelFromUpdate = sortedLabels[0]
- return this.selectLabel(lastLabelFromUpdate)
+ this.selectLabelForPointInTime(lastLabelFromUpdate)
}
}
- selectLabel(labelToSelect) {
+ selectLabelForPointInTime(labelToSelect) {
let updateToSelect = null
if (this._isLabelSelected(labelToSelect)) {
@@ -285,45 +425,45 @@ define([
this.$scope.history.selection.label = labelToSelect
if (updateToSelect != null) {
- return this.selectUpdate(updateToSelect)
+ this.selectVersionForPointInTime(updateToSelect.toV)
} else {
- this.$scope.history.selection.updates = []
- return this.loadFileTreeForVersion(labelToSelect.version)
+ let selection = this.$scope.history.selection
+ selection.range.toV = labelToSelect.version
+ selection.range.fromV = labelToSelect.version
+ selection.update = null
+ this.loadFileTreeForVersion(labelToSelect.version)
}
}
- recalculateSelectedUpdates() {
- let beforeSelection = true
- let afterSelection = false
- this.$scope.history.selection.updates = []
- return (() => {
- const result = []
- for (let update of Array.from(this.$scope.history.updates)) {
- var inSelection
- if (update.selectedTo) {
- inSelection = true
- beforeSelection = false
- }
-
- update.beforeSelection = beforeSelection
- update.inSelection = inSelection
- update.afterSelection = afterSelection
-
- if (inSelection) {
- this.$scope.history.selection.updates.push(update)
- }
-
- if (update.selectedFrom) {
- inSelection = false
- result.push((afterSelection = true))
- } else {
- result.push(undefined)
- }
+ _getUpdateForVersion(version) {
+ for (let update of this.$scope.history.updates) {
+ if (update.toV === version) {
+ return update
}
- return result
- })()
+ }
}
+
+ autoSelectLabelsForComparison() {
+ let labels = this.$scope.history.labels
+ let selection = this.$scope.history.selection
+ let nLabels = 0
+ if (Array.isArray(labels)) {
+ nLabels = labels.length
+ }
+ if (nLabels === 1) {
+ selection.range.toV = labels[0].version
+ selection.range.fromV = labels[0].version
+ } else if (nLabels > 1) {
+ selection.range.toV = labels[0].version
+ selection.range.fromV = labels[1].version
+ }
+ }
+
fetchNextBatchOfUpdates() {
+ if (this.$scope.history.atEnd) {
+ return
+ }
+
let updatesURL = `/project/${this.ide.project_id}/updates?min_count=${
this.BATCH_SIZE
}`
@@ -332,9 +472,6 @@ define([
}
const labelsURL = `/project/${this.ide.project_id}/labels`
- this.$scope.history.loading = true
- this.$scope.history.loadingFileTree = true
-
const requests = { updates: this.ide.$http.get(updatesURL) }
if (this.$scope.history.labels == null) {
@@ -346,8 +483,9 @@ define([
.then(response => {
const updatesData = response.updates.data
if (response.labels != null) {
- this.$scope.history.labels = this._sortLabelsByVersionAndDate(
- response.labels.data
+ this.$scope.history.labels = this._loadLabels(
+ response.labels.data,
+ updatesData.updates[0].toV
)
}
this._loadUpdates(updatesData.updates)
@@ -359,36 +497,59 @@ define([
) {
this.$scope.history.atEnd = true
}
- this.$scope.history.loading = false
if (this.$scope.history.updates.length === 0) {
- return (this.$scope.history.loadingFileTree = false)
+ this.$scope.history.loadingFileTree = false
}
})
.catch(error => {
const { status, statusText } = error
this.$scope.history.error = { status, statusText }
- this.$scope.history.loading = false
- return (this.$scope.history.loadingFileTree = false)
})
}
+ _loadLabels(labels, lastUpdateToV) {
+ let sortedLabels = this._sortLabelsByVersionAndDate(labels)
+ let hasPseudoCurrentStateLabel = false
+ let needsPseudoCurrentStateLabel = false
+ if (sortedLabels.length > 0 && lastUpdateToV) {
+ hasPseudoCurrentStateLabel = sortedLabels[0].isPseudoCurrentStateLabel
+ if (hasPseudoCurrentStateLabel) {
+ needsPseudoCurrentStateLabel =
+ sortedLabels.length > 1
+ ? sortedLabels[1].version !== lastUpdateToV
+ : false
+ } else {
+ needsPseudoCurrentStateLabel =
+ sortedLabels[0].version !== lastUpdateToV
+ }
+ if (needsPseudoCurrentStateLabel && !hasPseudoCurrentStateLabel) {
+ sortedLabels.unshift({
+ id: '1',
+ isPseudoCurrentStateLabel: true,
+ version: lastUpdateToV,
+ created_at: new Date().toISOString()
+ })
+ } else if (
+ !needsPseudoCurrentStateLabel &&
+ hasPseudoCurrentStateLabel
+ ) {
+ sortedLabels.shift()
+ }
+ }
+ return sortedLabels
+ }
+
_sortLabelsByVersionAndDate(labels) {
- return this.ide.$filter('orderBy')(labels, ['-version', '-created_at'])
+ return this.ide.$filter('orderBy')(labels, [
+ 'isPseudoCurrentStateLabel',
+ '-version',
+ '-created_at'
+ ])
}
loadFileAtPointInTime() {
- let toV
+ const toV = this.$scope.history.selection.range.toV
const { pathname } = this.$scope.history.selection
- if (
- (this.$scope.history.selection.updates != null
- ? this.$scope.history.selection.updates[0]
- : undefined) != null
- ) {
- ;({ toV } = this.$scope.history.selection.updates[0])
- } else if (this.$scope.history.selection.label != null) {
- toV = this.$scope.history.selection.label.version
- }
-
if (toV == null) {
return
}
@@ -399,25 +560,27 @@ define([
`to=${toV}`
]
url += `?${query.join('&')}`
- this.$scope.history.selectedFile = { loading: true }
+ this.$scope.history.selection.file.loading = true
return this.ide.$http
.get(url)
.then(response => {
const { text, binary } = this._parseDiff(response.data.diff)
- this.$scope.history.selectedFile.binary = binary
- this.$scope.history.selectedFile.text = text
- return (this.$scope.history.selectedFile.loading = false)
+ this.$scope.history.selection.file.binary = binary
+ this.$scope.history.selection.file.text = text
+ this.$scope.history.selection.file.loading = false
})
.catch(function() {})
}
reloadDiff() {
- let { diff } = this.$scope.history
- const { updates } = this.$scope.history.selection
- const { fromV, toV, pathname } = this._calculateDiffDataFromSelection()
+ let { diff } = this.$scope.history.selection
+ // const { updates } = this.$scope.history.selection
+ // const { fromV, toV, pathname } = this._calculateDiffDataFromSelection()
+ const { range, pathname } = this.$scope.history.selection
+ const { fromV, toV } = range
if (pathname == null) {
- this.$scope.history.diff = null
+ this.$scope.history.selection.diff = null
return
}
@@ -427,10 +590,10 @@ define([
diff.fromV === fromV &&
diff.toV === toV
) {
- return
+ return this.ide.$q.when(true)
}
- this.$scope.history.diff = diff = {
+ this.$scope.history.selection.diff = diff = {
fromV,
toV,
pathname,
@@ -444,7 +607,6 @@ define([
query.push(`from=${diff.fromV}`, `to=${diff.toV}`)
}
url += `?${query.join('&')}`
-
return this.ide.$http
.get(url)
.then(response => {
@@ -453,18 +615,18 @@ define([
const { text, highlights, binary } = this._parseDiff(data.diff)
diff.binary = binary
diff.text = text
- return (diff.highlights = highlights)
+ diff.highlights = highlights
})
.catch(function() {
diff.loading = false
- return (diff.error = true)
+ diff.error = true
})
}
labelCurrentVersion(labelComment) {
return this._labelVersion(
labelComment,
- this.$scope.history.selection.updates[0].toV
+ this.$scope.history.selection.range.toV
)
}
@@ -484,15 +646,6 @@ define([
})
}
- _isLabelSelected(label) {
- return (
- label.id ===
- (this.$scope.history.selection.label != null
- ? this.$scope.history.selection.label.id
- : undefined)
- )
- }
-
_deleteLabelLocally(labelToDelete) {
for (let i = 0; i < this.$scope.history.updates.length; i++) {
const update = this.$scope.history.updates[i]
@@ -504,10 +657,22 @@ define([
break
}
}
- return (this.$scope.history.labels = _.filter(
- this.$scope.history.labels,
- label => label.id !== labelToDelete.id
- ))
+ this.$scope.history.labels = this._loadLabels(
+ _.filter(
+ this.$scope.history.labels,
+ label => label.id !== labelToDelete.id
+ ),
+ this.$scope.history.updates[0].toV
+ )
+ this._handleHistoryUIStateChange()
+ }
+
+ _isLabelSelected(label) {
+ if (this.$scope.history.selection.label) {
+ return label.id === this.$scope.history.selection.label.id
+ } else {
+ return false
+ }
}
_parseDiff(diff) {
@@ -600,7 +765,6 @@ define([
user.hue = ColorManager.getHueForUserId(user.id)
}
}
-
if (
previousUpdate == null ||
!moment(previousUpdate.meta.end_ts).isSame(
@@ -611,10 +775,6 @@ define([
update.meta.first_in_day = true
}
- update.selectedFrom = false
- update.selectedTo = false
- update.inSelection = false
-
previousUpdate = update
if (
@@ -638,15 +798,7 @@ define([
)
if (firstLoad) {
- if (this.$scope.history.viewMode === HistoryViewModes.COMPARE) {
- return this.autoSelectRecentUpdates()
- } else {
- if (this.$scope.history.showOnlyLabels) {
- return this.autoSelectLastLabel()
- } else {
- return this.autoSelectLastUpdate()
- }
- }
+ this._handleHistoryUIStateChange()
}
}
@@ -659,135 +811,23 @@ define([
_csrf: window.csrfToken
})
.then(response => {
- return this._addLabelToLocalUpdate(response.data)
+ return this._addLabelLocally(response.data)
})
}
- _addLabelToLocalUpdate(label) {
+ _addLabelLocally(label) {
const localUpdate = _.find(
this.$scope.history.updates,
update => update.toV === label.version
)
if (localUpdate != null) {
- localUpdate.labels = this._sortLabelsByVersionAndDate(
- localUpdate.labels.concat(label)
- )
+ localUpdate.labels = localUpdate.labels.concat(label)
}
- return (this.$scope.history.labels = this._sortLabelsByVersionAndDate(
- this.$scope.history.labels.concat(label)
- ))
- }
-
- _perDocSummaryOfUpdates(updates) {
- // Track current_pathname -> original_pathname
- // create bare object for use as Map
- // http://ryanmorr.com/true-hash-maps-in-javascript/
- const original_pathnames = Object.create(null)
-
- // Map of original pathname -> doc summary
- const docs_summary = Object.create(null)
-
- const updatePathnameWithUpdateVersions = function(
- pathname,
- update,
- deletedAtV
- ) {
- // docs_summary is indexed by the original pathname the doc
- // had at the start, so we have to look this up from the current
- // pathname via original_pathname first
- if (original_pathnames[pathname] == null) {
- original_pathnames[pathname] = pathname
- }
- const original_pathname = original_pathnames[pathname]
- const doc_summary =
- docs_summary[original_pathname] != null
- ? docs_summary[original_pathname]
- : (docs_summary[original_pathname] = {
- fromV: update.fromV,
- toV: update.toV
- })
- doc_summary.fromV = Math.min(doc_summary.fromV, update.fromV)
- doc_summary.toV = Math.max(doc_summary.toV, update.toV)
- if (deletedAtV != null) {
- return (doc_summary.deletedAtV = deletedAtV)
- }
- }
-
- // Put updates in ascending chronological order
- updates = updates.slice().reverse()
- for (let update of Array.from(updates)) {
- for (let pathname of Array.from(update.pathnames || [])) {
- updatePathnameWithUpdateVersions(pathname, update)
- }
- for (let project_op of Array.from(update.project_ops || [])) {
- if (project_op.rename != null) {
- const { rename } = project_op
- updatePathnameWithUpdateVersions(rename.pathname, update)
- original_pathnames[rename.newPathname] =
- original_pathnames[rename.pathname]
- delete original_pathnames[rename.pathname]
- }
- if (project_op.add != null) {
- const { add } = project_op
- updatePathnameWithUpdateVersions(add.pathname, update)
- }
- if (project_op.remove != null) {
- const { remove } = project_op
- updatePathnameWithUpdateVersions(
- remove.pathname,
- update,
- project_op.atV
- )
- }
- }
- }
-
- return docs_summary
- }
-
- _calculateDiffDataFromSelection() {
- let pathname, toV
- let fromV = (toV = pathname = null)
-
- const selected_pathname = this.$scope.history.selection.pathname
-
- const object = this._perDocSummaryOfUpdates(
- this.$scope.history.selection.updates
+ this.$scope.history.labels = this._loadLabels(
+ this.$scope.history.labels.concat(label),
+ this.$scope.history.updates[0].toV
)
- for (pathname in object) {
- const doc = object[pathname]
- if (pathname === selected_pathname) {
- ;({ fromV, toV } = doc)
- return { fromV, toV, pathname }
- }
- }
-
- return {}
- }
-
- // 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 pathname
- const affected_docs = this._perDocSummaryOfUpdates(
- this.$scope.history.selection.updates
- )
- this.$scope.history.selection.docs = affected_docs
-
- let selected_pathname = this.$scope.history.selection.pathname
- if (selected_pathname != null && affected_docs[selected_pathname]) {
- // Selected doc is already open
- } else {
- // Set to first possible candidate
- for (pathname in affected_docs) {
- const doc = affected_docs[pathname]
- selected_pathname = pathname
- break
- }
- }
-
- return (this.$scope.history.selection.pathname = selected_pathname)
+ this._handleHistoryUIStateChange()
}
_updateContainsUserId(update, user_id) {
diff --git a/services/web/public/src/ide/history/components/historyEntriesList.js b/services/web/public/src/ide/history/components/historyEntriesList.js
index 5e38720fb5..6df211d326 100644
--- a/services/web/public/src/ide/history/components/historyEntriesList.js
+++ b/services/web/public/src/ide/history/components/historyEntriesList.js
@@ -31,8 +31,7 @@ define(['base'], function(App) {
}
ctrl.onEntryLinked = function(entry, $entryEl) {
if (
- entry.selectedTo &&
- entry.selectedFrom &&
+ entry.toV === ctrl.selectedHistoryVersion &&
!_isEntryElVisible($entryEl)
) {
return $scope.$applyAsync(() =>
@@ -57,6 +56,7 @@ define(['base'], function(App) {
currentUser: '<',
freeHistoryLimitHit: '<',
currentUserIsOwner: '<',
+ selectedHistoryVersion: '<',
onEntrySelect: '&',
onLabelDelete: '&'
},
diff --git a/services/web/public/src/ide/history/components/historyEntry.js b/services/web/public/src/ide/history/components/historyEntry.js
index ee960a5960..4f8b92da22 100644
--- a/services/web/public/src/ide/history/components/historyEntry.js
+++ b/services/web/public/src/ide/history/components/historyEntry.js
@@ -43,7 +43,7 @@ define([
(user != null ? user._id : undefined) ||
(user != null ? user.id : undefined)
const hue = ColorManager.getHueForUserId(curUserId) || 100
- if (ctrl.entry.inSelection) {
+ if (ctrl.entry.toV === ctrl.selectedHistoryVersion) {
return { color: '#FFF' }
} else {
return { color: `hsl(${hue}, 70%, 50%)` }
@@ -61,6 +61,7 @@ define([
entry: '<',
currentUser: '<',
users: '<',
+ selectedHistoryVersion: '<',
onSelect: '&',
onLabelDelete: '&'
},
diff --git a/services/web/public/src/ide/history/components/historyFileEntity.js b/services/web/public/src/ide/history/components/historyFileEntity.js
index e005b883d5..123d740f16 100644
--- a/services/web/public/src/ide/history/components/historyFileEntity.js
+++ b/services/web/public/src/ide/history/components/historyFileEntity.js
@@ -10,17 +10,60 @@
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
-define(['base', 'ide/file-tree/util/iconTypeFromName'], function(
- App,
- iconTypeFromName
-) {
- // TODO Add arrows in folders
+define([
+ 'base',
+ 'ide/file-tree/util/iconTypeFromName',
+ 'ide/file-tree/util/fileOperationI18nNames'
+], function(App, iconTypeFromName, fileOperationI18nNames) {
const historyFileEntityController = function($scope, $element, $attrs) {
const ctrl = this
+ ctrl.hasOperation = false
+ ctrl.getRenameTooltip = i18nRenamedStr => {
+ let [simplifiedOldPathname, simplifiedPathname] = _getSimplifiedPaths(
+ ctrl.fileEntity.oldPathname,
+ ctrl.fileEntity.pathname
+ )
+ return `${
+ fileOperationI18nNames.renamed
+ } ${simplifiedOldPathname} → ${simplifiedPathname}`
+ }
+ ctrl.getFileOperationName = () => {
+ if (ctrl.fileEntity.operation === 'edited') {
+ return fileOperationI18nNames.edited
+ } else if (ctrl.fileEntity.operation === 'renamed') {
+ return fileOperationI18nNames.renamed
+ } else if (ctrl.fileEntity.operation === 'added') {
+ return fileOperationI18nNames.created
+ } else if (ctrl.fileEntity.operation === 'removed') {
+ return fileOperationI18nNames.deleted
+ } else {
+ return ''
+ }
+ }
+
+ const _getSimplifiedPaths = (path1, path2) => {
+ let path1Parts = path1.split('/')
+ let path2Parts = path2.split('/')
+ let maxIterations = Math.min(path1Parts.length, path2Parts.length) - 1
+ for (
+ var commonPartIndex = 0;
+ commonPartIndex < maxIterations;
+ commonPartIndex++
+ ) {
+ if (path1Parts[commonPartIndex] !== path2Parts[commonPartIndex]) {
+ break
+ }
+ }
+ path1Parts.splice(0, commonPartIndex)
+ path2Parts.splice(0, commonPartIndex)
+ return [path1Parts.join('/'), path2Parts.join('/')]
+ }
+
const _handleFolderClick = function() {
ctrl.isOpen = !ctrl.isOpen
- return (ctrl.iconClass = _getFolderIcon())
+ ctrl.entityTypeIconClass = _getFolderIcon()
}
+
const _handleFileClick = () =>
ctrl.historyFileTreeController.handleEntityClick(ctrl.fileEntity)
var _getFolderIcon = function() {
@@ -33,12 +76,20 @@ define(['base', 'ide/file-tree/util/iconTypeFromName'], function(
ctrl.$onInit = function() {
if (ctrl.fileEntity.type === 'folder') {
ctrl.isOpen = true
- ctrl.iconClass = _getFolderIcon()
- return (ctrl.handleClick = _handleFolderClick)
+ ctrl.entityTypeIconClass = _getFolderIcon()
+ ctrl.handleClick = _handleFolderClick
} else {
- ctrl.iconClass = `fa-${iconTypeFromName(ctrl.fileEntity.name)}`
+ if (ctrl.fileEntity.operation) {
+ ctrl.hasOperation = true
+ }
+ ctrl.entityTypeIconClass = `fa-${iconTypeFromName(
+ ctrl.fileEntity.name
+ )}`
+ ctrl.entityOpTextClass = ctrl.fileEntity.operation
+ ? `history-file-entity-name-${ctrl.fileEntity.operation}`
+ : null
ctrl.handleClick = _handleFileClick
- return $scope.$watch(
+ $scope.$watch(
() => ctrl.historyFileTreeController.selectedPathname,
newPathname =>
(ctrl.isSelected = ctrl.fileEntity.pathname === newPathname)
diff --git a/services/web/public/src/ide/history/components/historyLabel.js b/services/web/public/src/ide/history/components/historyLabel.js
index 90857143c1..e3ebc9fb60 100644
--- a/services/web/public/src/ide/history/components/historyLabel.js
+++ b/services/web/public/src/ide/history/components/historyLabel.js
@@ -19,8 +19,14 @@ define(['base'], function(App) {
_
) {
const ctrl = this
- ctrl.$onInit = () =>
- ctrl.showTooltip != null ? ctrl.showTooltip : (ctrl.showTooltip = true)
+ ctrl.$onInit = () => {
+ if (ctrl.showTooltip == null) {
+ ctrl.showTooltip = true
+ }
+ if (ctrl.isPseudoCurrentStateLabel == null) {
+ ctrl.isPseudoCurrentStateLabel = false
+ }
+ }
}
return App.component('historyLabel', {
@@ -29,6 +35,7 @@ define(['base'], function(App) {
labelOwnerName: '',
labelCreationDateTime: '',
isOwnedByCurrentUser: '<',
+ isPseudoCurrentStateLabel: '<',
onLabelDelete: '&',
showTooltip: ''
},
diff --git a/services/web/public/src/ide/history/controllers/HistoryCompareController.js b/services/web/public/src/ide/history/controllers/HistoryCompareController.js
new file mode 100644
index 0000000000..59dae6dfc3
--- /dev/null
+++ b/services/web/public/src/ide/history/controllers/HistoryCompareController.js
@@ -0,0 +1,85 @@
+/* eslint-disable
+ camelcase,
+ max-len,
+ no-return-assign,
+ no-undef,
+*/
+define(['base', 'ide/history/util/displayNameForUser'], function(
+ App,
+ displayNameForUser
+) {
+ App.controller('HistoryCompareController', [
+ '$scope',
+ '$modal',
+ 'ide',
+ '_',
+ function($scope, $modal, ide, _) {
+ $scope.projectUsers = []
+ $scope.versionsWithLabels = []
+
+ $scope.$watch('project.members', function(newVal) {
+ if (newVal != null) {
+ $scope.projectUsers = newVal.concat($scope.project.owner)
+ }
+ })
+
+ $scope.$watchCollection('history.labels', function(labels) {
+ if (labels != null && labels.length > 0) {
+ const groupedLabelsHash = _.groupBy(labels, 'version')
+ $scope.versionsWithLabels = _.map(
+ groupedLabelsHash,
+ (labels, version) => {
+ return {
+ version: parseInt(version, 10),
+ labels
+ }
+ }
+ )
+ }
+ })
+
+ $scope.loadMore = () => ide.historyManager.fetchNextBatchOfUpdates()
+
+ $scope.setHoverFrom = fromV => ide.historyManager.setHoverFrom(fromV)
+
+ $scope.setHoverTo = toV => ide.historyManager.setHoverTo(toV)
+
+ $scope.resetHover = () => ide.historyManager.resetHover()
+
+ $scope.select = (toV, fromV) => {
+ $scope.history.selection.range.toV = toV
+ $scope.history.selection.range.fromV = fromV
+ }
+
+ $scope.addLabelVersionToSelection = version => {
+ ide.historyManager.expandSelectionToVersion(version)
+ }
+
+ // 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.
+ $scope.getUserById = id =>
+ _.find($scope.projectUsers, function(user) {
+ let curUserId
+ if (user) {
+ curUserId = user._id || user.id
+ }
+ return curUserId === id
+ })
+
+ $scope.getDisplayNameById = id =>
+ displayNameForUser($scope.getUserById(id))
+
+ $scope.deleteLabel = labelDetails =>
+ $modal.open({
+ templateUrl: 'historyV2DeleteLabelModalTemplate',
+ controller: 'HistoryV2DeleteLabelModalController',
+ resolve: {
+ labelDetails() {
+ return labelDetails
+ }
+ }
+ })
+ }
+ ])
+})
diff --git a/services/web/public/src/ide/history/controllers/HistoryListController.js b/services/web/public/src/ide/history/controllers/HistoryListController.js
deleted file mode 100644
index 51786bcf99..0000000000
--- a/services/web/public/src/ide/history/controllers/HistoryListController.js
+++ /dev/null
@@ -1,223 +0,0 @@
-/* eslint-disable
- camelcase,
- max-len,
- no-return-assign,
- no-undef,
-*/
-// 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
- */
-define(['base', 'ide/history/util/displayNameForUser'], function(
- App,
- displayNameForUser
-) {
- App.controller('HistoryListController', [
- '$scope',
- '$modal',
- 'ide',
- 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 (let update of Array.from($scope.history.updates)) {
- 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 (var 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 (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 (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 (let 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()
- )
- }
- ])
-
- return App.controller('HistoryListItemController', [
- '$scope',
- 'event_tracking',
- function($scope, event_tracking) {
- $scope.$watch('update.selectedFrom', function(
- selectedFrom,
- oldSelectedFrom
- ) {
- if (selectedFrom) {
- for (let 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 (let update of Array.from($scope.history.updates)) {
- if (update !== $scope.update) {
- update.selectedTo = false
- }
- }
- return $scope.recalculateSelectedUpdates()
- }
- })
-
- $scope.select = function() {
- event_tracking.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)
- }
- ])
-})
diff --git a/services/web/public/src/ide/history/controllers/HistoryV2DiffController.js b/services/web/public/src/ide/history/controllers/HistoryV2DiffController.js
deleted file mode 100644
index b957bb77bc..0000000000
--- a/services/web/public/src/ide/history/controllers/HistoryV2DiffController.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/* eslint-disable
- camelcase,
- max-len,
- no-return-assign,
- no-undef,
-*/
-// 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
- */
-define(['base'], App =>
- App.controller('HistoryV2DiffController', function(
- $scope,
- ide,
- event_tracking,
- waitFor
- ) {
- let openEntity
- $scope.restoreState = {
- inflight: false,
- error: false
- }
-
- $scope.restoreDeletedFile = function() {
- const { pathname } = $scope.history.selection
- if (pathname == null) {
- return
- }
- const version =
- $scope.history.selection.docs[pathname] != null
- ? $scope.history.selection.docs[pathname].deletedAtV
- : undefined
- if (version == null) {
- return
- }
- event_tracking.sendMB('history-v2-restore-deleted')
- $scope.restoreState.inflight = true
- return ide.historyManager
- .restoreFile(version, pathname)
- .then(function(response) {
- const { data } = response
- return openEntity(data)
- })
- .catch(() =>
- ide.showGenericMessageModal(
- 'Sorry, something went wrong with the restore'
- )
- )
- .finally(() => ($scope.restoreState.inflight = false))
- }
-
- return (openEntity = function(data) {
- const { id, type } = data
- return waitFor(() => ide.fileTreeManager.findEntityById(id), 3000)
- .then(function(entity) {
- if (type === 'doc') {
- return ide.editorManager.openDoc(entity)
- } else if (type === 'file') {
- return ide.binaryFilesManager.openFile(entity)
- }
- })
- .catch(err => console.warn(err))
- })
- }))
diff --git a/services/web/public/src/ide/history/controllers/HistoryV2FileTreeController.js b/services/web/public/src/ide/history/controllers/HistoryV2FileTreeController.js
index 424b926647..1c50eb07c1 100644
--- a/services/web/public/src/ide/history/controllers/HistoryV2FileTreeController.js
+++ b/services/web/public/src/ide/history/controllers/HistoryV2FileTreeController.js
@@ -24,7 +24,7 @@ define(['base'], App =>
const _pathnameExistsInFiles = (pathname, files) =>
_.any(files, file => file.pathname === pathname)
- const _getSelectedDefaultPathname = function(files) {
+ const _getSelectedDefaultFile = function(files) {
let selectedPathname = null
if (
_previouslySelectedPathname != null &&
@@ -41,24 +41,25 @@ define(['base'], App =>
selectedPathname = _previouslySelectedPathname = files[0].pathname
}
}
- return selectedPathname
+ return _.find(files, { pathname: selectedPathname })
}
- $scope.handleFileSelection = file =>
- ($scope.history.selection.pathname = _previouslySelectedPathname =
- file.pathname)
+ $scope.handleFileSelection = file => {
+ _previouslySelectedPathname = file.pathname
+ ide.historyManager.selectFile(file)
+ }
- $scope.$watch('history.files', function(files) {
+ $scope.$watch('history.selection.files', function(files) {
if (files != null && files.length > 0) {
$scope.currentFileTree = _.reduce(files, _reducePathsToTree, [])
- return ($scope.history.selection.pathname = _getSelectedDefaultPathname(
- files
- ))
+ ide.historyManager.selectFile(_getSelectedDefaultFile(files))
}
})
return (_reducePathsToTree = function(currentFileTree, fileObject) {
- const filePathParts = fileObject.pathname.split('/')
+ const filePathParts = fileObject.newPathname
+ ? fileObject.newPathname.split('/')
+ : fileObject.pathname.split('/')
let currentFileTreeLocation = currentFileTree
for (let index = 0; index < filePathParts.length; index++) {
var fileTreeEntity
@@ -69,7 +70,14 @@ define(['base'], App =>
name: pathPart,
pathname: fileObject.pathname,
type: 'file',
- operation: fileObject.operation || 'edited'
+ operation: fileObject.operation
+ }
+ if (fileObject.operation === 'renamed') {
+ fileTreeEntity.pathname = fileObject.newPathname
+ fileTreeEntity.oldPathname = fileObject.pathname
+ }
+ if (fileObject.operation === 'removed' && fileObject.deletedAtV) {
+ fileTreeEntity.deletedAtV = fileObject.deletedAtV
}
currentFileTreeLocation.push(fileTreeEntity)
} else {
diff --git a/services/web/public/src/ide/history/controllers/HistoryV2ListController.js b/services/web/public/src/ide/history/controllers/HistoryV2ListController.js
index ff7b88a479..23e7e43d85 100644
--- a/services/web/public/src/ide/history/controllers/HistoryV2ListController.js
+++ b/services/web/public/src/ide/history/controllers/HistoryV2ListController.js
@@ -35,9 +35,11 @@ define(['base', 'ide/history/util/displayNameForUser'], (
return ide.historyManager.fetchNextBatchOfUpdates()
}
- $scope.handleEntrySelect = entry => ide.historyManager.selectUpdate(entry)
+ $scope.handleEntrySelect = entry =>
+ ide.historyManager.selectVersionForPointInTime(entry.toV)
- $scope.handleLabelSelect = label => ide.historyManager.selectLabel(label)
+ $scope.handleLabelSelect = label =>
+ ide.historyManager.selectLabelForPointInTime(label)
return ($scope.handleLabelDelete = labelDetails =>
$modal.open({
diff --git a/services/web/public/src/ide/history/controllers/HistoryV2ToolbarController.js b/services/web/public/src/ide/history/controllers/HistoryV2ToolbarController.js
index 310b8fb3c4..9b2a8284e8 100644
--- a/services/web/public/src/ide/history/controllers/HistoryV2ToolbarController.js
+++ b/services/web/public/src/ide/history/controllers/HistoryV2ToolbarController.js
@@ -2,6 +2,7 @@
max-len,
no-return-assign,
no-undef,
+ camelcase,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
@@ -15,15 +16,95 @@ define(['base'], App =>
'$scope',
'$modal',
'ide',
- ($scope, $modal, ide) =>
- ($scope.showAddLabelDialog = () =>
+ 'event_tracking',
+ 'waitFor',
+ ($scope, $modal, ide, event_tracking, waitFor) => {
+ let openEntity
+
+ $scope.restoreState = {
+ inflight: false,
+ error: false
+ }
+
+ $scope.toolbarUIConfig = {
+ showOnlyLabels: false
+ }
+
+ let _deregistershowOnlyLabelsWatcher = $scope.$watch(
+ 'history.showOnlyLabels',
+ showOnlyLabels => {
+ if (showOnlyLabels != null) {
+ $scope.toolbarUIConfig.showOnlyLabels = showOnlyLabels
+ _deregistershowOnlyLabelsWatcher()
+ }
+ }
+ )
+
+ $scope.$watch('toolbarUIConfig.showOnlyLabels', (newVal, oldVal) => {
+ if (newVal != null && newVal !== oldVal) {
+ if (newVal) {
+ ide.historyManager.showOnlyLabels()
+ } else {
+ ide.historyManager.showAllUpdates()
+ }
+ }
+ })
+
+ $scope.toggleHistoryViewMode = () => {
+ ide.historyManager.toggleHistoryViewMode()
+ }
+
+ $scope.restoreDeletedFile = function() {
+ const { pathname, deletedAtV } = $scope.history.selection.file
+ if (pathname == null || deletedAtV == null) {
+ return
+ }
+
+ event_tracking.sendMB('history-v2-restore-deleted')
+ $scope.restoreState.inflight = true
+ return ide.historyManager
+ .restoreFile(deletedAtV, pathname)
+ .then(function(response) {
+ const { data } = response
+ return openEntity(data)
+ })
+ .catch(() =>
+ ide.showGenericMessageModal(
+ 'Sorry, something went wrong with the restore'
+ )
+ )
+ .finally(() => ($scope.restoreState.inflight = false))
+ }
+
+ $scope.showAddLabelDialog = () => {
$modal.open({
templateUrl: 'historyV2AddLabelModalTemplate',
controller: 'HistoryV2AddLabelModalController',
resolve: {
update() {
- return $scope.history.selection.updates[0]
+ return $scope.history.selection.update
}
}
- }))
+ })
+ }
+
+ openEntity = function(data) {
+ const { id, type } = data
+ return waitFor(() => ide.fileTreeManager.findEntityById(id), 3000)
+ .then(function(entity) {
+ if (type === 'doc') {
+ ide.editorManager.openDoc(entity)
+ this.ide.$timeout(() => {
+ this.$scope.$broadcast('history:toggle')
+ }, 0)
+ } else if (type === 'file') {
+ ide.binaryFilesManager.openFile(entity)
+ this.ide.$timeout(() => {
+ this.$scope.$broadcast('history:toggle')
+ }, 0)
+ }
+ })
+ .catch(err => console.warn(err))
+ }
+ }
]))
diff --git a/services/web/public/stylesheets/app/editor/file-tree.less b/services/web/public/stylesheets/app/editor/file-tree.less
index 95bc0d38bf..051ce091bc 100644
--- a/services/web/public/stylesheets/app/editor/file-tree.less
+++ b/services/web/public/stylesheets/app/editor/file-tree.less
@@ -49,6 +49,14 @@
text-decoration: line-through;
}
}
+ .loading {
+ padding-left: 6px;
+ color: #FFF;
+
+ i.fa {
+ color: #FFF;
+ }
+ }
}
ul.file-tree-list {
diff --git a/services/web/public/stylesheets/app/editor/history-v2.less b/services/web/public/stylesheets/app/editor/history-v2.less
index dafb190ff4..99f3a0fed7 100644
--- a/services/web/public/stylesheets/app/editor/history-v2.less
+++ b/services/web/public/stylesheets/app/editor/history-v2.less
@@ -13,6 +13,19 @@
padding-left: (@line-height-computed / 2);
}
+.history-compare-mode-toolbar {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ justify-content: center;
+ line-height: 1;
+ font-size: @font-size-small;
+ background-color: @history-toolbar-bg-color;
+ height: @editor-toolbar-height;
+ color: @history-toolbar-color;
+ padding: 0 10px;
+}
+
.history-toolbar when (@is-overleaf = false) or (@is-overleaf-light) {
border-bottom: @toolbar-border-bottom;
}
@@ -89,12 +102,22 @@
.history-entry-label-selected & {
color: @history-entry-selected-label-color;
}
+ &.history-label-pseudo-current-state {
+ .history-entry-selected &,
+ .history-entry-label-selected & {
+ color: @history-entry-selected-pseudo-label-color;
+ }
+ }
}
.history-label-comment,
.history-label-delete-btn {
padding: 0 @padding-xs-horizontal 1px @padding-xs-horizontal;
border: 0;
background-color: @history-entry-label-bg-color;
+ .history-label-pseudo-current-state & {
+ background-color: @history-entry-pseudo-label-bg-color;
+
+ }
.history-entry-selected &,
.history-entry-label-selected & {
background-color: @history-entry-selected-label-bg-color;
@@ -187,11 +210,18 @@
background-color: #FFF;
margin-bottom: 2px;
padding: 5px 10px;
+ .change-list-compare & {
+ font-size: @font-size-small;
+ }
}
-.history-labels-list {
+.history-labels-list,
+.history-labels-list-compare {
.history-entries;
overflow-y: auto;
+}
+.history-labels-list-compare {
+ background-color: transparent;
}
.history-entry-label {
.history-entry-details;
@@ -215,6 +245,14 @@
}
}
+.tooltip-history-file-tree {
+ font-size: 12px;
+ .tooltip-inner {
+ max-width: 400px;
+ text-align: left;
+ }
+}
+
.history-file-tree-inner when (@is-overleaf = false) {
font-size: 0.8rem;
}
@@ -234,7 +272,7 @@
text-decoration: none;
}
&:focus {
- color: @file-tree-item-color;
+ color: @file-tree-item-focus-color;
outline: none;
text-decoration: none;
}
@@ -251,22 +289,57 @@
&:hover {
background-color: @file-tree-item-hover-bg;
}
+ &:focus {
+ color: @file-tree-item-focus-selected-color;
+ }
}
- .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-container {
+ display: flex;
+ align-items: center;
+ }
+ .history-file-entity-name {
+ flex: 0 1 auto;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
- }
- .history-file-entity-name {
- display: block;
- max-width: 100%;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
+ .history-file-entity-operation-badge {
+ flex: 0 0 auto;
+ text-transform: lowercase;
+ margin-left: .5em;
+ font-size: .7em;
+ background: @history-file-badge-bg;
+ color: @history-file-badge-color;
+ border-radius: 8px;
+ line-height: 1;
+ padding: 2px 4px 3px;
+ margin-top: 2px;
+ }
+
+ .history-file-entity-icon,
+ .history-file-operation-icon {
+ flex: 0 0 auto;
+ color: @file-tree-item-icon-color;
+ font-size: 14px;
+ margin-right: .5em;
+ .history-file-entity-link-selected & {
+ color: #FFF;
+ }
+ }
+
+ .history-file-operation-icon {
+ margin-left: .5em;
+ margin-right: 0;
+ }
+ .history-file-entity-name-edited,
+ .history-file-entity-name-added,
+ .history-file-entity-name-removed,
+ .history-file-entity-name-renamed {
+ }
+ .history-file-entity-name-removed {
+ text-decoration: line-through;
+ }
+
.history-file-entity-link-selected when (@is-overleaf = false) {
color: @brand-primary;
diff --git a/services/web/public/stylesheets/app/editor/history.less b/services/web/public/stylesheets/app/editor/history.less
index 3e558b6c5f..d4fc9f1e9c 100644
--- a/services/web/public/stylesheets/app/editor/history.less
+++ b/services/web/public/stylesheets/app/editor/history.less
@@ -59,7 +59,6 @@
}
.diff-editor {
.full-size;
- top: 32px;
}
.diff-deleted {
diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less
index 1acf8daceb..a4ad6055b4 100644
--- a/services/web/public/stylesheets/core/_common-variables.less
+++ b/services/web/public/stylesheets/core/_common-variables.less
@@ -948,17 +948,19 @@
@global-alerts-padding : (@line-height-computed / 4);
// Editor file-tree
-@file-tree-bg : transparent;
-@file-tree-line-height : 2.6;
-@file-tree-item-color : @gray-darker;
-@file-tree-item-toggle-color : @gray;
-@file-tree-item-icon-color : @gray-light;
-@file-tree-item-input-color : inherit;
-@file-tree-item-folder-color : lighten(desaturate(@link-color, 10%), 5%);
-@file-tree-item-hover-bg : @gray-lightest;
-@file-tree-item-selected-bg : transparent;
-@file-tree-multiselect-bg : lighten(@brand-info, 40%);
-@file-tree-multiselect-hover-bg : lighten(@brand-info, 30%);
+@file-tree-bg : transparent;
+@file-tree-line-height : 2.6;
+@file-tree-item-color : @gray-darker;
+@file-tree-item-focus-color : @file-tree-item-color;
+@file-tree-item-focus-selected-color : @file-tree-item-color;
+@file-tree-item-toggle-color : @gray;
+@file-tree-item-icon-color : @gray-light;
+@file-tree-item-input-color : inherit;
+@file-tree-item-folder-color : lighten(desaturate(@link-color, 10%), 5%);
+@file-tree-item-hover-bg : @gray-lightest;
+@file-tree-item-selected-bg : transparent;
+@file-tree-multiselect-bg : lighten(@brand-info, 40%);
+@file-tree-multiselect-hover-bg : lighten(@brand-info, 30%);
// Editor resizers
@editor-resizer-bg-color : #F4F4F4;
@@ -1018,18 +1020,22 @@
@sys-msg-border : 1px solid @common-border-color;
// v2 History
-@history-base-font-size : @font-size-small;
-@history-base-bg : @gray-lightest;
-@history-entry-label-bg-color : @red;
-@history-entry-label-color : #FFF;
-@history-entry-selected-label-bg-color : #FFF;
-@history-entry-selected-label-color : @red;
-@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;
+@history-base-font-size : @font-size-small;
+@history-base-bg : @gray-lightest;
+@history-entry-label-bg-color : @red;
+@history-entry-pseudo-label-bg-color : @green;
+@history-entry-label-color : #FFF;
+@history-entry-selected-label-bg-color : #FFF;
+@history-entry-selected-label-color : @red;
+@history-entry-selected-pseudo-label-color: @green;
+@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;
+@history-file-badge-bg : rgba(0, 0, 0, .25);
+@history-file-badge-color : #FFF;
// Input suggestions
@input-suggestion-v-offset : 6px;
diff --git a/services/web/public/stylesheets/core/ol-light-variables.less b/services/web/public/stylesheets/core/ol-light-variables.less
index 44814d434b..9fb0507aed 100644
--- a/services/web/public/stylesheets/core/ol-light-variables.less
+++ b/services/web/public/stylesheets/core/ol-light-variables.less
@@ -39,19 +39,21 @@
@input-border : @ol-blue-gray-1;
// Editor file-tree
-@file-tree-bg : #FFF;
-@file-tree-line-height : 2.05;
-@file-tree-item-color : @ol-blue-gray-3;
-@file-tree-item-selected-color : #FFF;
-@file-tree-item-input-color : @ol-blue-gray-2;
-@file-tree-item-toggle-color : @ol-blue-gray-2;
-@file-tree-item-icon-color : @ol-blue-gray-2;
-@file-tree-item-folder-color : @ol-blue-gray-2;
-@file-tree-item-hover-bg : @ol-blue-gray-1;
-@file-tree-item-selected-bg : @ol-green;
-@file-tree-multiselect-bg : @ol-blue;
-@file-tree-multiselect-hover-bg : @ol-dark-blue;
-@file-tree-droppable-bg-color : tint(@ol-green, 5%);
+@file-tree-bg : #FFF;
+@file-tree-line-height : 2.05;
+@file-tree-item-color : @ol-blue-gray-3;
+@file-tree-item-focus-color : @file-tree-item-color;
+@file-tree-item-focus-selected-color : #FFF;
+@file-tree-item-selected-color : #FFF;
+@file-tree-item-input-color : @ol-blue-gray-2;
+@file-tree-item-toggle-color : @ol-blue-gray-2;
+@file-tree-item-icon-color : @ol-blue-gray-2;
+@file-tree-item-folder-color : @ol-blue-gray-2;
+@file-tree-item-hover-bg : @ol-blue-gray-1;
+@file-tree-item-selected-bg : @ol-green;
+@file-tree-multiselect-bg : @ol-blue;
+@file-tree-multiselect-hover-bg : @ol-dark-blue;
+@file-tree-droppable-bg-color : tint(@ol-green, 5%);
@content-alt-bg-color : @ol-blue-gray-0;
@@ -105,6 +107,8 @@
@history-toolbar-color : @ol-blue-gray-3;
@history-entry-day-bg : @ol-blue-gray-2;
@history-base-bg : @ol-blue-gray-0;
+@history-file-badge-bg : rgba(0, 0, 0, .25);
+@history-file-badge-color : #FFF;
// Formatting buttons
@formatting-btn-color : @toolbar-icon-btn-color;
diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less
index 742a1d7592..4b2bb933b0 100644
--- a/services/web/public/stylesheets/core/ol-variables.less
+++ b/services/web/public/stylesheets/core/ol-variables.less
@@ -249,19 +249,21 @@
@global-alerts-padding : 7px;
// Editor file-tree
-@file-tree-bg : @ol-blue-gray-4;
-@file-tree-line-height : 2.05;
-@file-tree-item-color : #FFF;
-@file-tree-item-selected-color : @file-tree-item-color;
-@file-tree-item-input-color : @ol-blue-gray-5;
-@file-tree-item-toggle-color : @ol-blue-gray-2;
-@file-tree-item-icon-color : @ol-blue-gray-2;
-@file-tree-item-folder-color : @ol-blue-gray-2;
-@file-tree-item-hover-bg : @ol-blue-gray-5;
-@file-tree-item-selected-bg : @ol-green;
-@file-tree-multiselect-bg : @ol-blue;
-@file-tree-multiselect-hover-bg : @ol-dark-blue;
-@file-tree-droppable-bg-color : tint(@ol-green, 5%);
+@file-tree-bg : @ol-blue-gray-4;
+@file-tree-line-height : 2.05;
+@file-tree-item-color : #FFF;
+@file-tree-item-focus-color : @file-tree-item-color;
+@file-tree-item-focus-selected-color : @file-tree-item-color;
+@file-tree-item-selected-color : @file-tree-item-color;
+@file-tree-item-input-color : @ol-blue-gray-5;
+@file-tree-item-toggle-color : @ol-blue-gray-2;
+@file-tree-item-icon-color : @ol-blue-gray-2;
+@file-tree-item-folder-color : @ol-blue-gray-2;
+@file-tree-item-hover-bg : @ol-blue-gray-5;
+@file-tree-item-selected-bg : @ol-green;
+@file-tree-multiselect-bg : @ol-blue;
+@file-tree-multiselect-hover-bg : @ol-dark-blue;
+@file-tree-droppable-bg-color : tint(@ol-green, 5%);
// Editor resizers
@editor-resizer-bg-color : @ol-blue-gray-5;
@@ -333,19 +335,22 @@
// v2 History
-@history-base-font-size : @font-size-small;
-@history-base-bg : @ol-blue-gray-1;
-@history-entry-label-bg-color : @ol-blue;
-@history-entry-label-color : #FFF;
-@history-entry-selected-label-bg-color: #FFF;
-@history-entry-selected-label-color : @ol-blue;
-@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;
-
+@history-base-font-size : @font-size-small;
+@history-base-bg : @ol-blue-gray-1;
+@history-entry-label-bg-color : @ol-blue;
+@history-entry-pseudo-label-bg-color : @ol-green;
+@history-entry-label-color : #FFF;
+@history-entry-selected-label-bg-color : #FFF;
+@history-entry-selected-label-color : @ol-blue;
+@history-entry-selected-pseudo-label-color: @ol-green;
+@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;
+@history-file-badge-bg : rgba(255, 255, 255, .25);
+@history-file-badge-color: : @file-tree-item-color;
// Screens
// added -size to not conflict with common_variables
@screen-size-sm-max : 767px;
diff --git a/services/web/test/unit_frontend/src/ide/history/HistoryV2ManagerTests.js b/services/web/test/unit_frontend/src/ide/history/HistoryV2ManagerTests.js
index d4965ec8b4..b57645a9b1 100644
--- a/services/web/test/unit_frontend/src/ide/history/HistoryV2ManagerTests.js
+++ b/services/web/test/unit_frontend/src/ide/history/HistoryV2ManagerTests.js
@@ -12,244 +12,192 @@
*/
define(['ide/history/HistoryV2Manager'], HistoryV2Manager =>
describe('HistoryV2Manager', function() {
- beforeEach(function() {
- this.scope = {
- $watch: sinon.stub(),
- $on: sinon.stub(),
- project: {
- features: {
- versioning: true
- }
- }
- }
- this.ide = {}
- return (this.historyManager = new HistoryV2Manager(this.ide, this.scope))
- })
-
- it('should setup the history scope on initialization', function() {
- return expect(this.scope.history).to.deep.equal({
+ beforeEach(function(done) {
+ this.defaultHistoryScope = {
isV2: true,
updates: [],
- viewMode: null,
+ viewMode: 'point_in_time',
nextBeforeTimestamp: null,
atEnd: false,
- userHasFullFeature: true,
+ userHasFullFeature: undefined,
freeHistoryLimitHit: false,
selection: {
- label: null,
- updates: [],
docs: {},
pathname: null,
range: {
fromV: null,
toV: null
- }
+ },
+ hoveredRange: {
+ fromV: null,
+ toV: null
+ },
+ diff: null,
+ files: [],
+ update: null,
+ label: null,
+ file: null
},
error: null,
showOnlyLabels: false,
labels: null,
- diff: null,
- files: [],
- selectedFile: null
+ loadingFileTree: true
+ }
+
+ this.sampleUpdates = [
+ {
+ fromV: 4,
+ toV: 5,
+ meta: {
+ users: [
+ {
+ first_name: 'john.doe',
+ last_name: '',
+ email: 'john.doe@domain.tld',
+ id: '5b57299087712202fb599ab4',
+ hue: 200
+ }
+ ],
+ start_ts: 1544021278346,
+ end_ts: 1544021278346
+ },
+ labels: [
+ {
+ id: '5c07e822042e67003b448f18',
+ comment: 'My first label',
+ version: 5,
+ user_id: '5b57299087712202fb599ab4',
+ created_at: '2018-12-05T15:00:50.688Z'
+ }
+ ],
+ pathnames: [],
+ project_ops: [
+ {
+ add: {
+ pathname: 'chapters/chapter1.tex'
+ },
+ atV: 4
+ }
+ ]
+ },
+ {
+ fromV: 3,
+ toV: 4,
+ meta: {
+ users: [
+ {
+ first_name: 'john.doe',
+ last_name: '',
+ email: 'john.doe@domain.tld',
+ id: '5b57299087712202fb599ab4',
+ hue: 200
+ }
+ ],
+ start_ts: 1544021262622,
+ end_ts: 1544021262622
+ },
+ labels: [],
+ pathnames: ['main.tex'],
+ project_ops: []
+ },
+ {
+ fromV: 0,
+ toV: 3,
+ meta: {
+ users: [
+ {
+ first_name: 'john.doe',
+ last_name: '',
+ email: 'john.doe@domain.tld',
+ id: '5b57299087712202fb599ab4',
+ hue: 200
+ }
+ ],
+ start_ts: 1544021213540,
+ end_ts: 1544021213618
+ },
+ labels: [],
+ pathnames: [],
+ project_ops: [
+ {
+ add: {
+ pathname: 'universe.jpg'
+ },
+ atV: 2
+ },
+ {
+ add: {
+ pathname: 'references.bib'
+ },
+ atV: 1
+ },
+ {
+ add: {
+ pathname: 'main.tex'
+ },
+ atV: 0
+ }
+ ]
+ }
+ ]
+
+ inject(($q, $http, $rootScope) => {
+ this.$scope = $rootScope.$new()
+ this.$scope.project = {
+ features: {
+ versioning: true
+ }
+ }
+ this.ide = {
+ $q: $q,
+ $http: $http
+ }
+ this.localStorage = sinon.stub().returns(null)
+ this.historyManager = new HistoryV2Manager(
+ this.ide,
+ this.$scope,
+ this.localStorage
+ )
+ done()
})
})
+ it('should setup the history scope on initialization', function() {
+ expect(this.$scope.history).to.deep.equal(this.defaultHistoryScope)
+ })
+
+ it('should keep history updates after performing a soft reset', function() {
+ let historyScopeWithUpdates = Object.assign(
+ {},
+ this.defaultHistoryScope,
+ {
+ updates: this.sampleUpdates
+ }
+ )
+ this.$scope.history.updates = this.sampleUpdates
+ this.historyManager.softReset()
+ expect(this.$scope.history).to.deep.equal(historyScopeWithUpdates)
+ })
+
+ it('should discard history updates after performing a hard reset', function() {
+ this.$scope.history.updates = this.sampleUpdates
+ this.historyManager.hardReset()
+ expect(this.$scope.history).to.deep.equal(this.defaultHistoryScope)
+ })
+
+ it('should setup history with full access to the feature if the project has versioning', function() {
+ this.$scope.$digest()
+ expect(this.$scope.history.userHasFullFeature).to.equal(true)
+ })
+
it('should setup history without full access to the feature if the project does not have versioning', function() {
- this.scope.project.features.versioning = false
- this.historyManager = new HistoryV2Manager(this.ide, this.scope)
- return expect(this.scope.history.userHasFullFeature).to.equal(false)
- })
-
- return describe('_perDocSummaryOfUpdates', function() {
- it('should return the range of updates for the docs', function() {
- const result = this.historyManager._perDocSummaryOfUpdates([
- {
- pathnames: ['main.tex'],
- fromV: 7,
- toV: 9
- },
- {
- pathnames: ['main.tex', 'foo.tex'],
- fromV: 4,
- toV: 6
- },
- {
- pathnames: ['main.tex'],
- fromV: 3,
- toV: 3
- },
- {
- pathnames: ['foo.tex'],
- fromV: 0,
- toV: 2
- }
- ])
-
- return expect(result).to.deep.equal({
- 'main.tex': { fromV: 3, toV: 9 },
- 'foo.tex': { fromV: 0, toV: 6 }
- })
- })
-
- it('should track renames', function() {
- const result = this.historyManager._perDocSummaryOfUpdates([
- {
- pathnames: ['main2.tex'],
- fromV: 5,
- toV: 9
- },
- {
- project_ops: [
- {
- rename: {
- pathname: 'main1.tex',
- newPathname: 'main2.tex'
- }
- }
- ],
- fromV: 4,
- toV: 4
- },
- {
- pathnames: ['main1.tex'],
- fromV: 3,
- toV: 3
- },
- {
- project_ops: [
- {
- rename: {
- pathname: 'main0.tex',
- newPathname: 'main1.tex'
- }
- }
- ],
- fromV: 2,
- toV: 2
- },
- {
- pathnames: ['main0.tex'],
- fromV: 0,
- toV: 1
- }
- ])
-
- return expect(result).to.deep.equal({
- 'main0.tex': { fromV: 0, toV: 9 }
- })
- })
-
- it('should track single renames', function() {
- const result = this.historyManager._perDocSummaryOfUpdates([
- {
- project_ops: [
- {
- rename: {
- pathname: 'main1.tex',
- newPathname: 'main2.tex'
- }
- }
- ],
- fromV: 4,
- toV: 5
- }
- ])
-
- return expect(result).to.deep.equal({
- 'main1.tex': { fromV: 4, toV: 5 }
- })
- })
-
- it('should track additions', function() {
- const result = this.historyManager._perDocSummaryOfUpdates([
- {
- project_ops: [
- {
- add: {
- pathname: 'main.tex'
- }
- }
- ],
- fromV: 0,
- toV: 1
- },
- {
- pathnames: ['main.tex'],
- fromV: 1,
- toV: 4
- }
- ])
-
- return expect(result).to.deep.equal({
- 'main.tex': { fromV: 0, toV: 4 }
- })
- })
-
- it('should track single additions', function() {
- const result = this.historyManager._perDocSummaryOfUpdates([
- {
- project_ops: [
- {
- add: {
- pathname: 'main.tex'
- }
- }
- ],
- fromV: 0,
- toV: 1
- }
- ])
-
- return expect(result).to.deep.equal({
- 'main.tex': { fromV: 0, toV: 1 }
- })
- })
-
- it('should track deletions', function() {
- const result = this.historyManager._perDocSummaryOfUpdates([
- {
- pathnames: ['main.tex'],
- fromV: 0,
- toV: 1
- },
- {
- project_ops: [
- {
- remove: {
- pathname: 'main.tex'
- },
- atV: 2
- }
- ],
- fromV: 1,
- toV: 2
- }
- ])
-
- return expect(result).to.deep.equal({
- 'main.tex': { fromV: 0, toV: 2, deletedAtV: 2 }
- })
- })
-
- return it('should track single deletions', function() {
- const result = this.historyManager._perDocSummaryOfUpdates([
- {
- project_ops: [
- {
- remove: {
- pathname: 'main.tex'
- },
- atV: 1
- }
- ],
- fromV: 0,
- toV: 1
- }
- ])
-
- return expect(result).to.deep.equal({
- 'main.tex': { fromV: 0, toV: 1, deletedAtV: 1 }
- })
- })
+ this.$scope.project.features.versioning = false
+ this.historyManager = new HistoryV2Manager(
+ this.ide,
+ this.$scope,
+ this.localStorage
+ )
+ this.$scope.$digest()
+ expect(this.$scope.history.userHasFullFeature).to.equal(false)
})
}))
diff --git a/services/web/test/unit_frontend/src/mocks/ide/file-tree/util/fileOperationI18nNames.js b/services/web/test/unit_frontend/src/mocks/ide/file-tree/util/fileOperationI18nNames.js
new file mode 100644
index 0000000000..b118705f96
--- /dev/null
+++ b/services/web/test/unit_frontend/src/mocks/ide/file-tree/util/fileOperationI18nNames.js
@@ -0,0 +1,8 @@
+define([], function() {
+ return {
+ edited: 'edited',
+ renamed: 'renamed',
+ created: 'created',
+ deleted: 'deleted'
+ }
+})
diff --git a/services/web/test/unit_frontend/src/test-main.js b/services/web/test/unit_frontend/src/test-main.js
index 4221091c24..890fa39fa2 100644
--- a/services/web/test/unit_frontend/src/test-main.js
+++ b/services/web/test/unit_frontend/src/test-main.js
@@ -1,16 +1,19 @@
/* eslint-disable
no-undef,
+ max-len,
*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-// Set up requirejs to load the tests
-// Uses heuristic that test filenames end with Tests.js (existing frontend code)
-// or _tests.js (newer frontend code)
-const tests = []
+// Set up requirejs to load the tests and mocked dependencies
+// For tests, uses heuristic that test filenames end with Tests.js (existing
+// frontend code) or _tests.js (newer frontend code)
+// For mocks, uses heuristic that loads any .js file within the mocks subfolder
+const testDeps = []
for (let file in window.__karma__.files) {
if (window.__karma__.files.hasOwnProperty(file)) {
- if (/test\/unit_frontend\/js.+(_t|T)ests\.js$/.test(file)) {
- tests.push(file)
+ if (
+ /test\/unit_frontend\/js.+(_t|T)ests\.js$/.test(file) ||
+ /test\/unit_frontend\/js\/mocks\/.+\.js$/.test(file)
+ ) {
+ testDeps.push(file)
}
}
}
@@ -20,6 +23,12 @@ requirejs.config({
paths: {
moment: 'libs/moment-2.9.0'
},
- deps: tests,
+ map: {
+ '*': {
+ 'ide/file-tree/util/fileOperationI18nNames':
+ '../../test/unit_frontend/js/mocks/ide/file-tree/util/fileOperationI18nNames'
+ }
+ },
+ deps: testDeps,
callback: window.__karma__.start
})