1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-26 07:22:34 +00:00
* update

* update

* Update utils.py

* Update utils.py

* add fontend code

* Update utils.py

* update

---------

Co-authored-by: 小强 <shuntian@Mac.lan>
This commit is contained in:
Ranjiwei
2025-08-28 17:14:22 +08:00
committed by GitHub
parent 32d0de784a
commit 1c58b515db
8 changed files with 104 additions and 11 deletions

View File

@@ -32,6 +32,12 @@ class CommentPanel extends React.Component {
this.toBeAddedParticipant = []; this.toBeAddedParticipant = [];
} }
forceUpdate = () => {
this.listComments();
this.getParticipants();
this.listRepoRelatedUsers();
};
listComments = () => { listComments = () => {
seafileAPI.listComments(repoID, fileUuid).then((res) => { seafileAPI.listComments(repoID, fileUuid).then((res) => {
this.setState({ this.setState({

View File

@@ -22,7 +22,8 @@ const propTypes = {
toggleCommentPanel: PropTypes.func.isRequired, toggleCommentPanel: PropTypes.func.isRequired,
toggleDetailsPanel: PropTypes.func.isRequired, toggleDetailsPanel: PropTypes.func.isRequired,
setImageScale: PropTypes.func, setImageScale: PropTypes.func,
rotateImage: PropTypes.func rotateImage: PropTypes.func,
isCommentUpdated: PropTypes.bool,
}; };
const { const {
@@ -103,7 +104,7 @@ class FileToolbar extends React.Component {
const { moreDropdownOpen } = this.state; const { moreDropdownOpen } = this.state;
const { isLocked, lockedByMe } = this.props; const { isLocked, lockedByMe, isCommentUpdated } = this.props;
let showLockUnlockBtn = false; let showLockUnlockBtn = false;
let lockUnlockText; let lockUnlockIcon; let lockUnlockText; let lockUnlockIcon;
if (canLockUnlockFile) { if (canLockUnlockFile) {
@@ -214,6 +215,7 @@ class FileToolbar extends React.Component {
aria-label={gettext('Comment')} aria-label={gettext('Comment')}
> >
<i className="sdocfont sdoc-comments"></i> <i className="sdocfont sdoc-comments"></i>
{isCommentUpdated && <span className='comment-tip'></span>}
</div> </div>
{showShareBtn && ( {showShareBtn && (
<IconButton <IconButton

View File

@@ -15,6 +15,7 @@ import OnlyofficeFileToolbar from './onlyoffice-file-toolbar';
import EmbeddedFileDetails from '../dirent-detail/embedded-file-details'; import EmbeddedFileDetails from '../dirent-detail/embedded-file-details';
import { MetadataMiddlewareProvider, MetadataStatusProvider } from '../../hooks'; import { MetadataMiddlewareProvider, MetadataStatusProvider } from '../../hooks';
import Loading from '../loading'; import Loading from '../loading';
import WebSocketClient from '../../utils/websocket-service';
import '../../css/file-view.css'; import '../../css/file-view.css';
@@ -29,7 +30,7 @@ const propTypes = {
}; };
const { isStarred, isLocked, lockedByMe, const { isStarred, isLocked, lockedByMe,
repoID, filePath, filePerm, enableWatermark, userNickName, repoID, fileUuid, filePath, filePerm, enableWatermark, userNickName,
fileName, repoEncrypted, isRepoAdmin, fileType fileName, repoEncrypted, isRepoAdmin, fileType
} = window.app.pageOptions; } = window.app.pageOptions;
@@ -44,8 +45,11 @@ class FileView extends React.Component {
lockedByMe: lockedByMe, lockedByMe: lockedByMe,
isCommentPanelOpen: false, isCommentPanelOpen: false,
isHeaderShown: (storedIsHeaderShown === null) || (storedIsHeaderShown == 'true'), isHeaderShown: (storedIsHeaderShown === null) || (storedIsHeaderShown == 'true'),
isDetailsPanelOpen: false isDetailsPanelOpen: false,
isCommentUpdated: false,
}; };
this.socketManager = new WebSocketClient(this.onMessageCallback, repoID);
} }
componentDidMount() { componentDidMount() {
@@ -53,10 +57,25 @@ class FileView extends React.Component {
document.getElementById('favicon').href = fileIcon; document.getElementById('favicon').href = fileIcon;
} }
onMessageCallback = (data) => {
const { type, content } = data;
if (type === 'comment-update') {
const { repo_id, file_uuid } = content;
if (repoID === repo_id && file_uuid === fileUuid) {
if (!this.state.isCommentPanelOpen) {
this.setState({ isCommentUpdated: true });
} else {
this.commentPanelRef.forceUpdate();
}
}
}
};
toggleCommentPanel = () => { toggleCommentPanel = () => {
this.setState({ this.setState({
isCommentPanelOpen: !this.state.isCommentPanelOpen, isCommentPanelOpen: !this.state.isCommentPanelOpen,
isDetailsPanelOpen: false, isDetailsPanelOpen: false,
isCommentUpdated: false,
}); });
}; };
@@ -121,6 +140,10 @@ class FileView extends React.Component {
}); });
}; };
setCommentPanelRef = (ref) => {
this.commentPanelRef = ref;
};
render() { render() {
const { isOnlyofficeFile = false } = this.props; const { isOnlyofficeFile = false } = this.props;
const { isDetailsPanelOpen, isHeaderShown } = this.state; const { isDetailsPanelOpen, isHeaderShown } = this.state;
@@ -142,6 +165,7 @@ class FileView extends React.Component {
/> />
{isOnlyofficeFile ? {isOnlyofficeFile ?
<OnlyofficeFileToolbar <OnlyofficeFileToolbar
isCommentUpdated={this.state.isCommentUpdated}
toggleDetailsPanel={this.toggleDetailsPanel} toggleDetailsPanel={this.toggleDetailsPanel}
toggleHeader={this.toggleHeader} toggleHeader={this.toggleHeader}
toggleCommentPanel={this.toggleCommentPanel} toggleCommentPanel={this.toggleCommentPanel}
@@ -149,6 +173,7 @@ class FileView extends React.Component {
<FileToolbar <FileToolbar
isLocked={this.state.isLocked} isLocked={this.state.isLocked}
lockedByMe={this.state.lockedByMe} lockedByMe={this.state.lockedByMe}
isCommentUpdated={this.state.isCommentUpdated}
onSave={this.props.onSave} onSave={this.props.onSave}
isSaving={this.props.isSaving} isSaving={this.props.isSaving}
needSave={this.props.needSave} needSave={this.props.needSave}
@@ -175,6 +200,7 @@ class FileView extends React.Component {
{this.props.content} {this.props.content}
{this.state.isCommentPanelOpen && {this.state.isCommentPanelOpen &&
<CommentPanel <CommentPanel
ref={this.setCommentPanelRef}
toggleCommentPanel={this.toggleCommentPanel} toggleCommentPanel={this.toggleCommentPanel}
participants={this.props.participants} participants={this.props.participants}
onParticipantsChange={this.props.onParticipantsChange} onParticipantsChange={this.props.onParticipantsChange}

View File

@@ -7,8 +7,9 @@ import Icon from '../../components/icon';
import IconButton from '../icon-button'; import IconButton from '../icon-button';
const propTypes = { const propTypes = {
isCommentUpdated: PropTypes.bool,
toggleDetailsPanel: PropTypes.func.isRequired, toggleDetailsPanel: PropTypes.func.isRequired,
toggleHeader: PropTypes.func.isRequired toggleHeader: PropTypes.func.isRequired,
}; };
const { const {
@@ -38,6 +39,7 @@ class OnlyofficeFileToolbar extends React.Component {
}; };
render() { render() {
const { isCommentUpdated } = this.props;
const { moreDropdownOpen } = this.state; const { moreDropdownOpen } = this.state;
return ( return (
<Fragment> <Fragment>
@@ -54,6 +56,7 @@ class OnlyofficeFileToolbar extends React.Component {
aria-label={gettext('Comment')} aria-label={gettext('Comment')}
> >
<i className="sdocfont sdoc-comments"></i> <i className="sdocfont sdoc-comments"></i>
{isCommentUpdated && <span className='comment-tip'></span>}
</div> </div>
<Dropdown isOpen={moreDropdownOpen} toggle={this.toggleMoreOpMenu}> <Dropdown isOpen={moreDropdownOpen} toggle={this.toggleMoreOpMenu}>
<DropdownToggle <DropdownToggle

View File

@@ -20,7 +20,7 @@ body {
.onlyoffice-file-view-header-hidden { .onlyoffice-file-view-header-hidden {
transition: all .3s ease-in-out; transition: all .3s ease-in-out;
display: none!important; display: none !important;
} }
#unfold-onlyoffice-file-view-header { #unfold-onlyoffice-file-view-header {
@@ -32,17 +32,18 @@ body {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: rgba(255,255,255, 0.6); background: rgba(255, 255, 255, 0.6);
border-radius: 0 0 14px 14px; border-radius: 0 0 14px 14px;
box-shadow: 0 0 8px rgba(0,0,0, 0.4); box-shadow: 0 0 8px rgba(0, 0, 0, 0.4);
cursor: pointer; cursor: pointer;
} }
#unfold-onlyoffice-file-view-header:hover { #unfold-onlyoffice-file-view-header:hover {
background: rgba(255,255,255, 1); background: rgba(255, 255, 255, 1);
} }
.file-view-header .file-toolbar-btn { .file-view-header .file-toolbar-btn {
position: relative;
width: 28px; width: 28px;
height: 28px; height: 28px;
margin-left: 10px; margin-left: 10px;
@@ -78,6 +79,17 @@ body {
color: #ED7109; color: #ED7109;
} }
.file-view-header .file-toolbar-btn .comment-tip {
position: absolute;
left: 4px;
top: 4px;
border-radius: 50%;
width: 6px;
height: 6px;
z-index: 100;
background: red;
}
.file-view-header .file-toolbar-more-operations { .file-view-header .file-toolbar-more-operations {
height: 38px; height: 38px;
padding: 0; padding: 0;
@@ -140,6 +152,7 @@ body {
right: -500px; right: -500px;
opacity: 0.5; opacity: 0.5;
} }
to { to {
right: 0px; right: 0px;
opacity: 1; opacity: 1;

View File

@@ -14,7 +14,7 @@ from django.utils import timezone
from seahub.api2.authentication import TokenAuthentication from seahub.api2.authentication import TokenAuthentication
from seahub.api2.permissions import IsRepoAccessible from seahub.api2.permissions import IsRepoAccessible
from seahub.api2.throttling import UserRateThrottle from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error, user_to_dict, to_python_boolean from seahub.api2.utils import api_error, user_to_dict, to_python_boolean, send_comment_update_event
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
from seahub.base.models import FileComment from seahub.base.models import FileComment
from seahub.utils.repo import get_repo_owner from seahub.utils.repo import get_repo_owner
@@ -132,6 +132,7 @@ class FileCommentsView(APIView):
notification = detail notification = detail
notification['to_users'] = to_users notification['to_users'] = to_users
comment['notification'] = notification comment['notification'] = notification
send_comment_update_event(file_uuid)
return Response(comment) return Response(comment)
@@ -166,6 +167,7 @@ class FileCommentView(APIView):
file_comment.delete() file_comment.delete()
SeadocCommentReply.objects.filter(comment_id=comment_id).delete() SeadocCommentReply.objects.filter(comment_id=comment_id).delete()
send_comment_update_event(file_uuid)
return Response({'success': True}) return Response({'success': True})
def put(self, request, repo_id, file_uuid, comment_id): def put(self, request, repo_id, file_uuid, comment_id):
@@ -204,6 +206,7 @@ class FileCommentView(APIView):
comment = file_comment.to_dict() comment = file_comment.to_dict()
comment.update(user_to_dict(file_comment.author, request=request)) comment.update(user_to_dict(file_comment.author, request=request))
send_comment_update_event(file_uuid)
return Response(comment) return Response(comment)
@@ -313,6 +316,7 @@ class FileCommentRepliesView(APIView):
notification = detail notification = detail
notification['to_users'] = to_users notification['to_users'] = to_users
data['notification'] = notification data['notification'] = notification
send_comment_update_event(file_uuid)
return Response(data) return Response(data)
@@ -353,6 +357,7 @@ class FileCommentReplyView(APIView):
if not reply: if not reply:
return api_error(status.HTTP_404_NOT_FOUND, 'reply not found.') return api_error(status.HTTP_404_NOT_FOUND, 'reply not found.')
reply.delete() reply.delete()
send_comment_update_event(file_uuid)
return Response({'success': True}) return Response({'success': True})
def put(self, request, repo_id, file_uuid, comment_id, reply_id): def put(self, request, repo_id, file_uuid, comment_id, reply_id):
@@ -380,4 +385,5 @@ class FileCommentReplyView(APIView):
data = reply.to_dict() data = reply.to_dict()
data.update( data.update(
user_to_dict(reply.author, request=request)) user_to_dict(reply.author, request=request))
send_comment_update_event(file_uuid)
return Response(data) return Response(data)

View File

@@ -8,7 +8,7 @@ import json
import re import re
import logging import logging
import jwt import jwt
import requests
from collections import defaultdict from collections import defaultdict
from functools import wraps from functools import wraps
from django.core.cache import cache from django.core.cache import cache
@@ -34,9 +34,11 @@ from seahub.utils import get_user_repos
from seahub.utils.mail import send_html_email_with_dj_template from seahub.utils.mail import send_html_email_with_dj_template
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
import seahub.settings as settings import seahub.settings as settings
from seahub.tags.models import FileUUIDMap
JWT_PRIVATE_KEY = getattr(settings, 'JWT_PRIVATE_KEY', '') JWT_PRIVATE_KEY = getattr(settings, 'JWT_PRIVATE_KEY', '')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def api_error(code, msg): def api_error(code, msg):
@@ -361,3 +363,37 @@ def is_valid_internal_jwt(auth):
return True return True
return False return False
def send_comment_update_event(file_uuid):
if not settings.ENABLE_NOTIFICATION_SERVER:
return
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
return
repo_id = uuid_map.repo_id
event_data = {
"type": "comment-update",
"content": {
"repo_id": repo_id,
"type": "comment_updated",
"file_uuid": file_uuid,
"file_path": ""
}
}
notification_server_event_url = "%s/events" % settings.INNER_NOTIFICATION_SERVER_URL.rstrip('/')
payload = {
'exp': int(time.time()) + 500
}
jwt_token = jwt.encode(payload, JWT_PRIVATE_KEY, algorithm='HS256')
headers = {
'Authorization': 'Token %s' % jwt_token,
}
try:
resp = requests.post(notification_server_event_url, json=event_data, headers=headers)
if not resp.ok:
logger.error(f'Send comment update event failed: {resp.content}')
except Exception as e:
logger.error(f'Send comment update event error. ERROR: {e}')

View File

@@ -982,6 +982,7 @@ ENABLE_WHITEBOARD = False
ENABLE_NOTIFICATION_SERVER = os.environ.get('ENABLE_NOTIFICATION_SERVER', 'false') == 'true' ENABLE_NOTIFICATION_SERVER = os.environ.get('ENABLE_NOTIFICATION_SERVER', 'false') == 'true'
NOTIFICATION_SERVER_URL = os.environ.get('NOTIFICATION_SERVER_URL', '') NOTIFICATION_SERVER_URL = os.environ.get('NOTIFICATION_SERVER_URL', '')
INNER_NOTIFICATION_SERVER_URL = os.environ.get('INNER_NOTIFICATION_SERVER_URL', 'http://127.0.0.1:8083' )
############################ ############################
# Settings for Seahub Priv # # Settings for Seahub Priv #