diff --git a/services/web/app/coffee/Features/History/HistoryController.coffee b/services/web/app/coffee/Features/History/HistoryController.coffee index 8b014751f9..189e07b0f5 100644 --- a/services/web/app/coffee/Features/History/HistoryController.coffee +++ b/services/web/app/coffee/Features/History/HistoryController.coffee @@ -7,7 +7,6 @@ HistoryManager = require "./HistoryManager" ProjectDetailsHandler = require "../Project/ProjectDetailsHandler" ProjectEntityUpdateHandler = require "../Project/ProjectEntityUpdateHandler" RestoreManager = require "./RestoreManager" -ProjectUpdateHandler = require "../Project/ProjectUpdateHandler" module.exports = HistoryController = selectHistoryApi: (req, res, next = (error) ->) -> @@ -144,11 +143,3 @@ module.exports = HistoryController = error = new Error("history api responded with non-success code: #{response.statusCode}") logger.error err: error, "project-history api responded with non-success code: #{response.statusCode}" callback(error) - - setLastUpdated: (req, res, next) -> - {project_id} = req.params - {user_id, timestamp} = req.body - logger.log {project_id, user_id, timestamp}, 'updating last updated date' - ProjectUpdateHandler.markAsUpdated project_id, user_id, timestamp, (error) -> - return next(error) if error? - res.sendStatus 200 diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index ed5e219e3f..59c0647c19 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -184,7 +184,7 @@ module.exports = ProjectController = notifications: (cb)-> NotificationsHandler.getUserNotifications user_id, cb projects: (cb)-> - ProjectGetter.findAllUsersProjects user_id, 'name lastUpdated lastUpdatedBy publicAccesLevel archived owner_ref tokens', cb + ProjectGetter.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref tokens', cb v1Projects: (cb) -> Modules.hooks.fire "findAllV1Projects", user_id, (error, projects = []) -> if error? and error instanceof V1ConnectionError @@ -392,7 +392,6 @@ module.exports = ProjectController = id: project._id name: project.name lastUpdated: project.lastUpdated - lastUpdatedBy: project.lastUpdatedBy publicAccessLevel: project.publicAccesLevel accessLevel: accessLevel source: source @@ -431,8 +430,6 @@ module.exports = ProjectController = for project in projects if project.owner_ref? users[project.owner_ref.toString()] = true - if project.lastUpdatedBy? - users[project.lastUpdatedBy] = true jobs = [] for user_id, _ of users @@ -447,8 +444,6 @@ module.exports = ProjectController = for project in projects if project.owner_ref? project.owner = users[project.owner_ref.toString()] - if project.lastUpdatedBy? - project.lastUpdatedBy = users[project.lastUpdatedBy.toString()] callback null, projects _buildWarningsList: (v1ProjectData = {}) -> diff --git a/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee index 4800de05d4..b7be80cb47 100644 --- a/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee @@ -114,6 +114,8 @@ module.exports = ProjectEntityUpdateHandler = self = logger.log {project_id, doc_id, modified}, "finished updating doc lines" # path will only be present if the doc is not deleted if modified && !isDeletedDoc + # Don't need to block for marking as updated + ProjectUpdateHandler.markAsUpdated project_id TpdsUpdateSender.addDoc {project_id:project_id, path:path.fileSystem, doc_id:doc_id, project_name:project.name, rev:rev}, callback else callback() diff --git a/services/web/app/coffee/Features/Project/ProjectUpdateHandler.coffee b/services/web/app/coffee/Features/Project/ProjectUpdateHandler.coffee index d89d7c7e89..ba3b3b6295 100644 --- a/services/web/app/coffee/Features/Project/ProjectUpdateHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectUpdateHandler.coffee @@ -2,25 +2,30 @@ Project = require('../../models/Project').Project logger = require('logger-sharelatex') module.exports = - markAsUpdated : (project_id, user_id, timestamp, callback)-> + markAsUpdated : (project_id, callback)-> conditions = {_id:project_id} - update = { - lastUpdated: new Date(timestamp), - lastUpdatedBy: user_id - } - Project.update conditions, update, {}, callback + update = {lastUpdated:Date.now()} + Project.update conditions, update, {}, (err)-> + if callback? + callback() markAsOpened : (project_id, callback)-> conditions = {_id:project_id} update = {lastOpened:Date.now()} - Project.update conditions, update, {}, callback + Project.update conditions, update, {}, (err)-> + if callback? + callback() markAsInactive: (project_id, callback)-> conditions = {_id:project_id} update = {active:false} - Project.update conditions, update, {}, callback + Project.update conditions, update, {}, (err)-> + if callback? + callback() markAsActive: (project_id, callback)-> conditions = {_id:project_id} update = {active:true} - Project.update conditions, update, {}, callback + Project.update conditions, update, {}, (err)-> + if callback? + callback() \ No newline at end of file diff --git a/services/web/app/coffee/models/Project.coffee b/services/web/app/coffee/models/Project.coffee index f4b478d64e..00c2f1be52 100644 --- a/services/web/app/coffee/models/Project.coffee +++ b/services/web/app/coffee/models/Project.coffee @@ -19,7 +19,6 @@ DeletedFileSchema = new Schema ProjectSchema = new Schema name : {type:String, default:'new project'} lastUpdated : {type:Date, default: () -> new Date()} - lastUpdatedBy : {type:ObjectId, ref: 'User'} lastOpened : {type:Date} active : { type: Boolean, default: true } owner_ref : {type:ObjectId, ref:'User'} diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 2413632581..b196c56870 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -238,7 +238,6 @@ module.exports = class Router webRouter.post '/project/:project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreDocFromDeletedDoc webRouter.post "/project/:project_id/restore_file", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreFileFromV2 privateApiRouter.post "/project/:Project_id/history/resync", AuthenticationController.httpAuth, HistoryController.resyncProjectHistory - privateApiRouter.post "/project/:project_id/last_updated", AuthenticationController.httpAuth, HistoryController.setLastUpdated webRouter.get "/project/:Project_id/labels", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.ensureProjectHistoryEnabled, HistoryController.getLabels webRouter.post "/project/:Project_id/labels", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.selectHistoryApi, HistoryController.ensureProjectHistoryEnabled, HistoryController.createLabel diff --git a/services/web/app/views/project/list/item.pug b/services/web/app/views/project/list/item.pug index 5266571da7..deb9863d04 100644 --- a/services/web/app/views/project/list/item.pug +++ b/services/web/app/views/project/list/item.pug @@ -1,123 +1,104 @@ -td.selectProject - input.select-item( - ng-if="!project.isV1Project", - select-individual, - type="checkbox", - ng-disabled="shouldDisableCheckbox(project)", - ng-model="project.selected" - stop-propagation="click" - aria-label=translate('select_project') + " '{{ project.name }}'" - ) - span.v1-badge( - ng-if="project.isV1Project", - aria-label=translate("v1_badge") - tooltip-template="'v1ProjectTooltipTemplate'" - tooltip-append-to-body="true" - ) -td.projectName - span(ng-if="project.isV1Project") - if settings.overleaf && settings.overleaf.host - button.btn.btn-link.projectName( - ng-click="openV1ImportModal(project)" - stop-propagation="click" - ng-show="project.accessLevel == 'owner'" - ) {{project.name}} - a.projectName( - href=settings.overleaf.host + "/{{project.id}}" - target="_blank" - ng-hide="project.accessLevel == 'owner'" - ) {{project.name}} - span(ng-if="!project.isV1Project") - a.projectName( - ng-href="{{projectLink(project)}}" - stop-propagation="click" - ) {{project.name}} - span( - ng-controller="TagListController" - ) - .tag-label( - ng-repeat='tag in project.tags' - stop-propagation="click" - ) - a.label.label-default.tag-label-name( - href, - ng-click="selectTag(tag)" - ) {{tag.name}} - a.label.label-default.tag-label-remove( - href - ng-click="removeProjectFromTag(project, tag)" - ) × +- var titleClasses = settings.overleaf ? "col-xs-6 col-sm-4 col-md-6" : "col-xs-6" +- var lastUpdatedClasses = settings.overleaf ? " col-xs-4 col-sm-3 col-md-2" : "col-xs-4" -td - span.owner {{userDisplayName(project.owner)}} - span(ng-if="isLinkSharingProject(project)") - | - i.fa.fa-link.small( - tooltip=translate("link_sharing") - tooltip-placement="right" - tooltip-append-to-body="true" - ) +div(class=titleClasses) + input.select-item( + select-individual, + type="checkbox", + ng-disabled="shouldDisableCheckbox(project)", + ng-model="project.selected" + stop-propagation="click" + aria-label=translate('select_project') + " '{{ project.name }}'" + ) + span + a.projectName( + ng-href="{{projectLink(project)}}" + stop-propagation="click" + ) {{project.name}} + span( + ng-controller="TagListController" + ) + .tag-label( + ng-repeat='tag in project.tags' + stop-propagation="click" + ) + a.label.label-default.tag-label-name( + href, + ng-click="selectTag(tag)" + ) {{tag.name}} + a.label.label-default.tag-label-remove( + href + ng-click="removeProjectFromTag(project, tag)" + ) × -td - span.last-modified(tooltip="{{project.lastUpdated | formatDate}}") - | {{project.lastUpdated | fromNowDate}} - span(ng-if='project.lastUpdatedBy') - | - | #{translate('by')} - | - | {{userDisplayName(project.lastUpdatedBy)}} +.col-xs-2 + span.owner {{ownerName()}} + span(ng-if="isLinkSharingProject(project)") + | + i.fa.fa-link.small( + tooltip=translate("link_sharing") + tooltip-placement="right" + tooltip-append-to-body="true" + ) -td.text-right - div( - ng-if="!project.isTableActionInflight && !project.isV1Project" - ) - button.btn.btn-link.action-btn( - tooltip=translate('copy'), - tooltip-placement="top", - tooltip-append-to-body="true", - ng-click="clone($event)" - ) - i.icon.fa.fa-files-o - button.btn.btn-link.action-btn( - tooltip=translate('download'), - tooltip-placement="top", - tooltip-append-to-body="true", - ng-click="download($event)" - ) - i.icon.fa.fa-cloud-download - button.btn.btn-link.action-btn( - ng-if="!project.archived && isOwner()" - tooltip=translate('archive'), - tooltip-placement="top", - tooltip-append-to-body="true", - ng-click="archiveOrLeave($event)" - ) - i.icon.fa.fa-inbox - button.btn.btn-link.action-btn( - ng-if="!project.archived && !isOwner()" - tooltip=translate('leave'), - tooltip-placement="top", - tooltip-append-to-body="true", - ng-click="archiveOrLeave($event)" - ) - i.icon.fa.fa-sign-out - button.btn.btn-link.action-btn( - ng-if="project.archived" - tooltip=translate('unarchive'), - tooltip-placement="top", - tooltip-append-to-body="true", - ng-click="restore($event)" - ) - i.icon.fa.fa-reply - button.btn.btn-link.action-btn( - ng-if="project.archived && isOwner()" - tooltip=translate('delete_forever'), - tooltip-placement="top", - tooltip-append-to-body="true", - ng-click="deleteProject($event)" - ) - i.icon.fa.fa-trash - div( - ng-if="project.isTableActionInflight" - ) - i.fa.fa-spinner.fa-spin +div(class=lastUpdatedClasses) + if settings.overleaf + span.last-modified(tooltip="{{project.lastUpdated | formatDate}}") {{project.lastUpdated | fromNowDate}} + else + span.last-modified {{project.lastUpdated | formatDate}} + +if settings.overleaf + .hidden-xs.col-sm-3.col-md-2.action-btn-row + div( + ng-if="!project.isTableActionInflight" + ) + button.btn.btn-link.action-btn( + tooltip=translate('copy'), + tooltip-placement="top", + tooltip-append-to-body="true", + ng-click="clone($event)" + ) + i.icon.fa.fa-files-o + button.btn.btn-link.action-btn( + tooltip=translate('download'), + tooltip-placement="top", + tooltip-append-to-body="true", + ng-click="download($event)" + ) + i.icon.fa.fa-cloud-download + button.btn.btn-link.action-btn( + ng-if="!project.archived && isOwner()" + tooltip=translate('archive'), + tooltip-placement="top", + tooltip-append-to-body="true", + ng-click="archiveOrLeave($event)" + ) + i.icon.fa.fa-inbox + button.btn.btn-link.action-btn( + ng-if="!project.archived && !isOwner()" + tooltip=translate('leave'), + tooltip-placement="top", + tooltip-append-to-body="true", + ng-click="archiveOrLeave($event)" + ) + i.icon.fa.fa-sign-out + button.btn.btn-link.action-btn( + ng-if="project.archived" + tooltip=translate('unarchive'), + tooltip-placement="top", + tooltip-append-to-body="true", + ng-click="restore($event)" + ) + i.icon.fa.fa-reply + button.btn.btn-link.action-btn( + ng-if="project.archived && isOwner()" + tooltip=translate('delete_forever'), + tooltip-placement="top", + tooltip-append-to-body="true", + ng-click="deleteProject($event)" + ) + i.icon.fa.fa-trash + div( + ng-if="project.isTableActionInflight" + ) + i.fa.fa-spinner.fa-spin \ No newline at end of file diff --git a/services/web/app/views/project/list/project-list.pug b/services/web/app/views/project/list/project-list.pug index a6971aa49a..cfea4aa6c6 100644 --- a/services/web/app/views/project/list/project-list.pug +++ b/services/web/app/views/project/list/project-list.pug @@ -1,175 +1,185 @@ .row - .col-xs-12(ng-cloak) + .col-xs-12(ng-cloak) - form.project-search.form-horizontal(role="form") - .form-group.has-feedback.has-feedback-left.col-md-7.col-xs-12 - input.form-control.col-md-7.col-xs-12( - placeholder=translate('search_projects')+"…", - aria-label=translate('search_projects')+"…", - autofocus='autofocus', - ng-model="searchText.value", - focus-on='search:clear', - ng-keyup="searchProjects()" - ) - i.fa.fa-search.form-control-feedback-left - i.fa.fa-times.form-control-feedback( - ng-click="clearSearchText()", - style="cursor: pointer;", - ng-show="searchText.value.length > 0" - ) - //- i.fa.fa-remove + form.project-search.form-horizontal(role="form") + .form-group.has-feedback.has-feedback-left.col-md-7.col-xs-12 + input.form-control.col-md-7.col-xs-12( + placeholder=translate('search_projects')+"…", + aria-label=translate('search_projects')+"…", + autofocus='autofocus', + ng-model="searchText.value", + focus-on='search:clear', + ng-keyup="searchProjects()" + ) + i.fa.fa-search.form-control-feedback-left + i.fa.fa-times.form-control-feedback( + ng-click="clearSearchText()", + style="cursor: pointer;", + ng-show="searchText.value.length > 0" + ) + //- i.fa.fa-remove - .project-tools(ng-cloak) - .btn-toolbar(ng-show="filter != 'archived'") - .btn-group(ng-hide="selectedProjects.length < 1") - a.btn.btn-default( - href, - tooltip=translate('download'), - tooltip-placement="bottom", - tooltip-append-to-body="true", - ng-click="downloadSelectedProjects()" - ) - i.fa.fa-cloud-download - - var archiveButtonString = settings.overleaf ? translate("archive") : translate("delete") - - var archiveButtonIcon = settings.overleaf ? "fa-inbox" : "fa-trash-o" - a.btn.btn-default( - href, - tooltip=`{{ isArchiveableProjectSelected ? '${archiveButtonString}' : '${translate("leave")}' }}`, - tooltip-placement="bottom", - tooltip-append-to-body="true", - ng-click="openArchiveProjectsModal()" - ) - i.fa(ng-class=`isArchiveableProjectSelected ? '${archiveButtonIcon}' : 'fa-sign-out'`) + .project-tools(ng-cloak) + .btn-toolbar(ng-show="filter != 'archived'") + .btn-group(ng-hide="selectedProjects.length < 1") + a.btn.btn-default( + href, + tooltip=translate('download'), + tooltip-placement="bottom", + tooltip-append-to-body="true", + ng-click="downloadSelectedProjects()" + ) + i.fa.fa-cloud-download + - var archiveButtonString = settings.overleaf ? translate("archive") : translate("delete") + - var archiveButtonIcon = settings.overleaf ? "fa-inbox" : "fa-trash-o" + a.btn.btn-default( + href, + tooltip=`{{ isArchiveableProjectSelected ? '${archiveButtonString}' : '${translate("leave")}' }}`, + tooltip-placement="bottom", + tooltip-append-to-body="true", + ng-click="openArchiveProjectsModal()" + ) + i.fa(ng-class=`isArchiveableProjectSelected ? '${archiveButtonIcon}' : 'fa-sign-out'`) - .btn-group.dropdown(ng-hide="selectedProjects.length < 1", dropdown) - a.btn.btn-default.dropdown-toggle( - href, - data-toggle="dropdown", - dropdown-toggle, - tooltip=translate('add_to_folders'), - tooltip-append-to-body="true", - tooltip-placement="bottom" - ) - i.fa.fa-folder-open-o - | - span.caret - ul.dropdown-menu.dropdown-menu-right.js-tags-dropdown-menu.tags-dropdown-menu( - role="menu" - ng-controller="TagListController" - ) - li.dropdown-header #{translate("add_to_folder")} - li( - ng-repeat="tag in tags | orderBy:'name'", - ng-controller="TagDropdownItemController" - ng-if="!tag.isV1" - ) - a(href="#", ng-click="addOrRemoveProjectsFromTag()", stop-propagation="click") - i.fa( - ng-class="{\ - 'fa-check-square-o': areSelectedProjectsInTag == true,\ - 'fa-square-o': areSelectedProjectsInTag == false,\ - 'fa-minus-square-o': areSelectedProjectsInTag == 'partial'\ - }" - ) - | {{tag.name}} - li.divider - li - a(href, ng-click="openNewTagModal()", stop-propagation="click") #{translate("create_new_folder")} + .btn-group.dropdown(ng-hide="selectedProjects.length < 1", dropdown) + a.btn.btn-default.dropdown-toggle( + href, + data-toggle="dropdown", + dropdown-toggle, + tooltip=translate('add_to_folders'), + tooltip-append-to-body="true", + tooltip-placement="bottom" + ) + i.fa.fa-folder-open-o + | + span.caret + ul.dropdown-menu.dropdown-menu-right.js-tags-dropdown-menu.tags-dropdown-menu( + role="menu" + ng-controller="TagListController" + ) + li.dropdown-header #{translate("add_to_folder")} + li( + ng-repeat="tag in tags | orderBy:'name'", + ng-controller="TagDropdownItemController" + ng-if="!tag.isV1" + ) + a(href="#", ng-click="addOrRemoveProjectsFromTag()", stop-propagation="click") + i.fa( + ng-class="{\ + 'fa-check-square-o': areSelectedProjectsInTag == true,\ + 'fa-square-o': areSelectedProjectsInTag == false,\ + 'fa-minus-square-o': areSelectedProjectsInTag == 'partial'\ + }" + ) + | {{tag.name}} + li.divider + li + a(href, ng-click="openNewTagModal()", stop-propagation="click") #{translate("create_new_folder")} - .btn-group(ng-hide="selectedProjects.length != 1", dropdown).dropdown - a.btn.btn-default.dropdown-toggle( - href, - data-toggle="dropdown", - dropdown-toggle - ) #{translate("more")} - span.caret - ul.dropdown-menu.dropdown-menu-right(role="menu") - li(ng-show="getFirstSelectedProject().accessLevel == 'owner'") - a( - href, - ng-click="openRenameProjectModal()" - ) #{translate("rename")} - li - a( - href, - ng-click="openCloneProjectModal()" - ) #{translate("make_copy")} + .btn-group(ng-hide="selectedProjects.length != 1", dropdown).dropdown + a.btn.btn-default.dropdown-toggle( + href, + data-toggle="dropdown", + dropdown-toggle + ) #{translate("more")} + span.caret + ul.dropdown-menu.dropdown-menu-right(role="menu") + li(ng-show="getFirstSelectedProject().accessLevel == 'owner'") + a( + href, + ng-click="openRenameProjectModal()" + ) #{translate("rename")} + li + a( + href, + ng-click="openCloneProjectModal()" + ) #{translate("make_copy")} - .btn-toolbar(ng-show="filter == 'archived'") - .btn-group(ng-hide="selectedProjects.length < 1") - a.btn.btn-default( - href, - data-original-title="Restore", - data-toggle="tooltip", - data-placement="bottom", - ng-click="restoreSelectedProjects()" - ) #{translate("restore")} + .btn-toolbar(ng-show="filter == 'archived'") + .btn-group(ng-hide="selectedProjects.length < 1") + a.btn.btn-default( + href, + data-original-title="Restore", + data-toggle="tooltip", + data-placement="bottom", + ng-click="restoreSelectedProjects()" + ) #{translate("restore")} - .btn-group(ng-hide="selectedProjects.length < 1") - a.btn.btn-danger( - href, - data-original-title="Delete Forever", - data-toggle="tooltip", - data-placement="bottom", - ng-click="openDeleteProjectsModal()" - ) #{translate("delete_forever")} + .btn-group(ng-hide="selectedProjects.length < 1") + a.btn.btn-danger( + href, + data-original-title="Delete Forever", + data-toggle="tooltip", + data-placement="bottom", + ng-click="openDeleteProjectsModal()" + ) #{translate("delete_forever")} .row.row-spaced - each warning in warnings - .col-xs-12 - .alert.alert-warning(role="alert")= warning + each warning in warnings + .col-xs-12 + .alert.alert-warning(role="alert")= warning - .col-xs-12 - .card.card-thin.project-list-card - .table-wrapper(max-height="projectListHeight - 25",) - table.table.table-hover.project-list( - select-all-list, - ng-if="projects.length > 0", - ng-cloak - ) - thead - tr - th.selectProject - input.select-all( - select-all, - type="checkbox" - aria-label=translate('select_all_projects') - ) - th.projectName - span.header.clickable(ng-click="changePredicate('name')") #{translate("title")} - i.tablesort.fa(ng-class="getSortIconClass('name')") - th - span.header.clickable(ng-click="changePredicate('accessLevel')") #{translate("owner")} - i.tablesort.fa(ng-class="getSortIconClass('accessLevel')") - th - span.header.clickable(ng-click="changePredicate('lastUpdated')") #{translate("last_modified")} - i.tablesort.fa(ng-class="getSortIconClass('lastUpdated')") - if settings.overleaf - th.text-right - span.header #{translate("actions")} - tbody - tr.project_entry( - ng-repeat="project in visibleProjects | orderBy:predicate:reverse", - ng-controller="ProjectListItemController" - ) - include ./item - tr( - ng-if="visibleProjects.length == 0", - ng-cloak - ) - td(colspan=5).text-center - small #{translate("no_projects")} + .col-xs-12 + .card.card-thin.project-list-card + ul.list-unstyled.project-list.structured-list( + select-all-list, + ng-if="projects.length > 0", + max-height="projectListHeight - 25", + ng-cloak + ) + li.container-fluid + .row + - var titleClasses = settings.overleaf ? " col-xs-6 col-sm-4 col-md-6" : "col-xs-6" + - var lastUpdatedClasses = settings.overleaf ? " col-xs-4 col-sm-3 col-md-2" : "col-xs-4" - div.welcome.text-centered(ng-if="projects.length == 0", ng-cloak) - h2 #{translate("welcome_to_sl")} - p #{translate("new_to_latex_look_at")} - a(href="/templates") #{translate("templates").toLowerCase()} - | #{translate("or")} - a(href="/learn") #{translate("latex_help_guide")} - | , - br - | #{translate("or_create_project_left")} + div(class=titleClasses) + input.select-all( + select-all, + type="checkbox" + aria-label=translate('select_all_projects') + ) + span.header.clickable(ng-click="changePredicate('name')") #{translate("title")} + i.tablesort.fa(ng-class="getSortIconClass('name')") + .col-xs-2 + span.header.clickable(ng-click="changePredicate('accessLevel')") #{translate("owner")} + i.tablesort.fa(ng-class="getSortIconClass('accessLevel')") + div(class=lastUpdatedClasses) + span.header.clickable(ng-click="changePredicate('lastUpdated')") #{translate("last_modified")} + i.tablesort.fa(ng-class="getSortIconClass('lastUpdated')") + if settings.overleaf + .hidden-xs.col-sm-3.col-md-2.action-btn-row-header + span.header #{translate("actions")} + li.project_entry.container-fluid( + ng-repeat="project in visibleProjects | orderBy:predicate:reverse", + ng-controller="ProjectListItemController" + ) + .row( + ng-if="!project.isV1Project" + select-row + ) + include ./item + .row( + ng-if="project.isV1Project" + ) + include ./v1-item + li( + ng-if="visibleProjects.length == 0", + ng-cloak + ) + .row + .col-xs-12.text-centered + small #{translate("no_projects")} + div.welcome.text-centered(ng-if="projects.length == 0", ng-cloak) + h2 #{translate("welcome_to_sl")} + p #{translate("new_to_latex_look_at")} + a(href="/templates") #{translate("templates").toLowerCase()} + | #{translate("or")} + a(href="/learn") #{translate("latex_help_guide")} + | , + br + | #{translate("or_create_project_left")} + diff --git a/services/web/app/views/project/list/v1-item.pug b/services/web/app/views/project/list/v1-item.pug new file mode 100644 index 0000000000..5a8e37bca0 --- /dev/null +++ b/services/web/app/views/project/list/v1-item.pug @@ -0,0 +1,25 @@ +.col-xs-6.col-sm-4.col-md-6 + .select-item + span.v1-badge( + aria-label=translate("v1_badge") + tooltip-template="'v1ProjectTooltipTemplate'" + tooltip-append-to-body="true" + ) + span + if settings.overleaf && settings.overleaf.host + button.btn.btn-link.projectName( + ng-click="openV1ImportModal(project)" + stop-propagation="click" + ng-show="project.accessLevel == 'owner'" + ) {{project.name}} + a.projectName( + href=settings.overleaf.host + "/{{project.id}}" + target="_blank" + ng-hide="project.accessLevel == 'owner'" + ) {{project.name}} + +.col-xs-2 + span.owner {{ownerName()}} + +.col-xs-4.col-sm-3.col-md-2 + span.last-modified(tooltip="{{project.lastUpdated | formatDate}}") {{project.lastUpdated | fromNowDate}} \ No newline at end of file diff --git a/services/web/public/coffee/main/project-list/project-list.coffee b/services/web/public/coffee/main/project-list/project-list.coffee index 0146ff3348..9a8d7f7a73 100644 --- a/services/web/public/coffee/main/project-list/project-list.coffee +++ b/services/web/public/coffee/main/project-list/project-list.coffee @@ -13,7 +13,7 @@ define [ $scope.predicate = "lastUpdated" $scope.nUntagged = 0 $scope.reverse = true - $scope.searchText = + $scope.searchText = value : "" $timeout () -> @@ -37,7 +37,7 @@ define [ angular.element($window).bind "resize", () -> recalculateProjectListHeight() $scope.$apply() - + # Allow tags to be accessed on projects as well projectsById = {} for project in $scope.projects @@ -56,7 +56,7 @@ define [ tag.selected = true else tag.selected = false - + $scope.changePredicate = (newPredicate)-> if $scope.predicate == newPredicate $scope.reverse = !$scope.reverse @@ -145,7 +145,7 @@ define [ # We don't want hidden selections project.selected = false - localStorage("project_list", JSON.stringify({ + localStorage("project_list", JSON.stringify({ filter: $scope.filter, selectedTagId: selectedTag?._id })) @@ -461,7 +461,7 @@ define [ resolve: project: () -> project ) - + if storedUIOpts?.filter? if storedUIOpts.filter == "tag" and storedUIOpts.selectedTagId? markTagAsSelected(storedUIOpts.selectedTagId) @@ -485,11 +485,11 @@ define [ $scope.isLinkSharingProject = (project) -> return project.source == 'token' - $scope.userDisplayName = (user) -> - if user? and user._id == window.user_id + $scope.ownerName = () -> + if $scope.project.accessLevel == "owner" return "You" - else if user? - return [user.first_name, user.last_name].filter((n) -> n?).join(" ") + else if $scope.project.owner? + return [$scope.project.owner.first_name, $scope.project.owner.last_name].filter((n) -> n?).join(" ") else return "None" @@ -535,11 +535,11 @@ define [ url: "/project/#{$scope.project.id}?forever=true" headers: "X-CSRF-Token": window.csrfToken - }).then () -> + }).then () -> $scope.project.isTableActionInflight = false $scope._removeProjectFromList $scope.project for tag in $scope.tags $scope._removeProjectIdsFromTagArray(tag, [ $scope.project.id ]) $scope.updateVisibleProjects() - .catch () -> + .catch () -> $scope.project.isTableActionInflight = false diff --git a/services/web/public/stylesheets/app/project-list.less b/services/web/public/stylesheets/app/project-list.less index 86a72436c4..e5bc8da14c 100644 --- a/services/web/public/stylesheets/app/project-list.less +++ b/services/web/public/stylesheets/app/project-list.less @@ -62,7 +62,7 @@ .small { color: @sidebar-color; } - } + } .project-list-sidebar when (@is-overleaf) { overflow-x: hidden; @@ -271,7 +271,7 @@ ul.structured-list { li { border-bottom: 1px solid @structured-list-border-color; padding: (@line-height-computed / 4) 0; - + &:last-child { border-bottom: 0 none; } @@ -294,7 +294,7 @@ ul.structured-list { .header when (@is-overleaf = false) { text-transform: uppercase; } - + .select-item, .select-all { position: absolute; left: @line-height-computed; @@ -314,38 +314,8 @@ ul.structured-list { padding: 0 (@line-height-computed / 4); } -.table-wrapper { - overflow: scroll; -} - -table.project-list { - margin: 0; - width: 100%; - - th when (@is-overleaf = true) { - font-weight: 600; - } - th when (@is-overleaf = false) { - text-transform: uppercase; - font-weight: normal; - } - - // thead > tr > th { - // padding-top: @line-height-computed / 8; - // } - - td { - @media (min-width: @screen-md-min) { - white-space: nowrap; - } - &.projectName { - white-space: normal; - width: 50%; - } - &.selectProject { - width: 1%; - } - +ul.project-list { + li { .last-modified when (@is-overleaf = false) { font-size: .8rem; } @@ -355,13 +325,12 @@ table.project-list { .owner when (@is-overleaf = false) { margin-right: 0; } - a.projectName, button.projectName { + .projectName { margin-right: @line-height-computed / 4; padding: 0; vertical-align: inherit; white-space: normal; text-align: left; - color: @structured-list-link-color; } .tag-label { @@ -400,7 +369,6 @@ table.project-list { .v1-badge { margin-left: -4px; - margin-right: -4px; } .action-btn-row-header, .action-btn-row { @@ -596,7 +564,7 @@ table.project-list { &:last-child { margin-bottom: 0; } - } + } .announcement-header { .page-header; margin: 0; diff --git a/services/web/public/stylesheets/components/tables.less b/services/web/public/stylesheets/components/tables.less index 30ede6697c..c41989c04d 100755 --- a/services/web/public/stylesheets/components/tables.less +++ b/services/web/public/stylesheets/components/tables.less @@ -34,7 +34,7 @@ th { // Bottom align for column headings > thead > tr > th { vertical-align: bottom; - border-bottom: 1px solid @table-border-color; + border-bottom: 2px solid @table-border-color; } // Remove top border from thead by default > caption + thead, diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less index 41bdbd2d9b..9d7eab3493 100644 --- a/services/web/public/stylesheets/core/_common-variables.less +++ b/services/web/public/stylesheets/core/_common-variables.less @@ -110,7 +110,7 @@ //## Customizes the `.table` component with basic values, each used across all table variations. //** Padding for `