1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-17 15:53:28 +00:00

Merge branch '7.0'

This commit is contained in:
plt
2019-07-22 20:39:11 +08:00
9 changed files with 165 additions and 40 deletions

View File

@@ -39,11 +39,11 @@ class InternalLinkDialog extends React.Component {
this.setState({
isOpen: true,
smartLink: res.data.smart_link
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
});
}
copyToClipBoard() {

View File

@@ -0,0 +1,61 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'reactstrap';
import toaster from '../toast';
import copy from '@seafile/seafile-editor/dist/utils/copy-to-clipboard';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
const propTypes = {
path: PropTypes.string.isRequired,
repoID: PropTypes.string.isRequired,
};
class InternalLink extends React.Component {
constructor(props) {
super(props);
this.state = {
smartLink: '',
};
}
componentDidMount() {
let repoID = this.props.repoID;
let path = this.props.path;
seafileAPI.getInternalLink(repoID, path).then(res => {
this.setState({
smartLink: res.data.smart_link
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
copyToClipBoard = () => {
copy(this.state.smartLink);
let message = gettext('Internal link has been copied to clipboard');
toaster.success(message), {
duration: 2
};
}
render() {
return (
<div>
<p className="tip mb-1">
{gettext('An internal link is a link to a file or folder that can be accessed by users with read permission to the file or folder.')}
</p>
<p>
<a target="_blank" href={this.state.smartLink}>{this.state.smartLink}</a>
</p>
<Button onClick={this.copyToClipBoard} color="primary" className="mt-2">{gettext('Copy')}</Button>
</div>
);
}
}
InternalLink.propTypes = propTypes;
export default InternalLink;

View File

@@ -6,6 +6,7 @@ import ShareToUser from './share-to-user';
import ShareToGroup from './share-to-group';
import GenerateShareLink from './generate-share-link';
import GenerateUploadLink from './generate-upload-link';
import InternalLink from './internal-link';
import { seafileAPI } from '../../utils/seafile-api';
import Loading from '../loading';
import { Utils } from '../../utils/utils';
@@ -150,16 +151,22 @@ class ShareDialog extends React.Component {
}
renderFileContent = () => {
let activeTab = this.state.activeTab;
return (
<Fragment>
<div className="share-dialog-side">
<Nav pills vertical>
<NavItem>
<NavLink
className="active" onClick={() => {this.toggle.bind(this, 'shareLink');}}>
<NavLink className={activeTab === 'shareLink' ? 'active' : ''} onClick={(this.toggle.bind(this, 'shareLink'))}>
{gettext('Share Link')}
</NavLink>
</NavItem>
<NavItem>
<NavLink className={activeTab === 'internalLink' ? 'active' : ''} onClick={this.toggle.bind(this, 'internalLink')}>
{gettext('Internal Link')}
</NavLink>
</NavItem>
</Nav>
</div>
<div className="share-dialog-main">
@@ -171,6 +178,12 @@ class ShareDialog extends React.Component {
closeShareDialog={this.props.toggleDialog}
/>
</TabPane>
<TabPane tabId="internalLink">
<InternalLink
repoID={this.props.repoID}
path={this.props.itemPath}
/>
</TabPane>
</TabContent>
</div>
</Fragment>

View File

@@ -1,7 +1,8 @@
import React, { Component, Fragment } from 'react';
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
import PropTypes from 'prop-types';
import moment from 'moment';
import { siteRoot } from '../../utils/constants';
import { siteRoot, gettext } from '../../utils/constants';
// import { seafileAPI } from '../../utils/seafile-api';
// import Toast from '../toast';
import ModalPortal from '../modal-portal';
@@ -23,6 +24,7 @@ class WikiListItem extends Component {
constructor(props) {
super(props);
this.state = {
isOpMenuOpen: false, // for mobile
isShowDeleteDialog: false,
// isRenameing: false,
highlight: false,
@@ -30,6 +32,12 @@ class WikiListItem extends Component {
};
}
toggleOpMenu = () => {
this.setState({
isOpMenuOpen: !this.state.isOpMenuOpen
});
}
// clickMenuToggle = (e) => {
// e.preventDefault();
// this.onMenuToggle(e);
@@ -130,8 +138,7 @@ class WikiListItem extends Component {
let fileIconUrl = Utils.getDefaultLibIconUrl(false);
let deleteIcon = `action-icon sf2-icon-x3 ${this.state.highlight ? '' : 'invisible'}`;
return (
<Fragment>
const desktopItem = (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td><img src={fileIconUrl} width="24" alt="" /></td>
<td className="name">
@@ -147,6 +154,39 @@ class WikiListItem extends Component {
<span className={deleteIcon} onClick={this.onDeleteToggle}></span>
</td>
</tr>
);
const mobileItem = (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td><img src={fileIconUrl} width="24" alt="" /></td>
<td>
<a href={wiki.link}>{wiki.name}</a><br />
<a href={userProfileURL} target='_blank' className="item-meta-info">{wiki.owner_nickname}</a>
<span className="item-meta-info">{moment(wiki.updated_at).fromNow()}</span>
</td>
<td>
<Dropdown isOpen={this.state.isOpMenuOpen} toggle={this.toggleOpMenu}>
<DropdownToggle
tag="i"
className="sf-dropdown-toggle fa fa-ellipsis-v ml-0"
title={gettext('More Operations')}
data-toggle="dropdown"
aria-expanded={this.state.isOpMenuOpen}
/>
<div className={this.state.isOpMenuOpen ? '' : 'd-none'} onClick={this.toggleOpMenu}>
<div className="mobile-operation-menu-bg-layer"></div>
<div className="mobile-operation-menu">
<DropdownItem className="mobile-menu-item" onClick={this.onDeleteToggle}>{gettext('Unpublish')}</DropdownItem>
</div>
</div>
</Dropdown>
</td>
</tr>
);
return (
<Fragment>
{window.innerWidth >= 768 ? desktopItem : mobileItem}
{this.state.isShowDeleteDialog &&
<ModalPortal>
<WikiDeleteDialog

View File

@@ -1,8 +1,8 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import MediaQuery from 'react-responsive';
import { gettext } from '../../utils/constants';
import WikiListItem from './wiki-list-item';
import LibsMobileThead from '../libs-mobile-thead';
const propTypes = {
data: PropTypes.object.isRequired,
@@ -35,23 +35,20 @@ class WikiListView extends Component {
} else if (errorMsg) {
return <p className="error text-center">{errorMsg}</p>;
} else {
return (
<table>
const desktopThead = (
<thead>
<tr>
<MediaQuery query="(min-width: 768px)">
<th width="4%"></th>
<th width="36%">{gettext('Name')}</th>
</MediaQuery>
<MediaQuery query="(max-width: 767.8px)">
<th width="10%"></th>
<th width="30%">{gettext('Name')}</th>
</MediaQuery>
<th width="25%">{gettext('Owner')}</th>
<th width="25%">{gettext('Last Update')}</th>
<th width="10%">{/* operation */}</th>
</tr>
</thead>
);
return (
<table className={window.innerWidth >= 768 ? '' : 'table-thead-hidden'}>
{window.innerWidth >= 768 ? desktopThead : <LibsMobileThead />}
<tbody>
{wikis.map((wiki, index) => {
return (

View File

@@ -46,6 +46,7 @@
.seafile-comment-list .comment-vacant {
padding: 1em;
text-align: center;
list-style: none;
}
.seafile-comment-item {
padding: 15px 10px;

View File

@@ -4,6 +4,7 @@
position: relative;
display: flex;
flex: 1;
align-items: center;
}
.border-left-show:before {

View File

@@ -135,7 +135,7 @@ class Wikis extends Component {
<Button className="btn btn-secondary operation-item" onClick={this.onSelectToggle}>{gettext('Publish a Library')}</Button>
</MediaQuery>
<MediaQuery query="(max-width: 767.8px)">
<Button className="btn btn-secondary operation-item my-1" onClick={this.onSelectToggle}>{gettext('Publish a Library')}</Button>
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('Publish a Library')} onClick={this.onSelectToggle}></span>
</MediaQuery>
</Fragment>}
</div>

View File

@@ -2,6 +2,7 @@
import logging
import urllib
import hashlib
from urlparse import urlparse
from django import template
from django.core.urlresolvers import reverse
@@ -14,7 +15,7 @@ from seahub.avatar.settings import (AVATAR_GRAVATAR_BACKUP, AVATAR_GRAVATAR_DEFA
from seahub.avatar.util import get_primary_avatar, get_default_avatar_url, \
cache_result, get_default_avatar_non_registered_url
from seahub.utils import get_service_url
from seahub.settings import SITE_ROOT
from seahub.settings import SITE_ROOT, AVATAR_FILE_STORAGE
# Get an instance of a logger
logger = logging.getLogger(__name__)
@@ -38,6 +39,10 @@ def avatar_url(user, size=AVATAR_DEFAULT_SIZE):
else:
url = get_default_avatar_url()
# when store avatars in the media directory
if not AVATAR_FILE_STORAGE:
return url
if SITE_ROOT != '/':
return '/%s/%s' % (SITE_ROOT.strip('/'), url.strip('/'))
else:
@@ -48,6 +53,13 @@ def api_avatar_url(user, size=AVATAR_DEFAULT_SIZE):
service_url = get_service_url()
service_url = service_url.rstrip('/')
# when store avatars in the media directory
if not AVATAR_FILE_STORAGE:
# urlparse('https://192.157.12.3:89/demo')
# ParseResult(scheme='https', netloc='192.157.12.3:89', path='/demo', params='', query='', fragment='')
parse_result = urlparse(service_url)
service_url = '%s://%s' % (parse_result[0], parse_result[1])
avatar = get_primary_avatar(user, size=size)
if avatar:
url = avatar.avatar_url(size)