mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-11 03:41:12 +00:00
[Activities] mobile: redesigned it (#3913)
This commit is contained in:
@@ -1,16 +1,20 @@
|
|||||||
table tr .activity-date {
|
.activity-details {
|
||||||
border-top: 0;
|
|
||||||
}
|
|
||||||
table .activity-detail {
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
table .activity-detail div {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
table .activity-detail span {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
table .activity-detail span:hover {
|
|
||||||
color: #333;
|
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.activity-details:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.mobile-activity-op {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 0 .2em .8em;
|
||||||
|
padding: 0 .5em;
|
||||||
|
background: #ffbd6f;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
.mobile-activity-time {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: .2em;
|
||||||
}
|
}
|
@@ -8,6 +8,7 @@ import Loading from '../../components/loading';
|
|||||||
import Activity from '../../models/activity';
|
import Activity from '../../models/activity';
|
||||||
import ListCreatedFileDialog from '../../components/dialog/list-created-files-dialog';
|
import ListCreatedFileDialog from '../../components/dialog/list-created-files-dialog';
|
||||||
import ModalPortal from '../../components/modal-portal';
|
import ModalPortal from '../../components/modal-portal';
|
||||||
|
|
||||||
import '../../css/files-activities.css';
|
import '../../css/files-activities.css';
|
||||||
|
|
||||||
moment.locale(window.app.config.lang);
|
moment.locale(window.app.config.lang);
|
||||||
@@ -17,20 +18,39 @@ const contentPropTypes = {
|
|||||||
items: PropTypes.array.isRequired,
|
items: PropTypes.array.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isDesktop = window.innerWidth >= 768;
|
||||||
|
|
||||||
class FileActivitiesContent extends Component {
|
class FileActivitiesContent extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let { items, isLoadingMore } = this.props;
|
let { items, isLoadingMore } = this.props;
|
||||||
|
|
||||||
|
const desktopThead = (
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="8%">{/* avatar */}</th>
|
||||||
|
<th width="15%">{gettext('User')}</th>
|
||||||
|
<th width="20%">{gettext('Operation')}</th>
|
||||||
|
<th width="37%">{gettext('File')} / {gettext('Library')}</th>
|
||||||
|
<th width="20%">{gettext('Time')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
);
|
||||||
|
|
||||||
|
const mobileThead = (
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="15%"></th>
|
||||||
|
<th width="53%"></th>
|
||||||
|
<th width="32%"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<table width="100%" className="table table-hover table-vcenter">
|
<table className="table-hover table-thead-hidden">
|
||||||
<colgroup>
|
{isDesktop ? desktopThead : mobileThead}
|
||||||
<col width="8%" />
|
|
||||||
<col width="15%" />
|
|
||||||
<col width="20%" />
|
|
||||||
<col width="37%" />
|
|
||||||
<col width="20%" />
|
|
||||||
</colgroup>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{items.map((item, index) => {
|
{items.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
@@ -75,7 +95,7 @@ class ActivityItem extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
let {item, index, items} = this.props;
|
let {item, index, items} = this.props;
|
||||||
let op, details;
|
let op, details, moreDetails = false;
|
||||||
let userProfileURL = `${siteRoot}profile/${encodeURIComponent(item.author_email)}/`;
|
let userProfileURL = `${siteRoot}profile/${encodeURIComponent(item.author_email)}/`;
|
||||||
|
|
||||||
let libURL = siteRoot + 'library/' + item.repo_id + '/' + encodeURIComponent(item.repo_name) + '/';
|
let libURL = siteRoot + 'library/' + item.repo_id + '/' + encodeURIComponent(item.repo_name) + '/';
|
||||||
@@ -86,34 +106,36 @@ class ActivityItem extends Component {
|
|||||||
switch(item.op_type) {
|
switch(item.op_type) {
|
||||||
case 'create':
|
case 'create':
|
||||||
op = gettext('Created library');
|
op = gettext('Created library');
|
||||||
details = <td>{libLink}</td>;
|
details = libLink;
|
||||||
break;
|
break;
|
||||||
case 'rename':
|
case 'rename':
|
||||||
op = gettext('Renamed library');
|
op = gettext('Renamed library');
|
||||||
details = <td>{item.old_repo_name} => {libLink}</td>;
|
details = <span>{item.old_repo_name} => {libLink}</span>;
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
op = gettext('Deleted library');
|
op = gettext('Deleted library');
|
||||||
details = <td>{item.repo_name}</td>;
|
details = item.repo_name;
|
||||||
break;
|
break;
|
||||||
case 'recover':
|
case 'recover':
|
||||||
op = gettext('Restored library');
|
op = gettext('Restored library');
|
||||||
details = <td>{libLink}</td>;
|
details = libLink;
|
||||||
break;
|
break;
|
||||||
case 'clean-up-trash':
|
case 'clean-up-trash':
|
||||||
if (item.days == 0) {
|
|
||||||
details = <td>{gettext('Removed all items from trash.')}<br />{libLink}</td>;
|
|
||||||
} else {
|
|
||||||
details = <td>{gettext('Removed items older than {n} days from trash.').replace('{n}', item.days)}<br />{libLink}</td>;
|
|
||||||
}
|
|
||||||
op = gettext('Cleaned trash');
|
op = gettext('Cleaned trash');
|
||||||
|
if (item.days == 0) {
|
||||||
|
details = gettext('Removed all items from trash.');
|
||||||
|
} else {
|
||||||
|
details = gettext('Removed items older than {n} days from trash.').replace('{n}', item.days);
|
||||||
|
}
|
||||||
|
moreDetails = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (item.obj_type == 'draft') {
|
} else if (item.obj_type == 'draft') {
|
||||||
let fileURL = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
|
let fileURL = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
|
||||||
let fileLink = <a href={fileURL} target="_blank">{item.name}</a>;
|
let fileLink = <a href={fileURL} target="_blank">{item.name}</a>;
|
||||||
op = gettext('Publish draft');
|
op = gettext('Publish draft');
|
||||||
details = <td>{fileLink}<br />{smallLibLink}</td>;
|
details = fileLink;
|
||||||
|
moreDetails = true;
|
||||||
} else if (item.obj_type == 'files') {
|
} else if (item.obj_type == 'files') {
|
||||||
let fileURL = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
|
let fileURL = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
|
||||||
if (item.name.endsWith('(draft).md')) {
|
if (item.name.endsWith('(draft).md')) {
|
||||||
@@ -124,65 +146,56 @@ class ActivityItem extends Component {
|
|||||||
fileLink = item.name;
|
fileLink = item.name;
|
||||||
}
|
}
|
||||||
let fileCount = item.createdFilesCount - 1;
|
let fileCount = item.createdFilesCount - 1;
|
||||||
let firstLine = gettext('{file} and {n} other files');
|
let firstLine = gettext('{file} and {n} other files')
|
||||||
firstLine = firstLine.replace('{file}', fileLink);
|
.replace('{file}', fileLink)
|
||||||
firstLine = firstLine.replace('{n}', fileCount);
|
.replace('{n}', fileCount);
|
||||||
op = gettext('Created {n} files').replace('{n}', item.createdFilesCount);
|
op = gettext('Created {n} files').replace('{n}', item.createdFilesCount);
|
||||||
details =
|
details = (
|
||||||
<td className="activity-detail">
|
<Fragment>
|
||||||
<div dangerouslySetInnerHTML={{__html: firstLine}}></div>{' '}
|
<p className="m-0 d-inline" dangerouslySetInnerHTML={{__html: firstLine}}></p>
|
||||||
<span onClick={this.onListCreatedFilesToggle} className="cursor-pointer">{gettext('details')}</span>
|
{isDesktop && <span onClick={this.onListCreatedFilesToggle} className="activity-details text-secondary ml-2">{gettext('details')}</span>}
|
||||||
<br />{smallLibLink}
|
</Fragment>
|
||||||
</td>;
|
);
|
||||||
|
moreDetails = true;
|
||||||
} else if (item.obj_type == 'file') {
|
} else if (item.obj_type == 'file') {
|
||||||
let fileURL = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
|
const isDraft = item.name.endsWith('(draft).md');
|
||||||
if (item.name.endsWith('(draft).md')) {
|
const fileURL = isDraft ? serviceURL + '/drafts/' + item.draft_id + '/' :
|
||||||
fileURL = serviceURL + '/drafts/' + item.draft_id + '/';
|
`${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
|
||||||
}
|
|
||||||
let fileLink = <a href={fileURL} target="_blank">{item.name}</a>;
|
let fileLink = <a href={fileURL} target="_blank">{item.name}</a>;
|
||||||
if (item.name.endsWith('(draft).md') && !item.draft_id) {
|
if (isDraft && !item.draft_id) {
|
||||||
fileLink = item.name;
|
fileLink = item.name;
|
||||||
}
|
}
|
||||||
switch (item.op_type) {
|
switch (item.op_type) {
|
||||||
case 'create':
|
case 'create':
|
||||||
if (item.name.endsWith('(draft).md')) {
|
op = isDraft ? gettext('Created draft') : gettext('Created file');
|
||||||
op = gettext('Created draft');
|
details = fileLink;
|
||||||
details = <td>{fileLink}<br />{smallLibLink}</td>;
|
moreDetails = true;
|
||||||
break;
|
|
||||||
}
|
|
||||||
op = gettext('Created file');
|
|
||||||
details = <td>{fileLink}<br />{smallLibLink}</td>;
|
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
if (item.name.endsWith('(draft).md')) {
|
op = isDraft ? gettext('Deleted draft') : gettext('Deleted file');
|
||||||
op = gettext('Deleted draft');
|
details = item.name;
|
||||||
details = <td>{item.name}<br />{smallLibLink}</td>;
|
moreDetails = true;
|
||||||
break;
|
|
||||||
}
|
|
||||||
op = gettext('Deleted file');
|
|
||||||
details = <td>{item.name}<br />{smallLibLink}</td>;
|
|
||||||
break;
|
break;
|
||||||
case 'recover':
|
case 'recover':
|
||||||
op = gettext('Restored file');
|
op = gettext('Restored file');
|
||||||
details = <td>{fileLink}<br />{smallLibLink}</td>;
|
details = fileLink;
|
||||||
|
moreDetails = true;
|
||||||
break;
|
break;
|
||||||
case 'rename':
|
case 'rename':
|
||||||
op = gettext('Renamed file');
|
op = gettext('Renamed file');
|
||||||
details = <td>{item.old_name} => {fileLink}<br />{smallLibLink}</td>;
|
details = <span>{item.old_name} => {fileLink}</span>;
|
||||||
|
moreDetails = true;
|
||||||
break;
|
break;
|
||||||
case 'move':
|
case 'move':
|
||||||
var filePathLink = <a href={fileURL}>{item.path}</a>;
|
const filePathLink = <a href={fileURL}>{item.path}</a>;
|
||||||
op = gettext('Moved file');
|
op = gettext('Moved file');
|
||||||
details = <td>{item.old_path} => {filePathLink}<br />{smallLibLink}</td>;
|
details = <span>{item.old_path} => {filePathLink}</span>;
|
||||||
|
moreDetails = true;
|
||||||
break;
|
break;
|
||||||
case 'edit': // update
|
case 'edit': // update
|
||||||
if (item.name.endsWith('(draft).md')) {
|
op = isDraft ? gettext('Updated draft') : gettext('Updated file');
|
||||||
op = gettext('Updated draft');
|
details = fileLink;
|
||||||
details = <td>{fileLink}<br />{smallLibLink}</td>;
|
moreDetails = true;
|
||||||
break;
|
|
||||||
}
|
|
||||||
op = gettext('Updated file');
|
|
||||||
details = <td>{fileLink}<br />{smallLibLink}</td>;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else { // dir
|
} else { // dir
|
||||||
@@ -191,24 +204,29 @@ class ActivityItem extends Component {
|
|||||||
switch (item.op_type) {
|
switch (item.op_type) {
|
||||||
case 'create':
|
case 'create':
|
||||||
op = gettext('Created folder');
|
op = gettext('Created folder');
|
||||||
details = <td>{dirLink}<br />{smallLibLink}</td>;
|
details = dirLink;
|
||||||
|
moreDetails = true;
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
op = gettext('Deleted folder');
|
op = gettext('Deleted folder');
|
||||||
details = <td>{item.name}<br />{smallLibLink}</td>;
|
details = item.name;
|
||||||
|
moreDetails = true;
|
||||||
break;
|
break;
|
||||||
case 'recover':
|
case 'recover':
|
||||||
op = gettext('Restored folder');
|
op = gettext('Restored folder');
|
||||||
details = <td>{dirLink}<br />{smallLibLink}</td>;
|
details = dirLink;
|
||||||
|
moreDetails = true;
|
||||||
break;
|
break;
|
||||||
case 'rename':
|
case 'rename':
|
||||||
op = gettext('Renamed folder');
|
op = gettext('Renamed folder');
|
||||||
details = <td>{item.old_name} => {dirLink}<br />{smallLibLink}</td>;
|
details = <span>{item.old_name} => {dirLink}</span>;
|
||||||
|
moreDetails = true;
|
||||||
break;
|
break;
|
||||||
case 'move':
|
case 'move':
|
||||||
var dirPathLink = <a href={dirURL}>{item.path}</a>;
|
const dirPathLink = <a href={dirURL}>{item.path}</a>;
|
||||||
op = gettext('Moved folder');
|
op = gettext('Moved folder');
|
||||||
details = <td>{item.old_path} => {dirPathLink}<br />{smallLibLink}</td>;
|
details = <span>{item.old_path} => {dirPathLink}</span>;
|
||||||
|
moreDetails = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,22 +241,47 @@ class ActivityItem extends Component {
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
{isShowDate &&
|
{isShowDate &&
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan='5' className="activity-date">{moment(item.time).format('YYYY-MM-DD')}</td>
|
<td colSpan={isDesktop ? 5 : 3} className="border-top-0">{moment(item.time).format('YYYY-MM-DD')}</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
{isDesktop ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td className="text-center">
|
<td className="text-center">
|
||||||
<img src={item.avatar_url} alt="" width="36px" height="36px" className="avatar" />
|
<img src={item.avatar_url} alt="" width="32" height="32" className="avatar" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href={userProfileURL}>{item.author_name}</a>
|
<a href={userProfileURL}>{item.author_name}</a>
|
||||||
</td>
|
</td>
|
||||||
<td><span className="activity-op">{op}</span></td>
|
<td>{op}</td>
|
||||||
|
<td>
|
||||||
{details}
|
{details}
|
||||||
|
{moreDetails && <br /> }
|
||||||
|
{moreDetails && smallLibLink}
|
||||||
|
</td>
|
||||||
<td className="text-secondary">
|
<td className="text-secondary">
|
||||||
<time datetime={item.time} is="relative-time" title={moment(item.time).format('llll')}>{moment(item.time).fromNow()}</time>
|
<time datetime={item.time} is="relative-time" title={moment(item.time).format('llll')}>{moment(item.time).fromNow()}</time>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td className="text-center">
|
||||||
|
<img src={item.avatar_url} alt="" width="32" height="32" className="avatar" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href={userProfileURL}>{item.author_name}</a>
|
||||||
|
<span className="mobile-activity-op">{op}</span>
|
||||||
|
<br />{details}
|
||||||
|
</td>
|
||||||
|
<td className="text-right align-top">
|
||||||
|
<span className="text-secondary mobile-activity-time">
|
||||||
|
<time datetime={item.time} is="relative-time" title={moment(item.time).format('llll')}>{moment(item.time).fromNow()}</time>
|
||||||
|
</span>
|
||||||
|
{moreDetails && <br /> }
|
||||||
|
{moreDetails && libLink}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
)}
|
||||||
{this.state.isListCreatedFiles &&
|
{this.state.isListCreatedFiles &&
|
||||||
<ModalPortal>
|
<ModalPortal>
|
||||||
<ListCreatedFileDialog
|
<ListCreatedFileDialog
|
||||||
|
Reference in New Issue
Block a user