mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #900 from sharelatex/ja-show-last-modified
Record and show last modified by user for projects
This commit is contained in:
commit
078575b236
17 changed files with 410 additions and 344 deletions
|
@ -7,6 +7,7 @@ HistoryManager = require "./HistoryManager"
|
||||||
ProjectDetailsHandler = require "../Project/ProjectDetailsHandler"
|
ProjectDetailsHandler = require "../Project/ProjectDetailsHandler"
|
||||||
ProjectEntityUpdateHandler = require "../Project/ProjectEntityUpdateHandler"
|
ProjectEntityUpdateHandler = require "../Project/ProjectEntityUpdateHandler"
|
||||||
RestoreManager = require "./RestoreManager"
|
RestoreManager = require "./RestoreManager"
|
||||||
|
ProjectUpdateHandler = require "../Project/ProjectUpdateHandler"
|
||||||
|
|
||||||
module.exports = HistoryController =
|
module.exports = HistoryController =
|
||||||
selectHistoryApi: (req, res, next = (error) ->) ->
|
selectHistoryApi: (req, res, next = (error) ->) ->
|
||||||
|
@ -143,3 +144,11 @@ module.exports = HistoryController =
|
||||||
error = new Error("history api responded with non-success code: #{response.statusCode}")
|
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}"
|
logger.error err: error, "project-history api responded with non-success code: #{response.statusCode}"
|
||||||
callback(error)
|
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
|
||||||
|
|
|
@ -184,7 +184,7 @@ module.exports = ProjectController =
|
||||||
notifications: (cb)->
|
notifications: (cb)->
|
||||||
NotificationsHandler.getUserNotifications user_id, cb
|
NotificationsHandler.getUserNotifications user_id, cb
|
||||||
projects: (cb)->
|
projects: (cb)->
|
||||||
ProjectGetter.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref tokens', cb
|
ProjectGetter.findAllUsersProjects user_id, 'name lastUpdated lastUpdatedBy publicAccesLevel archived owner_ref tokens', cb
|
||||||
v1Projects: (cb) ->
|
v1Projects: (cb) ->
|
||||||
Modules.hooks.fire "findAllV1Projects", user_id, (error, projects = []) ->
|
Modules.hooks.fire "findAllV1Projects", user_id, (error, projects = []) ->
|
||||||
if error? and error instanceof V1ConnectionError
|
if error? and error instanceof V1ConnectionError
|
||||||
|
@ -392,6 +392,7 @@ module.exports = ProjectController =
|
||||||
id: project._id
|
id: project._id
|
||||||
name: project.name
|
name: project.name
|
||||||
lastUpdated: project.lastUpdated
|
lastUpdated: project.lastUpdated
|
||||||
|
lastUpdatedBy: project.lastUpdatedBy
|
||||||
publicAccessLevel: project.publicAccesLevel
|
publicAccessLevel: project.publicAccesLevel
|
||||||
accessLevel: accessLevel
|
accessLevel: accessLevel
|
||||||
source: source
|
source: source
|
||||||
|
@ -430,6 +431,8 @@ module.exports = ProjectController =
|
||||||
for project in projects
|
for project in projects
|
||||||
if project.owner_ref?
|
if project.owner_ref?
|
||||||
users[project.owner_ref.toString()] = true
|
users[project.owner_ref.toString()] = true
|
||||||
|
if project.lastUpdatedBy?
|
||||||
|
users[project.lastUpdatedBy] = true
|
||||||
|
|
||||||
jobs = []
|
jobs = []
|
||||||
for user_id, _ of users
|
for user_id, _ of users
|
||||||
|
@ -444,6 +447,8 @@ module.exports = ProjectController =
|
||||||
for project in projects
|
for project in projects
|
||||||
if project.owner_ref?
|
if project.owner_ref?
|
||||||
project.owner = users[project.owner_ref.toString()]
|
project.owner = users[project.owner_ref.toString()]
|
||||||
|
if project.lastUpdatedBy?
|
||||||
|
project.lastUpdatedBy = users[project.lastUpdatedBy.toString()]
|
||||||
callback null, projects
|
callback null, projects
|
||||||
|
|
||||||
_buildWarningsList: (v1ProjectData = {}) ->
|
_buildWarningsList: (v1ProjectData = {}) ->
|
||||||
|
|
|
@ -114,8 +114,6 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||||
logger.log {project_id, doc_id, modified}, "finished updating doc lines"
|
logger.log {project_id, doc_id, modified}, "finished updating doc lines"
|
||||||
# path will only be present if the doc is not deleted
|
# path will only be present if the doc is not deleted
|
||||||
if modified && !isDeletedDoc
|
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
|
TpdsUpdateSender.addDoc {project_id:project_id, path:path.fileSystem, doc_id:doc_id, project_name:project.name, rev:rev}, callback
|
||||||
else
|
else
|
||||||
callback()
|
callback()
|
||||||
|
|
|
@ -2,30 +2,25 @@ Project = require('../../models/Project').Project
|
||||||
logger = require('logger-sharelatex')
|
logger = require('logger-sharelatex')
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
markAsUpdated : (project_id, callback)->
|
markAsUpdated : (project_id, user_id, timestamp, callback)->
|
||||||
conditions = {_id:project_id}
|
conditions = {_id:project_id}
|
||||||
update = {lastUpdated:Date.now()}
|
update = {
|
||||||
Project.update conditions, update, {}, (err)->
|
lastUpdated: new Date(timestamp),
|
||||||
if callback?
|
lastUpdatedBy: user_id
|
||||||
callback()
|
}
|
||||||
|
Project.update conditions, update, {}, callback
|
||||||
|
|
||||||
markAsOpened : (project_id, callback)->
|
markAsOpened : (project_id, callback)->
|
||||||
conditions = {_id:project_id}
|
conditions = {_id:project_id}
|
||||||
update = {lastOpened:Date.now()}
|
update = {lastOpened:Date.now()}
|
||||||
Project.update conditions, update, {}, (err)->
|
Project.update conditions, update, {}, callback
|
||||||
if callback?
|
|
||||||
callback()
|
|
||||||
|
|
||||||
markAsInactive: (project_id, callback)->
|
markAsInactive: (project_id, callback)->
|
||||||
conditions = {_id:project_id}
|
conditions = {_id:project_id}
|
||||||
update = {active:false}
|
update = {active:false}
|
||||||
Project.update conditions, update, {}, (err)->
|
Project.update conditions, update, {}, callback
|
||||||
if callback?
|
|
||||||
callback()
|
|
||||||
|
|
||||||
markAsActive: (project_id, callback)->
|
markAsActive: (project_id, callback)->
|
||||||
conditions = {_id:project_id}
|
conditions = {_id:project_id}
|
||||||
update = {active:true}
|
update = {active:true}
|
||||||
Project.update conditions, update, {}, (err)->
|
Project.update conditions, update, {}, callback
|
||||||
if callback?
|
|
||||||
callback()
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ DeletedFileSchema = new Schema
|
||||||
ProjectSchema = new Schema
|
ProjectSchema = new Schema
|
||||||
name : {type:String, default:'new project'}
|
name : {type:String, default:'new project'}
|
||||||
lastUpdated : {type:Date, default: () -> new Date()}
|
lastUpdated : {type:Date, default: () -> new Date()}
|
||||||
|
lastUpdatedBy : {type:ObjectId, ref: 'User'}
|
||||||
lastOpened : {type:Date}
|
lastOpened : {type:Date}
|
||||||
active : { type: Boolean, default: true }
|
active : { type: Boolean, default: true }
|
||||||
owner_ref : {type:ObjectId, ref:'User'}
|
owner_ref : {type:ObjectId, ref:'User'}
|
||||||
|
|
|
@ -238,6 +238,7 @@ module.exports = class Router
|
||||||
webRouter.post '/project/:project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreDocFromDeletedDoc
|
webRouter.post '/project/:project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreDocFromDeletedDoc
|
||||||
webRouter.post "/project/:project_id/restore_file", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreFileFromV2
|
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/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.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
|
webRouter.post "/project/:Project_id/labels", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.selectHistoryApi, HistoryController.ensureProjectHistoryEnabled, HistoryController.createLabel
|
||||||
|
|
|
@ -1,104 +1,123 @@
|
||||||
- var titleClasses = settings.overleaf ? "col-xs-6 col-sm-4 col-md-6" : "col-xs-6"
|
td.selectProject
|
||||||
- var lastUpdatedClasses = settings.overleaf ? " col-xs-4 col-sm-3 col-md-2" : "col-xs-4"
|
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)"
|
||||||
|
) ×
|
||||||
|
|
||||||
div(class=titleClasses)
|
td
|
||||||
input.select-item(
|
span.owner {{userDisplayName(project.owner)}}
|
||||||
select-individual,
|
span(ng-if="isLinkSharingProject(project)")
|
||||||
type="checkbox",
|
|
|
||||||
ng-disabled="shouldDisableCheckbox(project)",
|
i.fa.fa-link.small(
|
||||||
ng-model="project.selected"
|
tooltip=translate("link_sharing")
|
||||||
stop-propagation="click"
|
tooltip-placement="right"
|
||||||
aria-label=translate('select_project') + " '{{ project.name }}'"
|
tooltip-append-to-body="true"
|
||||||
)
|
)
|
||||||
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)"
|
|
||||||
) ×
|
|
||||||
|
|
||||||
.col-xs-2
|
td
|
||||||
span.owner {{ownerName()}}
|
span.last-modified(tooltip="{{project.lastUpdated | formatDate}}")
|
||||||
span(ng-if="isLinkSharingProject(project)")
|
| {{project.lastUpdated | fromNowDate}}
|
||||||
|
|
span(ng-if='project.lastUpdatedBy')
|
||||||
i.fa.fa-link.small(
|
|
|
||||||
tooltip=translate("link_sharing")
|
| #{translate('by')}
|
||||||
tooltip-placement="right"
|
|
|
||||||
tooltip-append-to-body="true"
|
| {{userDisplayName(project.lastUpdatedBy)}}
|
||||||
)
|
|
||||||
|
|
||||||
div(class=lastUpdatedClasses)
|
td.text-right
|
||||||
if settings.overleaf
|
div(
|
||||||
span.last-modified(tooltip="{{project.lastUpdated | formatDate}}") {{project.lastUpdated | fromNowDate}}
|
ng-if="!project.isTableActionInflight && !project.isV1Project"
|
||||||
else
|
)
|
||||||
span.last-modified {{project.lastUpdated | formatDate}}
|
button.btn.btn-link.action-btn(
|
||||||
|
tooltip=translate('copy'),
|
||||||
if settings.overleaf
|
tooltip-placement="top",
|
||||||
.hidden-xs.col-sm-3.col-md-2.action-btn-row
|
tooltip-append-to-body="true",
|
||||||
div(
|
ng-click="clone($event)"
|
||||||
ng-if="!project.isTableActionInflight"
|
)
|
||||||
)
|
i.icon.fa.fa-files-o
|
||||||
button.btn.btn-link.action-btn(
|
button.btn.btn-link.action-btn(
|
||||||
tooltip=translate('copy'),
|
tooltip=translate('download'),
|
||||||
tooltip-placement="top",
|
tooltip-placement="top",
|
||||||
tooltip-append-to-body="true",
|
tooltip-append-to-body="true",
|
||||||
ng-click="clone($event)"
|
ng-click="download($event)"
|
||||||
)
|
)
|
||||||
i.icon.fa.fa-files-o
|
i.icon.fa.fa-cloud-download
|
||||||
button.btn.btn-link.action-btn(
|
button.btn.btn-link.action-btn(
|
||||||
tooltip=translate('download'),
|
ng-if="!project.archived && isOwner()"
|
||||||
tooltip-placement="top",
|
tooltip=translate('archive'),
|
||||||
tooltip-append-to-body="true",
|
tooltip-placement="top",
|
||||||
ng-click="download($event)"
|
tooltip-append-to-body="true",
|
||||||
)
|
ng-click="archiveOrLeave($event)"
|
||||||
i.icon.fa.fa-cloud-download
|
)
|
||||||
button.btn.btn-link.action-btn(
|
i.icon.fa.fa-inbox
|
||||||
ng-if="!project.archived && isOwner()"
|
button.btn.btn-link.action-btn(
|
||||||
tooltip=translate('archive'),
|
ng-if="!project.archived && !isOwner()"
|
||||||
tooltip-placement="top",
|
tooltip=translate('leave'),
|
||||||
tooltip-append-to-body="true",
|
tooltip-placement="top",
|
||||||
ng-click="archiveOrLeave($event)"
|
tooltip-append-to-body="true",
|
||||||
)
|
ng-click="archiveOrLeave($event)"
|
||||||
i.icon.fa.fa-inbox
|
)
|
||||||
button.btn.btn-link.action-btn(
|
i.icon.fa.fa-sign-out
|
||||||
ng-if="!project.archived && !isOwner()"
|
button.btn.btn-link.action-btn(
|
||||||
tooltip=translate('leave'),
|
ng-if="project.archived"
|
||||||
tooltip-placement="top",
|
tooltip=translate('unarchive'),
|
||||||
tooltip-append-to-body="true",
|
tooltip-placement="top",
|
||||||
ng-click="archiveOrLeave($event)"
|
tooltip-append-to-body="true",
|
||||||
)
|
ng-click="restore($event)"
|
||||||
i.icon.fa.fa-sign-out
|
)
|
||||||
button.btn.btn-link.action-btn(
|
i.icon.fa.fa-reply
|
||||||
ng-if="project.archived"
|
button.btn.btn-link.action-btn(
|
||||||
tooltip=translate('unarchive'),
|
ng-if="project.archived && isOwner()"
|
||||||
tooltip-placement="top",
|
tooltip=translate('delete_forever'),
|
||||||
tooltip-append-to-body="true",
|
tooltip-placement="top",
|
||||||
ng-click="restore($event)"
|
tooltip-append-to-body="true",
|
||||||
)
|
ng-click="deleteProject($event)"
|
||||||
i.icon.fa.fa-reply
|
)
|
||||||
button.btn.btn-link.action-btn(
|
i.icon.fa.fa-trash
|
||||||
ng-if="project.archived && isOwner()"
|
div(
|
||||||
tooltip=translate('delete_forever'),
|
ng-if="project.isTableActionInflight"
|
||||||
tooltip-placement="top",
|
)
|
||||||
tooltip-append-to-body="true",
|
i.fa.fa-spinner.fa-spin
|
||||||
ng-click="deleteProject($event)"
|
|
||||||
)
|
|
||||||
i.icon.fa.fa-trash
|
|
||||||
div(
|
|
||||||
ng-if="project.isTableActionInflight"
|
|
||||||
)
|
|
||||||
i.fa.fa-spinner.fa-spin
|
|
||||||
|
|
|
@ -1,185 +1,175 @@
|
||||||
.row
|
.row
|
||||||
.col-xs-12(ng-cloak)
|
.col-xs-12(ng-cloak)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
form.project-search.form-horizontal(role="form")
|
form.project-search.form-horizontal(role="form")
|
||||||
.form-group.has-feedback.has-feedback-left.col-md-7.col-xs-12
|
.form-group.has-feedback.has-feedback-left.col-md-7.col-xs-12
|
||||||
input.form-control.col-md-7.col-xs-12(
|
input.form-control.col-md-7.col-xs-12(
|
||||||
placeholder=translate('search_projects')+"…",
|
placeholder=translate('search_projects')+"…",
|
||||||
aria-label=translate('search_projects')+"…",
|
aria-label=translate('search_projects')+"…",
|
||||||
autofocus='autofocus',
|
autofocus='autofocus',
|
||||||
ng-model="searchText.value",
|
ng-model="searchText.value",
|
||||||
focus-on='search:clear',
|
focus-on='search:clear',
|
||||||
ng-keyup="searchProjects()"
|
ng-keyup="searchProjects()"
|
||||||
)
|
)
|
||||||
i.fa.fa-search.form-control-feedback-left
|
i.fa.fa-search.form-control-feedback-left
|
||||||
i.fa.fa-times.form-control-feedback(
|
i.fa.fa-times.form-control-feedback(
|
||||||
ng-click="clearSearchText()",
|
ng-click="clearSearchText()",
|
||||||
style="cursor: pointer;",
|
style="cursor: pointer;",
|
||||||
ng-show="searchText.value.length > 0"
|
ng-show="searchText.value.length > 0"
|
||||||
)
|
)
|
||||||
//- i.fa.fa-remove
|
//- i.fa.fa-remove
|
||||||
|
|
||||||
.project-tools(ng-cloak)
|
.project-tools(ng-cloak)
|
||||||
.btn-toolbar(ng-show="filter != 'archived'")
|
.btn-toolbar(ng-show="filter != 'archived'")
|
||||||
.btn-group(ng-hide="selectedProjects.length < 1")
|
.btn-group(ng-hide="selectedProjects.length < 1")
|
||||||
a.btn.btn-default(
|
a.btn.btn-default(
|
||||||
href,
|
href,
|
||||||
tooltip=translate('download'),
|
tooltip=translate('download'),
|
||||||
tooltip-placement="bottom",
|
tooltip-placement="bottom",
|
||||||
tooltip-append-to-body="true",
|
tooltip-append-to-body="true",
|
||||||
ng-click="downloadSelectedProjects()"
|
ng-click="downloadSelectedProjects()"
|
||||||
)
|
)
|
||||||
i.fa.fa-cloud-download
|
i.fa.fa-cloud-download
|
||||||
- var archiveButtonString = settings.overleaf ? translate("archive") : translate("delete")
|
- var archiveButtonString = settings.overleaf ? translate("archive") : translate("delete")
|
||||||
- var archiveButtonIcon = settings.overleaf ? "fa-inbox" : "fa-trash-o"
|
- var archiveButtonIcon = settings.overleaf ? "fa-inbox" : "fa-trash-o"
|
||||||
a.btn.btn-default(
|
a.btn.btn-default(
|
||||||
href,
|
href,
|
||||||
tooltip=`{{ isArchiveableProjectSelected ? '${archiveButtonString}' : '${translate("leave")}' }}`,
|
tooltip=`{{ isArchiveableProjectSelected ? '${archiveButtonString}' : '${translate("leave")}' }}`,
|
||||||
tooltip-placement="bottom",
|
tooltip-placement="bottom",
|
||||||
tooltip-append-to-body="true",
|
tooltip-append-to-body="true",
|
||||||
ng-click="openArchiveProjectsModal()"
|
ng-click="openArchiveProjectsModal()"
|
||||||
)
|
)
|
||||||
i.fa(ng-class=`isArchiveableProjectSelected ? '${archiveButtonIcon}' : 'fa-sign-out'`)
|
i.fa(ng-class=`isArchiveableProjectSelected ? '${archiveButtonIcon}' : 'fa-sign-out'`)
|
||||||
|
|
||||||
.btn-group.dropdown(ng-hide="selectedProjects.length < 1", dropdown)
|
.btn-group.dropdown(ng-hide="selectedProjects.length < 1", dropdown)
|
||||||
a.btn.btn-default.dropdown-toggle(
|
a.btn.btn-default.dropdown-toggle(
|
||||||
href,
|
href,
|
||||||
data-toggle="dropdown",
|
data-toggle="dropdown",
|
||||||
dropdown-toggle,
|
dropdown-toggle,
|
||||||
tooltip=translate('add_to_folders'),
|
tooltip=translate('add_to_folders'),
|
||||||
tooltip-append-to-body="true",
|
tooltip-append-to-body="true",
|
||||||
tooltip-placement="bottom"
|
tooltip-placement="bottom"
|
||||||
)
|
)
|
||||||
i.fa.fa-folder-open-o
|
i.fa.fa-folder-open-o
|
||||||
|
|
|
|
||||||
span.caret
|
span.caret
|
||||||
ul.dropdown-menu.dropdown-menu-right.js-tags-dropdown-menu.tags-dropdown-menu(
|
ul.dropdown-menu.dropdown-menu-right.js-tags-dropdown-menu.tags-dropdown-menu(
|
||||||
role="menu"
|
role="menu"
|
||||||
ng-controller="TagListController"
|
ng-controller="TagListController"
|
||||||
)
|
)
|
||||||
li.dropdown-header #{translate("add_to_folder")}
|
li.dropdown-header #{translate("add_to_folder")}
|
||||||
li(
|
li(
|
||||||
ng-repeat="tag in tags | orderBy:'name'",
|
ng-repeat="tag in tags | orderBy:'name'",
|
||||||
ng-controller="TagDropdownItemController"
|
ng-controller="TagDropdownItemController"
|
||||||
ng-if="!tag.isV1"
|
ng-if="!tag.isV1"
|
||||||
)
|
)
|
||||||
a(href="#", ng-click="addOrRemoveProjectsFromTag()", stop-propagation="click")
|
a(href="#", ng-click="addOrRemoveProjectsFromTag()", stop-propagation="click")
|
||||||
i.fa(
|
i.fa(
|
||||||
ng-class="{\
|
ng-class="{\
|
||||||
'fa-check-square-o': areSelectedProjectsInTag == true,\
|
'fa-check-square-o': areSelectedProjectsInTag == true,\
|
||||||
'fa-square-o': areSelectedProjectsInTag == false,\
|
'fa-square-o': areSelectedProjectsInTag == false,\
|
||||||
'fa-minus-square-o': areSelectedProjectsInTag == 'partial'\
|
'fa-minus-square-o': areSelectedProjectsInTag == 'partial'\
|
||||||
}"
|
}"
|
||||||
)
|
)
|
||||||
| {{tag.name}}
|
| {{tag.name}}
|
||||||
li.divider
|
li.divider
|
||||||
li
|
li
|
||||||
a(href, ng-click="openNewTagModal()", stop-propagation="click") #{translate("create_new_folder")}
|
a(href, ng-click="openNewTagModal()", stop-propagation="click") #{translate("create_new_folder")}
|
||||||
|
|
||||||
.btn-group(ng-hide="selectedProjects.length != 1", dropdown).dropdown
|
.btn-group(ng-hide="selectedProjects.length != 1", dropdown).dropdown
|
||||||
a.btn.btn-default.dropdown-toggle(
|
a.btn.btn-default.dropdown-toggle(
|
||||||
href,
|
href,
|
||||||
data-toggle="dropdown",
|
data-toggle="dropdown",
|
||||||
dropdown-toggle
|
dropdown-toggle
|
||||||
) #{translate("more")}
|
) #{translate("more")}
|
||||||
span.caret
|
span.caret
|
||||||
ul.dropdown-menu.dropdown-menu-right(role="menu")
|
ul.dropdown-menu.dropdown-menu-right(role="menu")
|
||||||
li(ng-show="getFirstSelectedProject().accessLevel == 'owner'")
|
li(ng-show="getFirstSelectedProject().accessLevel == 'owner'")
|
||||||
a(
|
a(
|
||||||
href,
|
href,
|
||||||
ng-click="openRenameProjectModal()"
|
ng-click="openRenameProjectModal()"
|
||||||
) #{translate("rename")}
|
) #{translate("rename")}
|
||||||
li
|
li
|
||||||
a(
|
a(
|
||||||
href,
|
href,
|
||||||
ng-click="openCloneProjectModal()"
|
ng-click="openCloneProjectModal()"
|
||||||
) #{translate("make_copy")}
|
) #{translate("make_copy")}
|
||||||
|
|
||||||
.btn-toolbar(ng-show="filter == 'archived'")
|
.btn-toolbar(ng-show="filter == 'archived'")
|
||||||
.btn-group(ng-hide="selectedProjects.length < 1")
|
.btn-group(ng-hide="selectedProjects.length < 1")
|
||||||
a.btn.btn-default(
|
a.btn.btn-default(
|
||||||
href,
|
href,
|
||||||
data-original-title="Restore",
|
data-original-title="Restore",
|
||||||
data-toggle="tooltip",
|
data-toggle="tooltip",
|
||||||
data-placement="bottom",
|
data-placement="bottom",
|
||||||
ng-click="restoreSelectedProjects()"
|
ng-click="restoreSelectedProjects()"
|
||||||
) #{translate("restore")}
|
) #{translate("restore")}
|
||||||
|
|
||||||
.btn-group(ng-hide="selectedProjects.length < 1")
|
.btn-group(ng-hide="selectedProjects.length < 1")
|
||||||
a.btn.btn-danger(
|
a.btn.btn-danger(
|
||||||
href,
|
href,
|
||||||
data-original-title="Delete Forever",
|
data-original-title="Delete Forever",
|
||||||
data-toggle="tooltip",
|
data-toggle="tooltip",
|
||||||
data-placement="bottom",
|
data-placement="bottom",
|
||||||
ng-click="openDeleteProjectsModal()"
|
ng-click="openDeleteProjectsModal()"
|
||||||
) #{translate("delete_forever")}
|
) #{translate("delete_forever")}
|
||||||
|
|
||||||
.row.row-spaced
|
.row.row-spaced
|
||||||
each warning in warnings
|
each warning in warnings
|
||||||
.col-xs-12
|
.col-xs-12
|
||||||
.alert.alert-warning(role="alert")= warning
|
.alert.alert-warning(role="alert")= warning
|
||||||
|
|
||||||
.col-xs-12
|
.col-xs-12
|
||||||
.card.card-thin.project-list-card
|
.card.card-thin.project-list-card
|
||||||
ul.list-unstyled.project-list.structured-list(
|
.table-wrapper(max-height="projectListHeight - 25",)
|
||||||
select-all-list,
|
table.table.table-hover.project-list(
|
||||||
ng-if="projects.length > 0",
|
select-all-list,
|
||||||
max-height="projectListHeight - 25",
|
ng-if="projects.length > 0",
|
||||||
ng-cloak
|
ng-cloak
|
||||||
)
|
)
|
||||||
li.container-fluid
|
thead
|
||||||
.row
|
tr
|
||||||
- var titleClasses = settings.overleaf ? " col-xs-6 col-sm-4 col-md-6" : "col-xs-6"
|
th.selectProject
|
||||||
- var lastUpdatedClasses = settings.overleaf ? " col-xs-4 col-sm-3 col-md-2" : "col-xs-4"
|
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")}
|
||||||
|
|
||||||
div(class=titleClasses)
|
div.welcome.text-centered(ng-if="projects.length == 0", ng-cloak)
|
||||||
input.select-all(
|
h2 #{translate("welcome_to_sl")}
|
||||||
select-all,
|
p #{translate("new_to_latex_look_at")}
|
||||||
type="checkbox"
|
a(href="/templates") #{translate("templates").toLowerCase()}
|
||||||
aria-label=translate('select_all_projects')
|
| #{translate("or")}
|
||||||
)
|
a(href="/learn") #{translate("latex_help_guide")}
|
||||||
span.header.clickable(ng-click="changePredicate('name')") #{translate("title")}
|
| ,
|
||||||
i.tablesort.fa(ng-class="getSortIconClass('name')")
|
br
|
||||||
.col-xs-2
|
| #{translate("or_create_project_left")}
|
||||||
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")}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
.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}}
|
|
|
@ -13,7 +13,7 @@ define [
|
||||||
$scope.predicate = "lastUpdated"
|
$scope.predicate = "lastUpdated"
|
||||||
$scope.nUntagged = 0
|
$scope.nUntagged = 0
|
||||||
$scope.reverse = true
|
$scope.reverse = true
|
||||||
$scope.searchText =
|
$scope.searchText =
|
||||||
value : ""
|
value : ""
|
||||||
|
|
||||||
$timeout () ->
|
$timeout () ->
|
||||||
|
@ -37,7 +37,7 @@ define [
|
||||||
angular.element($window).bind "resize", () ->
|
angular.element($window).bind "resize", () ->
|
||||||
recalculateProjectListHeight()
|
recalculateProjectListHeight()
|
||||||
$scope.$apply()
|
$scope.$apply()
|
||||||
|
|
||||||
# Allow tags to be accessed on projects as well
|
# Allow tags to be accessed on projects as well
|
||||||
projectsById = {}
|
projectsById = {}
|
||||||
for project in $scope.projects
|
for project in $scope.projects
|
||||||
|
@ -56,7 +56,7 @@ define [
|
||||||
tag.selected = true
|
tag.selected = true
|
||||||
else
|
else
|
||||||
tag.selected = false
|
tag.selected = false
|
||||||
|
|
||||||
$scope.changePredicate = (newPredicate)->
|
$scope.changePredicate = (newPredicate)->
|
||||||
if $scope.predicate == newPredicate
|
if $scope.predicate == newPredicate
|
||||||
$scope.reverse = !$scope.reverse
|
$scope.reverse = !$scope.reverse
|
||||||
|
@ -145,7 +145,7 @@ define [
|
||||||
# We don't want hidden selections
|
# We don't want hidden selections
|
||||||
project.selected = false
|
project.selected = false
|
||||||
|
|
||||||
localStorage("project_list", JSON.stringify({
|
localStorage("project_list", JSON.stringify({
|
||||||
filter: $scope.filter,
|
filter: $scope.filter,
|
||||||
selectedTagId: selectedTag?._id
|
selectedTagId: selectedTag?._id
|
||||||
}))
|
}))
|
||||||
|
@ -461,7 +461,7 @@ define [
|
||||||
resolve:
|
resolve:
|
||||||
project: () -> project
|
project: () -> project
|
||||||
)
|
)
|
||||||
|
|
||||||
if storedUIOpts?.filter?
|
if storedUIOpts?.filter?
|
||||||
if storedUIOpts.filter == "tag" and storedUIOpts.selectedTagId?
|
if storedUIOpts.filter == "tag" and storedUIOpts.selectedTagId?
|
||||||
markTagAsSelected(storedUIOpts.selectedTagId)
|
markTagAsSelected(storedUIOpts.selectedTagId)
|
||||||
|
@ -485,11 +485,11 @@ define [
|
||||||
$scope.isLinkSharingProject = (project) ->
|
$scope.isLinkSharingProject = (project) ->
|
||||||
return project.source == 'token'
|
return project.source == 'token'
|
||||||
|
|
||||||
$scope.ownerName = () ->
|
$scope.userDisplayName = (user) ->
|
||||||
if $scope.project.accessLevel == "owner"
|
if user? and user._id == window.user_id
|
||||||
return "You"
|
return "You"
|
||||||
else if $scope.project.owner?
|
else if user?
|
||||||
return [$scope.project.owner.first_name, $scope.project.owner.last_name].filter((n) -> n?).join(" ")
|
return [user.first_name, user.last_name].filter((n) -> n?).join(" ")
|
||||||
else
|
else
|
||||||
return "None"
|
return "None"
|
||||||
|
|
||||||
|
@ -535,11 +535,11 @@ define [
|
||||||
url: "/project/#{$scope.project.id}?forever=true"
|
url: "/project/#{$scope.project.id}?forever=true"
|
||||||
headers:
|
headers:
|
||||||
"X-CSRF-Token": window.csrfToken
|
"X-CSRF-Token": window.csrfToken
|
||||||
}).then () ->
|
}).then () ->
|
||||||
$scope.project.isTableActionInflight = false
|
$scope.project.isTableActionInflight = false
|
||||||
$scope._removeProjectFromList $scope.project
|
$scope._removeProjectFromList $scope.project
|
||||||
for tag in $scope.tags
|
for tag in $scope.tags
|
||||||
$scope._removeProjectIdsFromTagArray(tag, [ $scope.project.id ])
|
$scope._removeProjectIdsFromTagArray(tag, [ $scope.project.id ])
|
||||||
$scope.updateVisibleProjects()
|
$scope.updateVisibleProjects()
|
||||||
.catch () ->
|
.catch () ->
|
||||||
$scope.project.isTableActionInflight = false
|
$scope.project.isTableActionInflight = false
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
.small {
|
.small {
|
||||||
color: @sidebar-color;
|
color: @sidebar-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-list-sidebar when (@is-overleaf) {
|
.project-list-sidebar when (@is-overleaf) {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
@ -271,7 +271,7 @@ ul.structured-list {
|
||||||
li {
|
li {
|
||||||
border-bottom: 1px solid @structured-list-border-color;
|
border-bottom: 1px solid @structured-list-border-color;
|
||||||
padding: (@line-height-computed / 4) 0;
|
padding: (@line-height-computed / 4) 0;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-bottom: 0 none;
|
border-bottom: 0 none;
|
||||||
}
|
}
|
||||||
|
@ -294,7 +294,7 @@ ul.structured-list {
|
||||||
.header when (@is-overleaf = false) {
|
.header when (@is-overleaf = false) {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-item, .select-all {
|
.select-item, .select-all {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: @line-height-computed;
|
left: @line-height-computed;
|
||||||
|
@ -314,8 +314,38 @@ ul.structured-list {
|
||||||
padding: 0 (@line-height-computed / 4);
|
padding: 0 (@line-height-computed / 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.project-list {
|
.table-wrapper {
|
||||||
li {
|
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%;
|
||||||
|
}
|
||||||
|
|
||||||
.last-modified when (@is-overleaf = false) {
|
.last-modified when (@is-overleaf = false) {
|
||||||
font-size: .8rem;
|
font-size: .8rem;
|
||||||
}
|
}
|
||||||
|
@ -325,12 +355,13 @@ ul.project-list {
|
||||||
.owner when (@is-overleaf = false) {
|
.owner when (@is-overleaf = false) {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
.projectName {
|
a.projectName, button.projectName {
|
||||||
margin-right: @line-height-computed / 4;
|
margin-right: @line-height-computed / 4;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
vertical-align: inherit;
|
vertical-align: inherit;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
color: @structured-list-link-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-label {
|
.tag-label {
|
||||||
|
@ -369,6 +400,7 @@ ul.project-list {
|
||||||
|
|
||||||
.v1-badge {
|
.v1-badge {
|
||||||
margin-left: -4px;
|
margin-left: -4px;
|
||||||
|
margin-right: -4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn-row-header, .action-btn-row {
|
.action-btn-row-header, .action-btn-row {
|
||||||
|
@ -564,7 +596,7 @@ ul.project-list {
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.announcement-header {
|
.announcement-header {
|
||||||
.page-header;
|
.page-header;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
@ -34,7 +34,7 @@ th {
|
||||||
// Bottom align for column headings
|
// Bottom align for column headings
|
||||||
> thead > tr > th {
|
> thead > tr > th {
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
border-bottom: 2px solid @table-border-color;
|
border-bottom: 1px solid @table-border-color;
|
||||||
}
|
}
|
||||||
// Remove top border from thead by default
|
// Remove top border from thead by default
|
||||||
> caption + thead,
|
> caption + thead,
|
||||||
|
|
|
@ -110,7 +110,7 @@
|
||||||
//## Customizes the `.table` component with basic values, each used across all table variations.
|
//## Customizes the `.table` component with basic values, each used across all table variations.
|
||||||
|
|
||||||
//** Padding for `<th>`s and `<td>`s.
|
//** Padding for `<th>`s and `<td>`s.
|
||||||
@table-cell-padding: 8px;
|
@table-cell-padding: @line-height-computed / 4;
|
||||||
//** Padding for cells in `.table-condensed`.
|
//** Padding for cells in `.table-condensed`.
|
||||||
@table-condensed-cell-padding: 5px;
|
@table-condensed-cell-padding: 5px;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
expect = require("chai").expect
|
||||||
|
async = require("async")
|
||||||
|
User = require "./helpers/User"
|
||||||
|
request = require "./helpers/request"
|
||||||
|
settings = require "settings-sharelatex"
|
||||||
|
Project = require("../../../app/js/models/Project").Project
|
||||||
|
|
||||||
|
markAsUpdated = (project_id, user_id, timestamp, callback) ->
|
||||||
|
request.post {
|
||||||
|
url: "/project/#{project_id}/last_updated"
|
||||||
|
json: {
|
||||||
|
user_id,
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
auth:
|
||||||
|
user: settings.apis.web.user
|
||||||
|
pass: settings.apis.web.pass
|
||||||
|
sendImmediately: true
|
||||||
|
jar: false
|
||||||
|
}, callback
|
||||||
|
|
||||||
|
describe "ProjectLastUpdated", ->
|
||||||
|
before (done) ->
|
||||||
|
@timeout(90000)
|
||||||
|
@owner = new User()
|
||||||
|
@timestamp = Date.now()
|
||||||
|
@user_id = "abcdef1234567890abcdef12"
|
||||||
|
async.series [
|
||||||
|
(cb) => @owner.login cb
|
||||||
|
(cb) => @owner.createProject "private-project", (error, @project_id) => cb(error)
|
||||||
|
], done
|
||||||
|
|
||||||
|
describe "with user_id and timestamp", ->
|
||||||
|
it 'should update the project', (done) ->
|
||||||
|
markAsUpdated @project_id, @user_id, @timestamp, (error, response, body) =>
|
||||||
|
return done(error) if error?
|
||||||
|
expect(response.statusCode).to.equal 200
|
||||||
|
Project.findOne _id: @project_id, (error, project) =>
|
||||||
|
return done(error) if error?
|
||||||
|
expect(project.lastUpdated.getTime()).to.equal @timestamp
|
||||||
|
expect(project.lastUpdatedBy.toString()).to.equal @user_id
|
||||||
|
done()
|
|
@ -23,6 +23,7 @@ describe "HistoryController", ->
|
||||||
"../Project/ProjectDetailsHandler": @ProjectDetailsHandler = {}
|
"../Project/ProjectDetailsHandler": @ProjectDetailsHandler = {}
|
||||||
"../Project/ProjectEntityUpdateHandler": @ProjectEntityUpdateHandler = {}
|
"../Project/ProjectEntityUpdateHandler": @ProjectEntityUpdateHandler = {}
|
||||||
"./RestoreManager": @RestoreManager = {}
|
"./RestoreManager": @RestoreManager = {}
|
||||||
|
"../Project/ProjectUpdateHandler": @ProjectUpdateHandler = {}
|
||||||
@settings.apis =
|
@settings.apis =
|
||||||
trackchanges:
|
trackchanges:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
|
@ -190,11 +190,6 @@ describe 'ProjectEntityUpdateHandler', ->
|
||||||
.calledWith(project_id, doc_id, @docLines, @version, @ranges)
|
.calledWith(project_id, doc_id, @docLines, @version, @ranges)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should mark the project as updated", ->
|
|
||||||
@ProjectUpdater.markAsUpdated
|
|
||||||
.calledWith(project_id)
|
|
||||||
.should.equal true
|
|
||||||
|
|
||||||
it "should send the doc the to the TPDS", ->
|
it "should send the doc the to the TPDS", ->
|
||||||
@TpdsUpdateSender.addDoc
|
@TpdsUpdateSender.addDoc
|
||||||
.calledWith({
|
.calledWith({
|
||||||
|
|
|
@ -16,12 +16,15 @@ describe 'ProjectUpdateHandler', ->
|
||||||
describe 'marking a project as recently updated', ->
|
describe 'marking a project as recently updated', ->
|
||||||
it 'should send an update to mongo', (done)->
|
it 'should send an update to mongo', (done)->
|
||||||
project_id = "project_id"
|
project_id = "project_id"
|
||||||
@handler.markAsUpdated project_id, (err)=>
|
user_id = "mock_user_id"
|
||||||
args = @ProjectModel.update.args[0]
|
timestamp = Date.now()
|
||||||
args[0]._id.should.equal project_id
|
@handler.markAsUpdated project_id, user_id, timestamp, (err)=>
|
||||||
date = args[1].lastUpdated+""
|
@ProjectModel.update.calledWith({
|
||||||
now = Date.now()+""
|
_id: project_id,
|
||||||
date.substring(0,5).should.equal now.substring(0,5)
|
}, {
|
||||||
|
lastUpdated: new Date(timestamp),
|
||||||
|
lastUpdatedBy: user_id
|
||||||
|
}).should.equal true
|
||||||
done()
|
done()
|
||||||
|
|
||||||
describe "markAsOpened", ->
|
describe "markAsOpened", ->
|
||||||
|
|
Loading…
Reference in a new issue