mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
32149e652f
When invalid filenames are found during project-copy, the somewhat obscure (and non-localised) 'invalid element name' error is returned. Add a special case to handle this particular error and display something more descriptive to the user. Added a modal error handler for when this error is generated by clicking the 'copy' icon in the project list, instead of using the 'more' dropdown which opens a modal copy dialog bug: overleaf/sharelatex#908 Signed-off-by: Simon Detheridge <s@sd.ai>
554 lines
16 KiB
CoffeeScript
554 lines
16 KiB
CoffeeScript
define [
|
|
"base"
|
|
], (App) ->
|
|
|
|
App.controller "ProjectPageController", ($scope, $modal, $q, $window, queuedHttp, event_tracking, $timeout, localStorage) ->
|
|
$scope.projects = window.data.projects
|
|
$scope.tags = window.data.tags
|
|
$scope.notifications = window.data.notifications
|
|
$scope.allSelected = false
|
|
$scope.selectedProjects = []
|
|
$scope.isArchiveableProjectSelected = false
|
|
$scope.filter = "all"
|
|
$scope.predicate = "lastUpdated"
|
|
$scope.nUntagged = 0
|
|
$scope.reverse = true
|
|
$scope.searchText =
|
|
value : ""
|
|
|
|
$timeout () ->
|
|
recalculateProjectListHeight()
|
|
, 10
|
|
|
|
$scope.$watch((
|
|
() -> $scope.projects.filter((project) -> (!project.tags? or project.tags.length == 0) and !project.archived).length
|
|
), (newVal) -> $scope.nUntagged = newVal)
|
|
|
|
storedUIOpts = JSON.parse(localStorage("project_list"))
|
|
|
|
recalculateProjectListHeight = () ->
|
|
$projListCard = $(".project-list-card")
|
|
topOffset = $projListCard.offset()?.top
|
|
cardPadding = $projListCard.outerHeight() - $projListCard.height()
|
|
bottomOffset = $("footer").outerHeight()
|
|
height = $window.innerHeight - topOffset - bottomOffset - cardPadding
|
|
$scope.projectListHeight = height
|
|
|
|
angular.element($window).bind "resize", () ->
|
|
recalculateProjectListHeight()
|
|
$scope.$apply()
|
|
|
|
# Allow tags to be accessed on projects as well
|
|
projectsById = {}
|
|
for project in $scope.projects
|
|
projectsById[project.id] = project
|
|
|
|
for tag in $scope.tags
|
|
for project_id in tag.project_ids or []
|
|
project = projectsById[project_id]
|
|
if project?
|
|
project.tags ||= []
|
|
project.tags.push tag
|
|
|
|
markTagAsSelected = (id) ->
|
|
for tag in $scope.tags
|
|
if tag._id == id
|
|
tag.selected = true
|
|
else
|
|
tag.selected = false
|
|
|
|
$scope.changePredicate = (newPredicate)->
|
|
if $scope.predicate == newPredicate
|
|
$scope.reverse = !$scope.reverse
|
|
$scope.predicate = newPredicate
|
|
|
|
$scope.getSortIconClass = (column)->
|
|
if column == $scope.predicate and $scope.reverse
|
|
return "fa-caret-down"
|
|
else if column == $scope.predicate and !$scope.reverse
|
|
return "fa-caret-up"
|
|
else
|
|
return ""
|
|
|
|
$scope.searchProjects = ->
|
|
event_tracking.send 'project-list-page-interaction', 'project-search', 'keydown'
|
|
$scope.updateVisibleProjects()
|
|
|
|
$scope.clearSearchText = () ->
|
|
$scope.searchText.value = ""
|
|
$scope.filter = "all"
|
|
$scope.$emit "search:clear"
|
|
$scope.updateVisibleProjects()
|
|
|
|
$scope.setFilter = (filter) ->
|
|
$scope.filter = filter
|
|
$scope.updateVisibleProjects()
|
|
|
|
$scope.updateSelectedProjects = () ->
|
|
$scope.selectedProjects = $scope.projects.filter (project) -> project.selected
|
|
$scope.isArchiveableProjectSelected = $scope.selectedProjects.some (project) ->
|
|
window.user_id == project.owner._id
|
|
|
|
$scope.getSelectedProjects = () ->
|
|
$scope.selectedProjects
|
|
|
|
$scope.getSelectedProjectIds = () ->
|
|
$scope.selectedProjects.map (project) -> project.id
|
|
|
|
$scope.getFirstSelectedProject = () ->
|
|
$scope.selectedProjects[0]
|
|
|
|
$scope.updateVisibleProjects = () ->
|
|
$scope.visibleProjects = []
|
|
selectedTag = $scope.getSelectedTag()
|
|
for project in $scope.projects
|
|
visible = true
|
|
# Only show if it matches any search text
|
|
if $scope.searchText.value? and $scope.searchText.value != ""
|
|
if project.name.toLowerCase().indexOf($scope.searchText.value.toLowerCase()) == -1
|
|
visible = false
|
|
# Only show if it matches the selected tag
|
|
if $scope.filter == "tag" and selectedTag? and project.id not in selectedTag.project_ids
|
|
visible = false
|
|
|
|
# Hide tagged projects if we only want to see the uncategorized ones
|
|
if $scope.filter == "untagged" and project.tags?.length > 0
|
|
visible = false
|
|
|
|
# Hide projects we own if we only want to see shared projects
|
|
if $scope.filter == "shared" and project.accessLevel == "owner"
|
|
visible = false
|
|
|
|
# Hide projects from V1 if we only want to see shared projects
|
|
if $scope.filter == "shared" and project.isV1Project
|
|
visible = false
|
|
|
|
# Hide projects we don't own if we only want to see owned projects
|
|
if $scope.filter == "owned" and project.accessLevel != "owner"
|
|
visible = false
|
|
|
|
if $scope.filter == "archived"
|
|
# Only show archived projects
|
|
if !project.archived
|
|
visible = false
|
|
else
|
|
# Only show non-archived projects
|
|
if project.archived
|
|
visible = false
|
|
|
|
if $scope.filter == "v1" and !project.isV1Project
|
|
visible = false
|
|
|
|
if visible
|
|
$scope.visibleProjects.push project
|
|
else
|
|
# We don't want hidden selections
|
|
project.selected = false
|
|
|
|
localStorage("project_list", JSON.stringify({
|
|
filter: $scope.filter,
|
|
selectedTagId: selectedTag?._id
|
|
}))
|
|
$scope.updateSelectedProjects()
|
|
|
|
$scope.getSelectedTag = () ->
|
|
for tag in $scope.tags
|
|
return tag if tag.selected
|
|
return null
|
|
|
|
$scope._removeProjectIdsFromTagArray = (tag, remove_project_ids) ->
|
|
# Remove project_id from tag.project_ids
|
|
remaining_project_ids = []
|
|
removed_project_ids = []
|
|
for project_id in tag.project_ids
|
|
if project_id not in remove_project_ids
|
|
remaining_project_ids.push project_id
|
|
else
|
|
removed_project_ids.push project_id
|
|
tag.project_ids = remaining_project_ids
|
|
return removed_project_ids
|
|
|
|
$scope._removeProjectFromList = (project) ->
|
|
index = $scope.projects.indexOf(project)
|
|
if index > -1
|
|
$scope.projects.splice(index, 1)
|
|
|
|
$scope.removeSelectedProjectsFromTag = (tag) ->
|
|
tag.showWhenEmpty = true
|
|
|
|
selected_project_ids = $scope.getSelectedProjectIds()
|
|
selected_projects = $scope.getSelectedProjects()
|
|
|
|
removed_project_ids = $scope._removeProjectIdsFromTagArray(tag, selected_project_ids)
|
|
|
|
# Remove tag from project.tags
|
|
remaining_tags = []
|
|
for project in selected_projects
|
|
project.tags ||= []
|
|
index = project.tags.indexOf tag
|
|
if index > -1
|
|
project.tags.splice(index, 1)
|
|
|
|
for project_id in removed_project_ids
|
|
queuedHttp({
|
|
method: "DELETE"
|
|
url: "/tag/#{tag._id}/project/#{project_id}"
|
|
headers:
|
|
"X-CSRF-Token": window.csrfToken
|
|
})
|
|
|
|
# If we're filtering by this tag then we need to remove
|
|
# the projects from view
|
|
$scope.updateVisibleProjects()
|
|
|
|
$scope.removeProjectFromTag = (project, tag) ->
|
|
tag.showWhenEmpty = true
|
|
|
|
project.tags ||= []
|
|
index = project.tags.indexOf tag
|
|
|
|
if index > -1
|
|
$scope._removeProjectIdsFromTagArray(tag, [ project.id ])
|
|
project.tags.splice(index, 1)
|
|
queuedHttp({
|
|
method: "DELETE"
|
|
url: "/tag/#{tag._id}/project/#{project.id}"
|
|
headers:
|
|
"X-CSRF-Token": window.csrfToken
|
|
})
|
|
$scope.updateVisibleProjects()
|
|
|
|
$scope.addSelectedProjectsToTag = (tag) ->
|
|
selected_projects = $scope.getSelectedProjects()
|
|
event_tracking.send 'project-list-page-interaction', 'project action', 'addSelectedProjectsToTag'
|
|
|
|
# Add project_ids into tag.project_ids
|
|
added_project_ids = []
|
|
for project_id in $scope.getSelectedProjectIds()
|
|
unless project_id in tag.project_ids
|
|
tag.project_ids.push project_id
|
|
added_project_ids.push project_id
|
|
|
|
# Add tag into each project.tags
|
|
for project in selected_projects
|
|
project.tags ||= []
|
|
unless tag in project.tags
|
|
project.tags.push tag
|
|
|
|
for project_id in added_project_ids
|
|
queuedHttp.post "/tag/#{tag._id}/project/#{project_id}", {
|
|
_csrf: window.csrfToken
|
|
}
|
|
|
|
$scope.createTag = (name) ->
|
|
return tag
|
|
|
|
$scope.openNewTagModal = (e) ->
|
|
modalInstance = $modal.open(
|
|
templateUrl: "newTagModalTemplate"
|
|
controller: "NewTagModalController"
|
|
)
|
|
|
|
modalInstance.result.then(
|
|
(tag) ->
|
|
$scope.tags.push tag
|
|
$scope.addSelectedProjectsToTag(tag)
|
|
)
|
|
|
|
$scope.createProject = (name, template = "none") ->
|
|
return queuedHttp
|
|
.post("/project/new", {
|
|
_csrf: window.csrfToken
|
|
projectName: name
|
|
template: template
|
|
})
|
|
.then((response) ->
|
|
{ data } = response
|
|
$scope.projects.push {
|
|
name: name
|
|
_id: data.project_id
|
|
accessLevel: "owner"
|
|
# TODO: Check access level if correct after adding it in
|
|
# to the rest of the app
|
|
}
|
|
$scope.updateVisibleProjects()
|
|
)
|
|
|
|
$scope.openCreateProjectModal = (template = "none") ->
|
|
event_tracking.send 'project-list-page-interaction', 'new-project', template
|
|
modalInstance = $modal.open(
|
|
templateUrl: "newProjectModalTemplate"
|
|
controller: "NewProjectModalController"
|
|
resolve:
|
|
template: () -> template
|
|
scope: $scope
|
|
)
|
|
|
|
modalInstance.result.then (project_id) ->
|
|
window.location = "/project/#{project_id}"
|
|
|
|
$scope.renameProject = (project, newName) ->
|
|
return queuedHttp.post "/project/#{project.id}/rename", {
|
|
newProjectName: newName,
|
|
_csrf: window.csrfToken
|
|
}
|
|
.then () ->
|
|
project.name = newName
|
|
|
|
$scope.openRenameProjectModal = () ->
|
|
project = $scope.getFirstSelectedProject()
|
|
return if !project? or project.accessLevel != "owner"
|
|
event_tracking.send 'project-list-page-interaction', 'project action', 'Rename'
|
|
modalInstance = $modal.open(
|
|
templateUrl: "renameProjectModalTemplate"
|
|
controller: "RenameProjectModalController"
|
|
resolve:
|
|
project: () -> project
|
|
scope: $scope
|
|
)
|
|
|
|
$scope.cloneProject = (project, cloneName) ->
|
|
event_tracking.send 'project-list-page-interaction', 'project action', 'Clone'
|
|
queuedHttp
|
|
.post("/project/#{project.id}/clone", {
|
|
_csrf: window.csrfToken
|
|
projectName: cloneName
|
|
})
|
|
.then((response) ->
|
|
{ data } = response
|
|
$scope.projects.push {
|
|
name: cloneName
|
|
id: data.project_id
|
|
accessLevel: "owner"
|
|
owner: {
|
|
_id: user_id
|
|
}
|
|
# TODO: Check access level if correct after adding it in
|
|
# to the rest of the app
|
|
}
|
|
$scope.updateVisibleProjects()
|
|
)
|
|
|
|
$scope.openCloneProjectModal = () ->
|
|
project = $scope.getFirstSelectedProject()
|
|
return if !project?
|
|
|
|
modalInstance = $modal.open(
|
|
templateUrl: "cloneProjectModalTemplate"
|
|
controller: "CloneProjectModalController"
|
|
resolve:
|
|
project: () -> project
|
|
scope: $scope
|
|
)
|
|
|
|
$scope.openArchiveProjectsModal = () ->
|
|
modalInstance = $modal.open(
|
|
templateUrl: "deleteProjectsModalTemplate"
|
|
controller: "DeleteProjectsModalController"
|
|
resolve:
|
|
projects: () -> $scope.getSelectedProjects()
|
|
)
|
|
event_tracking.send 'project-list-page-interaction', 'project action', 'Delete'
|
|
modalInstance.result.then () ->
|
|
$scope.archiveOrLeaveSelectedProjects()
|
|
|
|
$scope.archiveOrLeaveSelectedProjects = () ->
|
|
$scope.archiveOrLeaveProjects($scope.getSelectedProjects())
|
|
|
|
$scope.archiveOrLeaveProjects = (projects) ->
|
|
projectIds = projects.map (p) -> p.id
|
|
# Remove project from any tags
|
|
for tag in $scope.tags
|
|
$scope._removeProjectIdsFromTagArray(tag, projectIds)
|
|
|
|
for project in projects
|
|
project.tags = []
|
|
if project.accessLevel == "owner"
|
|
project.archived = true
|
|
queuedHttp {
|
|
method: "DELETE"
|
|
url: "/project/#{project.id}"
|
|
headers:
|
|
"X-CSRF-Token": window.csrfToken
|
|
}
|
|
else
|
|
$scope._removeProjectFromList project
|
|
|
|
queuedHttp {
|
|
method: "POST"
|
|
url: "/project/#{project.id}/leave"
|
|
headers:
|
|
"X-CSRF-Token": window.csrfToken
|
|
}
|
|
|
|
$scope.updateVisibleProjects()
|
|
|
|
|
|
$scope.openDeleteProjectsModal = () ->
|
|
modalInstance = $modal.open(
|
|
templateUrl: "deleteProjectsModalTemplate"
|
|
controller: "DeleteProjectsModalController"
|
|
resolve:
|
|
projects: () -> $scope.getSelectedProjects()
|
|
)
|
|
|
|
modalInstance.result.then () ->
|
|
$scope.deleteSelectedProjects()
|
|
|
|
$scope.deleteSelectedProjects = () ->
|
|
selected_projects = $scope.getSelectedProjects()
|
|
selected_project_ids = $scope.getSelectedProjectIds()
|
|
|
|
# Remove projects from array
|
|
for project in selected_projects
|
|
$scope._removeProjectFromList project
|
|
|
|
# Remove project from any tags
|
|
for tag in $scope.tags
|
|
$scope._removeProjectIdsFromTagArray(tag, selected_project_ids)
|
|
|
|
for project_id in selected_project_ids
|
|
queuedHttp {
|
|
method: "DELETE"
|
|
url: "/project/#{project_id}?forever=true"
|
|
headers:
|
|
"X-CSRF-Token": window.csrfToken
|
|
}
|
|
|
|
$scope.updateVisibleProjects()
|
|
|
|
$scope.restoreSelectedProjects = () ->
|
|
$scope.restoreProjects($scope.getSelectedProjects())
|
|
|
|
$scope.restoreProjects = (projects) ->
|
|
projectIds = projects.map (p) -> p.id
|
|
for project in projects
|
|
project.archived = false
|
|
|
|
for projectId in projectIds
|
|
queuedHttp {
|
|
method: "POST"
|
|
url: "/project/#{projectId}/restore"
|
|
headers:
|
|
"X-CSRF-Token": window.csrfToken
|
|
}
|
|
|
|
$scope.updateVisibleProjects()
|
|
|
|
$scope.openUploadProjectModal = () ->
|
|
modalInstance = $modal.open(
|
|
templateUrl: "uploadProjectModalTemplate"
|
|
controller: "UploadProjectModalController"
|
|
)
|
|
|
|
$scope.downloadSelectedProjects = () ->
|
|
$scope.downloadProjectsById($scope.getSelectedProjectIds())
|
|
|
|
$scope.downloadProjectsById = (projectIds) ->
|
|
event_tracking.send 'project-list-page-interaction', 'project action', 'Download Zip'
|
|
if projectIds.length > 1
|
|
path = "/project/download/zip?project_ids=#{projectIds.join(',')}"
|
|
else
|
|
path = "/project/#{projectIds[0]}/download/zip"
|
|
window.location = path
|
|
|
|
$scope.openV1ImportModal = (project) ->
|
|
$modal.open(
|
|
templateUrl: 'v1ImportModalTemplate'
|
|
controller: 'V1ImportModalController'
|
|
size: 'lg'
|
|
windowClass: 'v1-import-modal'
|
|
resolve:
|
|
project: () -> project
|
|
)
|
|
|
|
if storedUIOpts?.filter?
|
|
if storedUIOpts.filter == "tag" and storedUIOpts.selectedTagId?
|
|
markTagAsSelected(storedUIOpts.selectedTagId)
|
|
$scope.setFilter(storedUIOpts.filter)
|
|
else
|
|
$scope.updateVisibleProjects()
|
|
|
|
App.controller "ProjectListItemController", ($scope, $modal, queuedHttp) ->
|
|
|
|
$scope.shouldDisableCheckbox = (project) ->
|
|
$scope.filter == 'archived' && project.accessLevel != 'owner'
|
|
|
|
$scope.projectLink = (project) ->
|
|
if project.accessLevel == 'readAndWrite' and project.source == 'token'
|
|
"/#{project.tokens.readAndWrite}"
|
|
else if project.accessLevel == 'readOnly' and project.source == 'token'
|
|
"/read/#{project.tokens.readOnly}"
|
|
else
|
|
"/project/#{project.id}"
|
|
|
|
$scope.isLinkSharingProject = (project) ->
|
|
return project.source == 'token'
|
|
|
|
$scope.ownerName = () ->
|
|
if $scope.project.accessLevel == "owner"
|
|
return "You"
|
|
else if $scope.project.owner?
|
|
return [$scope.project.owner.first_name, $scope.project.owner.last_name].filter((n) -> n?).join(" ")
|
|
else
|
|
return "None"
|
|
|
|
$scope.isOwner = () ->
|
|
window.user_id == $scope.project.owner._id
|
|
|
|
$scope.$watch "project.selected", (value) ->
|
|
if value?
|
|
$scope.updateSelectedProjects()
|
|
|
|
$scope.clone = (e) ->
|
|
e.stopPropagation()
|
|
$scope.project.isTableActionInflight = true
|
|
$scope.cloneProject($scope.project, "#{$scope.project.name} (Copy)")
|
|
.then () -> $scope.project.isTableActionInflight = false
|
|
.catch (response) ->
|
|
{ data, status } = response
|
|
error = if status == 400 then message: data else true
|
|
modalInstance = $modal.open(
|
|
templateUrl: "showErrorModalTemplate"
|
|
controller: "ShowErrorModalController"
|
|
resolve:
|
|
error: () -> error
|
|
)
|
|
$scope.project.isTableActionInflight = false
|
|
|
|
$scope.download = (e) ->
|
|
e.stopPropagation()
|
|
$scope.downloadProjectsById([$scope.project.id])
|
|
|
|
$scope.archiveOrLeave = (e) ->
|
|
e.stopPropagation()
|
|
$scope.archiveOrLeaveProjects([$scope.project])
|
|
|
|
$scope.restore = (e) ->
|
|
e.stopPropagation()
|
|
$scope.restoreProjects([$scope.project])
|
|
|
|
$scope.deleteProject = (e) ->
|
|
e.stopPropagation()
|
|
modalInstance = $modal.open(
|
|
templateUrl: "deleteProjectsModalTemplate"
|
|
controller: "DeleteProjectsModalController"
|
|
resolve:
|
|
projects: () -> [ $scope.project ]
|
|
)
|
|
|
|
modalInstance.result.then () ->
|
|
$scope.project.isTableActionInflight = true
|
|
queuedHttp({
|
|
method: "DELETE"
|
|
url: "/project/#{$scope.project.id}?forever=true"
|
|
headers:
|
|
"X-CSRF-Token": window.csrfToken
|
|
}).then () ->
|
|
$scope.project.isTableActionInflight = false
|
|
$scope._removeProjectFromList $scope.project
|
|
for tag in $scope.tags
|
|
$scope._removeProjectIdsFromTagArray(tag, [ $scope.project.id ])
|
|
$scope.updateVisibleProjects()
|
|
.catch () ->
|
|
$scope.project.isTableActionInflight = false
|