diff --git a/src/components/AssetsLibrary/FilesGridView.jsx b/src/components/AssetsLibrary/FilesGridView.jsx
index 2618fab04..a5ea04d09 100644
--- a/src/components/AssetsLibrary/FilesGridView.jsx
+++ b/src/components/AssetsLibrary/FilesGridView.jsx
@@ -10,6 +10,7 @@ import DeleteFileLinkModal from '../LinksMenu/DeleteFileLinkModal'
import EditFileAttachment from '../LinksMenu/EditFileAttachment'
import SubFolder from './SubFolder'
import ItemOperations from './ItemOperations'
+import UserTooltip from '../User/UserTooltip'
import FolderIcon from '../../assets/icons/v.2.5/icon-folder-small.svg'
@@ -28,6 +29,7 @@ const FilesGridView = ({
title,
selectedUsers,
onAddAttachment,
+ assetsMembers,
isSharingAttachment,
discardAttachments,
onChangePermissions,
@@ -78,6 +80,7 @@ const FilesGridView = ({
link={ subFolderContent }
renderLink={ renderLink }
goBack={goBack}
+ assetsMembers={assetsMembers}
onDeletePostAttachment={onDeletePostAttachment}
loggedInUser={loggedInUser}
formatModifyDate={formatModifyDate}
@@ -99,6 +102,7 @@ const FilesGridView = ({
Type
Name
+ Created By
Modified
@@ -119,12 +123,14 @@ const FilesGridView = ({
const canEdit = `${link.createdBy}` === `${loggedInUser.userId}`
const changeSubFolder = () => onChangeSubFolder(link)
+ const owner = _.find(assetsMembers, m => m.userId === _.parseInt(link.createdBy))
if (Array.isArray(link.children) && link.children.length > 0) {
return (
{formatFolderTitle(link.title)}
+ —
{formatModifyDate(link)}
)
@@ -161,6 +167,15 @@ const FilesGridView = ({
+
+ {!owner && (
Unknown
)}
+ {owner && (
+
)}
+
{formatModifyDate(link)}
{canEdit && (
diff --git a/src/components/AssetsLibrary/GridView.scss b/src/components/AssetsLibrary/GridView.scss
index 7ab34c62b..638539628 100644
--- a/src/components/AssetsLibrary/GridView.scss
+++ b/src/components/AssetsLibrary/GridView.scss
@@ -85,7 +85,7 @@
}
}
-.item-modified {
+.item-modified, .item-created-by {
-webkit-box-flex: 0;
-ms-flex: none;
flex: none;
@@ -134,4 +134,11 @@
.assets-gridview-container-active {
position: relative;
+}
+
+:global {
+ .user-block .tooltip-container {
+ text-align: left;
+ line-height: 20px;
+ }
}
\ No newline at end of file
diff --git a/src/components/AssetsLibrary/LinksGridView.jsx b/src/components/AssetsLibrary/LinksGridView.jsx
index 25fd4b45d..8f211347a 100644
--- a/src/components/AssetsLibrary/LinksGridView.jsx
+++ b/src/components/AssetsLibrary/LinksGridView.jsx
@@ -8,6 +8,7 @@ import DeleteLinkModal from '../LinksMenu/DeleteLinkModal'
import EditLinkModal from '../LinksMenu/EditLinkModal'
import SubFolder from './SubFolder'
import ItemOperations from './ItemOperations'
+import UserTooltip from '../User/UserTooltip'
import FolderIcon from '../../assets/icons/v.2.5/icon-folder-small.svg'
import LinkIcon from '../../assets/icons/link-12.svg'
@@ -28,6 +29,7 @@ const LinksGridView = ({
title,
formatModifyDate,
formatFolderTitle,
+ assetsMembers,
}) => {
const renderLink = (link) => {
if (link.onClick) {
@@ -59,6 +61,7 @@ const LinksGridView = ({
renderLink={ renderLink }
goBack={goBack}
formatModifyDate={formatModifyDate}
+ assetsMembers={assetsMembers}
isLinkSubFolder
/>)}
{(!subFolderContent) && (
@@ -69,6 +72,7 @@ const LinksGridView = ({
Type
Name
+ Created By
Modified
@@ -87,12 +91,14 @@ const LinksGridView = ({
const onEditCancel = () => onEditIntent(-1)
const handleEditClick = () => onEditIntent(idx)
const changeSubFolder = () => onChangeSubFolder(link)
+ const owner = _.find(assetsMembers, m => m.userId === _.parseInt(link.createdBy))
if (Array.isArray(link.children) && link.children.length > 0) {
return (
{formatFolderTitle(link.title)}
+ —
{formatModifyDate(link)}
)
@@ -119,6 +125,16 @@ const LinksGridView = ({
+
+ {!owner && !link.createdBy && (
—
)}
+ {!owner && link.createdBy && (
Unknown
)}
+ {owner && (
+
)}
+
{formatModifyDate(link)}
{(canEdit || canDelete) && (
diff --git a/src/components/AssetsLibrary/SubFolder.jsx b/src/components/AssetsLibrary/SubFolder.jsx
index e8680cbe4..a23cac3ba 100644
--- a/src/components/AssetsLibrary/SubFolder.jsx
+++ b/src/components/AssetsLibrary/SubFolder.jsx
@@ -1,9 +1,11 @@
import React from 'react'
import PropTypes from 'prop-types'
+import _ from 'lodash'
import cn from 'classnames'
import DeleteFileLinkModal from '../LinksMenu/DeleteFileLinkModal'
import ItemOperations from './ItemOperations'
+import UserTooltip from '../User/UserTooltip'
import FolderIcon from '../../assets/icons/v.2.5/icon-folder-small.svg'
import './GridView.scss'
@@ -54,7 +56,7 @@ class SubFolder extends React.Component {
}
render() {
- const { link, renderLink, goBack, formatModifyDate, isLinkSubFolder } = this.props
+ const { link, renderLink, goBack, formatModifyDate, isLinkSubFolder, assetsMembers } = this.props
const { linkToDelete } = this.state
return (
= 0)}, '')}>
@@ -64,17 +66,20 @@ class SubFolder extends React.Component {
Type
Name
+ Created By
Modified
..
+
{
link.children.map((childLink, i) => {
+ const owner = _.find(assetsMembers, m => m.userId === _.parseInt(childLink.createdBy))
if (linkToDelete === i) {
return (
@@ -100,6 +105,16 @@ class SubFolder extends React.Component {
return (
+
+ {!owner && childLink.createdBy !== 'CoderBot' && (
Unknown
)}
+ {!owner && childLink.createdBy === 'CoderBot' && (
CoderBot
)}
+ {owner && (
+
)}
+
{formatModifyDate(childLink)}
{childLink.deletable && this.hasAccess(childLink.createdBy) && (
diff --git a/src/projects/detail/containers/AssetsInfoContainer.jsx b/src/projects/detail/containers/AssetsInfoContainer.jsx
index b0a19d47c..6ee6fc61a 100644
--- a/src/projects/detail/containers/AssetsInfoContainer.jsx
+++ b/src/projects/detail/containers/AssetsInfoContainer.jsx
@@ -10,6 +10,7 @@ import LinksGridView from '../../../components/AssetsLibrary/LinksGridView'
import FilesGridView from '../../../components/AssetsLibrary/FilesGridView'
import AssetsStatistics from '../../../components/AssetsLibrary/AssetsStatistics'
import { updateProject, deleteProject } from '../../actions/project'
+import { loadMembers } from '../../../actions/members'
import { loadDashboardFeeds, loadProjectMessages } from '../../actions/projectTopics'
import { loadTopic } from '../../../actions/topics'
import { loadProjectPlan } from '../../actions/projectPlan'
@@ -68,6 +69,7 @@ class AssetsInfoContainer extends React.Component {
!_.isEqual(nextProps.attachmentsAwaitingPermission, this.props.attachmentsAwaitingPermission) ||
!_.isEqual(nextProps.attachmentPermissions, this.props.attachmentPermissions) ||
!_.isEqual(nextProps.isSharingAttachment, this.props.isSharingAttachment) ||
+ !_.isEqual(nextProps.assetsMembers, this.props.assetsMembers) ||
!_.isEqual(nextState.activeAssetsType, this.state.activeAssetsType) ||
!_.isEqual(nextState.ifModalOpen, this.state.ifModalOpen)
}
@@ -167,7 +169,11 @@ class AssetsInfoContainer extends React.Component {
}
onAddNewLink(link) {
- const { updateProject, project } = this.props
+ const { updateProject, project, loggedInUser } = this.props
+ link.createdAt = moment().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]')
+ link.createdBy = loggedInUser.userId
+ link.updatedAt = moment().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]')
+ link.updatedBy = loggedInUser.userId
updateProject(project.id, {
bookmarks: update(project.bookmarks || [], { $push: [link] })
})
@@ -181,10 +187,14 @@ class AssetsInfoContainer extends React.Component {
}
onEditLink(idx, title, address) {
- const { updateProject, project } = this.props
+ const { updateProject, project, loggedInUser } = this.props
+ const updatedAt = moment().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]')
+ const updatedBy = loggedInUser.userId
const updatedLink = {
title,
- address
+ address,
+ updatedAt,
+ updatedBy
}
updateProject(project.id, {
bookmarks: update(project.bookmarks, { $splice: [[idx, 1, updatedLink]] })
@@ -223,7 +233,7 @@ class AssetsInfoContainer extends React.Component {
this.props.uploadProjectAttachments(project.id, attachment)
}
- extractHtmlLink(str) {
+ extractHtmlLink(str, userId) {
const links = []
const regex = /
]+href="(.*?)"[^>]*>([\s\S]*?)<\/a>/gm
const urlRegex = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/gm // eslint-disable-line no-useless-escape
@@ -238,7 +248,8 @@ class AssetsInfoContainer extends React.Component {
if (urlRegex.test(address)) {
links.push({
title,
- address
+ address,
+ createdBy: userId
})
}
@@ -249,7 +260,7 @@ class AssetsInfoContainer extends React.Component {
return links
}
- extractMarkdownLink(str) {
+ extractMarkdownLink(str, userId) {
const links = []
const regex = /(?:__|[*#])|\[(.*?)\]\((.*?)\)/gm
const urlRegex = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/gm // eslint-disable-line no-useless-escape
@@ -264,7 +275,8 @@ class AssetsInfoContainer extends React.Component {
if (urlRegex.test(address)) {
links.push({
title,
- address
+ address,
+ createdBy: userId
})
}
@@ -275,7 +287,7 @@ class AssetsInfoContainer extends React.Component {
return links
}
- extractRawLink(str) {
+ extractRawLink(str, userId) {
let links = []
const regex = /(\s|^)(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,}[\s])(\s|$)/igm // eslint-disable-line no-useless-escape
const rawLinks = str.match(regex)
@@ -289,7 +301,8 @@ class AssetsInfoContainer extends React.Component {
return {
title: name,
- address: url
+ address: url,
+ createdBy: userId
}
})
}
@@ -303,9 +316,9 @@ class AssetsInfoContainer extends React.Component {
let childrenLinks = []
feed.posts.forEach(post => {
childrenLinks = childrenLinks.concat([
- ...this.extractHtmlLink(post.rawContent),
- ...this.extractMarkdownLink(post.rawContent),
- ...this.extractRawLink(post.rawContent)
+ ...this.extractHtmlLink(post.rawContent, post.userId),
+ ...this.extractMarkdownLink(post.rawContent, post.userId),
+ ...this.extractRawLink(post.rawContent, post.userId)
])
})
@@ -382,22 +395,9 @@ class AssetsInfoContainer extends React.Component {
}
}
- render() {
- const { project, currentMemberRole, isSuperUser, phases, feeds,
- isManageUser, phasesTopics, projectTemplates, hideLinks,
- attachmentsAwaitingPermission, addProjectAttachment, discardAttachments, attachmentPermissions,
- changeAttachmentPermission, projectMembers, loggedInUser, isSharingAttachment, canAccessPrivatePosts } = this.props
- const { ifModalOpen } = this.state
-
- const canManageLinks = !!currentMemberRole || isSuperUser
-
- let devices = []
- const primaryTarget = _.get(project, 'details.appDefinition.primaryTarget')
- if (primaryTarget && !primaryTarget.seeAttached) {
- devices.push(primaryTarget.value)
- } else {
- devices = _.get(project, 'details.devices', [])
- }
+ getLinksAndAttachments() {
+ const { project, isSuperUser, phases, feeds,
+ isManageUser, phasesTopics, canAccessPrivatePosts } = this.props
let attachments = project.attachments
// merges the product attachments to show in the links menu
@@ -444,16 +444,6 @@ class AssetsInfoContainer extends React.Component {
})
)
- const attachmentsStorePath = `${PROJECT_ATTACHMENTS_FOLDER}/${project.id}/`
- let enableFileUpload = true
- if(project.version !== 'v2') {
- const templateId = _.get(project, 'templateId')
- const projectTemplate = _.find(projectTemplates, template => template.id === templateId)
- enableFileUpload = _.some(projectTemplate.scope.sections, section => {
- return _.some(section.subSections, subSection => subSection.id === 'files')
- })
- }
-
// extract links from posts
const topicLinks = this.extractLinksFromPosts(feeds)
const publicTopicLinks = topicLinks.filter(link => link.tag !== PROJECT_FEED_TYPE_MESSAGES)
@@ -475,6 +465,65 @@ class AssetsInfoContainer extends React.Component {
...this.extractAttachmentLinksFromPosts(phaseFeeds)
]
+ return ({
+ links,
+ attachments,
+ })
+ }
+
+ componentDidMount() {
+ const {loadMembers } = this.props
+ const {links, attachments} = this.getLinksAndAttachments()
+
+ let tmpUserIds = []
+ let userIds = []
+ _.forEach(links, link => {
+ tmpUserIds = _.union(tmpUserIds, _.map(link.children, 'createdBy'))
+ tmpUserIds = _.union(tmpUserIds, [link.createdBy])
+ tmpUserIds = _.union(tmpUserIds, [link.updatedBy])
+ })
+
+ _.forEach(attachments, attachment => {
+ tmpUserIds = _.union(tmpUserIds, _.map(attachment.children, 'createdBy'))
+ tmpUserIds = _.union(tmpUserIds, [attachment.createdBy])
+ })
+
+ _.forEach(tmpUserIds, userId => {
+ userIds = _.union(userIds, [_.parseInt(userId)])
+ })
+ _.remove(userIds, i => !i)
+
+ loadMembers(userIds)
+ }
+
+ render() {
+ const { project, currentMemberRole, isSuperUser, projectTemplates, hideLinks,
+ attachmentsAwaitingPermission, addProjectAttachment, discardAttachments, attachmentPermissions,
+ changeAttachmentPermission, projectMembers, loggedInUser, isSharingAttachment, assetsMembers } = this.props
+ const { ifModalOpen } = this.state
+
+ const canManageLinks = !!currentMemberRole || isSuperUser
+
+ let devices = []
+ const primaryTarget = _.get(project, 'details.appDefinition.primaryTarget')
+ if (primaryTarget && !primaryTarget.seeAttached) {
+ devices.push(primaryTarget.value)
+ } else {
+ devices = _.get(project, 'details.devices', [])
+ }
+
+ const {links, attachments} = this.getLinksAndAttachments()
+
+ const attachmentsStorePath = `${PROJECT_ATTACHMENTS_FOLDER}/${project.id}/`
+ let enableFileUpload = true
+ if(project.version !== 'v2') {
+ const templateId = _.get(project, 'templateId')
+ const projectTemplate = _.find(projectTemplates, template => template.id === templateId)
+ enableFileUpload = _.some(projectTemplate.scope.sections, section => {
+ return _.some(section.subSections, subSection => subSection.id === 'files')
+ })
+ }
+
const assetsData = []
enableFileUpload && assetsData.push({name: 'Files', total: _.toString(attachments.length)})
!hideLinks && assetsData.push({name: 'Links', total: _.toString(links.length)})
@@ -597,6 +646,7 @@ class AssetsInfoContainer extends React.Component {
onChangePermissions={changeAttachmentPermission}
selectedUsers={attachmentPermissions}
projectMembers={projectMembers}
+ assetsMembers={assetsMembers}
pendingAttachments={attachmentsAwaitingPermission}
loggedInUser={loggedInUser}
attachmentsStorePath={attachmentsStorePath}
@@ -607,6 +657,7 @@ class AssetsInfoContainer extends React.Component {
{(!hideLinks && activeAssetsType === 'Links') &&
{
attachmentPermissions: projectState.attachmentPermissions,
isSharingAttachment: projectState.processingAttachments,
projectMembers: _.keyBy(projectMembers, 'userId'),
+ assetsMembers: _.keyBy(members.members, 'userId'),
loggedInUser: loadUser.user,
canAccessPrivatePosts
})
}
-const mapDispatchToProps = { updateProject, deleteProject, addProjectAttachment, updateProjectAttachment,
+const mapDispatchToProps = { updateProject, deleteProject, loadMembers, addProjectAttachment, updateProjectAttachment,
loadProjectMessages, discardAttachments, uploadProjectAttachments, loadDashboardFeeds, loadTopic, changeAttachmentPermission,
removeProjectAttachment, loadProjectPlan, saveFeedComment }
diff --git a/src/projects/reducers/project.js b/src/projects/reducers/project.js
index 6abe03f07..8b7e28993 100644
--- a/src/projects/reducers/project.js
+++ b/src/projects/reducers/project.js
@@ -40,6 +40,7 @@ const initialState = {
project: {
invites: [] // invites are pushed directly into it hence need to declare first
},
+ assetsMembers: {},
projectNonDirty: {},
updateExisting: false,
phases: null,