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