1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-09 10:50:24 +00:00

Merge pull request #3321 from haiwen/editor-update

Editor update
This commit is contained in:
Daniel Pan
2019-04-18 17:36:29 +08:00
committed by GitHub
3 changed files with 272 additions and 64 deletions

View File

@@ -765,7 +765,7 @@
}, },
"axios": { "axios": {
"version": "0.18.0", "version": "0.18.0",
"resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
"requires": { "requires": {
"follow-redirects": "^1.3.0", "follow-redirects": "^1.3.0",
@@ -6796,7 +6796,7 @@
}, },
"git-up": { "git-up": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "http://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz", "resolved": "https://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz",
"integrity": "sha1-JkSAoAax2EJhrB/gmjpRacV+oZ0=", "integrity": "sha1-JkSAoAax2EJhrB/gmjpRacV+oZ0=",
"requires": { "requires": {
"is-ssh": "^1.0.0", "is-ssh": "^1.0.0",
@@ -6805,7 +6805,7 @@
}, },
"git-url-parse": { "git-url-parse": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "http://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz", "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz",
"integrity": "sha1-/j15xnRq4FBIz6UIyB553du6OEM=", "integrity": "sha1-/j15xnRq4FBIz6UIyB553du6OEM=",
"requires": { "requires": {
"git-up": "^1.0.0" "git-up": "^1.0.0"
@@ -9766,7 +9766,7 @@
}, },
"node-status-codes": { "node-status-codes": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "http://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
"integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=" "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8="
}, },
"noop6": { "noop6": {
@@ -10137,7 +10137,7 @@
}, },
"package.json": { "package.json": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "http://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz", "resolved": "https://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz",
"integrity": "sha1-+IYFnSpJ7QduZIg2ldc7K0bSHW0=", "integrity": "sha1-+IYFnSpJ7QduZIg2ldc7K0bSHW0=",
"requires": { "requires": {
"git-package-json": "^1.4.0", "git-package-json": "^1.4.0",
@@ -10147,7 +10147,7 @@
"dependencies": { "dependencies": {
"got": { "got": {
"version": "5.7.1", "version": "5.7.1",
"resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz", "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz",
"integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
"requires": { "requires": {
"create-error-class": "^3.0.1", "create-error-class": "^3.0.1",
@@ -10169,7 +10169,7 @@
}, },
"package-json": { "package-json": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "http://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz",
"integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=", "integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=",
"requires": { "requires": {
"got": "^5.0.0", "got": "^5.0.0",
@@ -15306,13 +15306,13 @@
"dependencies": { "dependencies": {
"ansi-regex": { "ansi-regex": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", "resolved": "http://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz",
"integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=", "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=",
"dev": true "dev": true
}, },
"strip-ansi": { "strip-ansi": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz",
"integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=", "integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=",
"dev": true, "dev": true,
"requires": { "requires": {

View File

@@ -0,0 +1,91 @@
import React from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import { IconButton, ButtonGroup, CollabUsersButton } from '@seafile/seafile-editor/dist/components/topbarcomponent/editorToolBar';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem, Tooltip } from 'reactstrap';
import FileInfo from '@seafile/seafile-editor/dist/components/topbarcomponent/file-info';
class MoreMenu extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
tooltipOpen: false,
dropdownOpen:false
};
}
tooltipToggle = () => {
this.setState({ tooltipOpen: !this.state.tooltipOpen });
}
dropdownToggle = () => {
this.setState({ dropdownOpen:!this.state.dropdownOpen });
}
render() {
return (
<Dropdown isOpen={this.state.dropdownOpen} toggle={this.dropdownToggle} direction="down" className="mx-lg-1">
<DropdownToggle id="moreButton">
<i className="fa fa-ellipsis-v"/>
<Tooltip toggle={this.tooltipToggle} delay={{show: 0, hide: 0}} target="moreButton" placement='bottom' isOpen={this.state.tooltipOpen}>{gettext('More')}
</Tooltip>
</DropdownToggle>
<DropdownMenu className="drop-list" right={true}>
<DropdownItem onMouseDown={this.props.openDialogs.bind(this, 'help')}>{gettext('Help')}</DropdownItem>
</DropdownMenu>
</Dropdown>
);
}
}
class CDOCTopbar extends React.Component {
constructor(props) {
super(props);
}
render() {
let { contentChanged, saving } = this.props;
return (
<div className="sf-md-viewer-topbar">
<div className="sf-md-viewer-topbar-first d-flex justify-content-between">
<FileInfo
toggleStar={this.props.toggleStar}
editorUtilities={this.props.editorUtilities}
fileInfo={this.props.fileInfo}
showDraftSaved={this.props.showDraftSaved}
/>
<div className="topbar-btn-container">
{this.props.collabUsers.length > 0 && <CollabUsersButton className={'collab-users-dropdown'}
users={this.props.collabUsers} id={'usersButton'} />}
<ButtonGroup>
<IconButton id={'shareBtn'} text={gettext('Share')} icon={'fa fa-share-alt'}
onMouseDown={this.props.toggleShareLinkDialog}/>
<IconButton text={gettext('Back to parent directory')} id={'parentDirectory'}
icon={'fa fa-folder-open'} onMouseDown={this.props.backToParentDirectory}/>
{
this.props.showFileHistory && <IconButton id={'historyButton'}
text={gettext('File History')} onMouseDown={this.props.toggleHistory} icon={'fa fa-history'}/>
}
{ saving ?
<button type={'button'} className={'btn btn-icon btn-secondary btn-active'}>
<i className={'fa fa-spin fa-spinner'}/></button>
:
<IconButton text={gettext('Save')} id={'saveButton'} icon={'fa fa-save'} disabled={!contentChanged}
onMouseDown={this.props.onSave} isActive={contentChanged}/>
}
</ButtonGroup>
<MoreMenu
openDialogs={this.props.openDialogs}
/>
</div>
</div>
</div>
);
}
}
export default CDOCTopbar;

View File

@@ -5,20 +5,31 @@ import i18n from './i18n';
import { seafileAPI } from './utils/seafile-api'; import { seafileAPI } from './utils/seafile-api';
import io from 'socket.io-client'; import io from 'socket.io-client';
import { gettext } from './utils/constants'; import { gettext } from './utils/constants';
import ModalPortal from './components/modal-portal';
import { RichEditor } from '@seafile/seafile-editor'; import { RichEditor } from '@seafile/seafile-editor';
import CDOCTypeChooser from '@seafile/seafile-editor/dist/components/codc-type-chooser';
import { Value } from 'slate';
import CDOCTopbar from './components/toolbar/cdoc-editor-topbar';
import ShareDialog from './components/dialog/share-dialog';
import { Utils } from './utils/utils';
import { translate } from 'react-i18next';
import { EditorUtilities } from '@seafile/seafile-editor/dist/editorUtilities'; import { EditorUtilities } from '@seafile/seafile-editor/dist/editorUtilities';
import toaster from './components/toast'; import toaster from './components/toast';
import { RichEditorUtils } from '@seafile/seafile-editor/dist/rich-editor-utils';
import './css/markdown-viewer/markdown-editor.css';
import './assets/css/fa-solid.css'; import './assets/css/fa-solid.css';
import './assets/css/fa-regular.css'; import './assets/css/fa-regular.css';
import './assets/css/fontawesome.css'; import './assets/css/fontawesome.css';
import './index.css'; import './index.css';
const CryptoJS = require('crypto-js'); const CryptoJS = require('crypto-js');
const lang = window.app.config.lang; const lang = window.app.config.lang;
const { repoID, repoName, filePath, fileName, username, contactEmail } = window.app.pageOptions; const { repoID, repoName, filePath, fileName, username, contactEmail } = window.app.pageOptions;
const { siteRoot, seafileCollabServer, serviceURL} = window.app.config; const { siteRoot, seafileCollabServer, serviceURL } = window.app.config;
const { name } = window.app.userInfo; const { name } = window.app.userInfo;
let dirPath = '/'; let dirPath = '/';
@@ -28,10 +39,12 @@ class CDOCEditor extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.collabServer = seafileCollabServer ? seafileCollabServer : null this.collabServer = seafileCollabServer ? seafileCollabServer : null;
this.richEditorUtils = new RichEditorUtils(editorUtilities, this)
this.state = { this.state = {
value: Value.create({}),
collabUsers: userInfo ? collabUsers: userInfo ?
[{user: userInfo, is_editing: false}] : [], [{ user: userInfo, is_editing: false }] : [],
fileInfo: { fileInfo: {
repoID: repoID, repoID: repoID,
name: fileName, name: fileName,
@@ -39,6 +52,11 @@ class CDOCEditor extends React.Component {
lastModifier: '', lastModifier: '',
id: '', id: '',
}, },
isShowTypeChooser: false,
isSaving: false,
contentChanged: false,
showShareLinkDialog: false,
isShowHistory: false,
} }
if (this.state.collabServer) { if (this.state.collabServer) {
@@ -53,27 +71,11 @@ class CDOCEditor extends React.Component {
} }
componentDidMount() { componentDidMount() {
editorUtilities.getFileInfo(repoID, filePath).then((res) => {
let { mtime, size, starred, permission, last_modifier_name, id } = res.data;
let lastModifier = last_modifier_name;
this.setState((prevState, props) => ({
fileInfo: {
...prevState.fileInfo,
mtime,
size,
starred,
permission,
lastModifier,
id
}
}));
})
if (userInfo && this.socket) { if (userInfo && this.socket) {
const { repoID, path } = this.state.fileInfo; const { repoID, path } = this.state.fileInfo;
this.socket.emit('presence', { this.socket.emit('presence', {
request: 'join_room', request: 'join_room',
doc_id: CryptoJS.MD5(repoID+path).toString(), doc_id: CryptoJS.MD5(repoID + path).toString(),
user: userInfo user: userInfo
}); });
@@ -89,9 +91,9 @@ class CDOCEditor extends React.Component {
} }
} }
receiveUpdateData (data) { receiveUpdateData(data) {
let currentTime = new Date(); let currentTime = new Date();
if ((parseFloat(currentTime - this.lastModifyTime)/1000) <= 5) { if ((parseFloat(currentTime - this.lastModifyTime) / 1000) <= 5) {
return; return;
} }
editorUtilities.fileMetaData().then((res) => { editorUtilities.fileMetaData().then((res) => {
@@ -101,13 +103,13 @@ class CDOCEditor extends React.Component {
{gettext('This file has been updated.')} {gettext('This file has been updated.')}
<a href='' >{' '}{gettext('Refresh')}</a> <a href='' >{' '}{gettext('Refresh')}</a>
</span>, </span>,
{id: 'repo_updated', duration: 3600}); { id: 'repo_updated', duration: 3600 });
} }
}); });
} }
receivePresenceData(data) { receivePresenceData(data) {
switch(data.response) { switch (data.response) {
case 'user_join': case 'user_join':
toaster.notify(`user ${data.user.name} joined`, { toaster.notify(`user ${data.user.name} joined`, {
duration: 3 duration: 3
@@ -128,7 +130,7 @@ class CDOCEditor extends React.Component {
} }
} }
} }
this.setState({collabUsers: Object.values(data.users)}); this.setState({ collabUsers: Object.values(data.users) });
return; return;
case 'user_editing': case 'user_editing':
toaster.danger(`user ${data.user.name} is editing this file!`, { toaster.danger(`user ${data.user.name} is editing this file!`, {
@@ -141,20 +143,135 @@ class CDOCEditor extends React.Component {
} }
} }
toggleStar = () => {
let starrd = this.state.fileInfo.starred;
if (starrd) {
editorUtilities.unStarItem().then((response) => {
this.setState({
fileInfo: Object.assign({}, this.state.fileInfo, { starred: !starrd })
});
});
} else if (!starrd) {
editorUtilities.starItem().then((response) => {
this.setState({
fileInfo: Object.assign({}, this.state.fileInfo, { starred: !starrd })
});
});
}
}
toggleHistory = () => {
window.location.href = siteRoot + 'repo/file_revisions/' + repoID + '/?p=' + Utils.encodePath(filePath);
}
backToParentDirectory = () => {
window.location.href = editorUtilities.getParentDectionaryUrl();
}
toggleShareLinkDialog = () => {
this.openDialogs('share_link');
}
toggleCancel = () => {
this.setState({
showShareLinkDialog: false,
});
}
openDialogs = (option) => {
switch (option) {
case 'help':
window.richEditor.showHelpDialog();
break;
case 'share_link':
this.setState({
showMarkdownEditorDialog: true,
showShareLinkDialog: true,
});
break;
case 'insert_file':
this.setState({
showMarkdownEditorDialog: true,
showInsertFileDialog: true,
});
break;
default:
return;
}
}
componentWillMount() {
this.richEditorUtils.initialContent();
editorUtilities.getFileInfo().then((response) => {
this.setState({
fileInfo: Object.assign({}, this.state.fileInfo, {
mtime: response.data.mtime,
size: response.data.size,
name: response.data.name,
starred: response.data.starred,
lastModifier: response.data.last_modifier_name,
permission: response.data.permission,
id: response.data.id,
})
});
});
}
render() { render() {
return( return (
<I18nextProvider i18n={ i18n } initialLanguage={ lang } > <React.Fragment>
<RichEditor <CDOCTopbar
collabUsers = {this.state.collabUsers} fileInfo={this.state.fileInfo}
editorUtilities = {editorUtilities} collabUsers={this.state.collabUsers}
toggleStar={this.toggleStar}
editorUtilities={editorUtilities}
backToParentDirectory={this.backToParentDirectory}
toggleShareLinkDialog={this.toggleShareLinkDialog}
openDialogs={this.openDialogs}
showFileHistory={this.state.isShowHistory ? false : true}
toggleHistory={this.toggleHistory}
saving={this.state.isSaving}
onSave={this.richEditorUtils.onSave}
contentChanged={this.state.contentChanged}
/> />
</I18nextProvider> <RichEditor
onSave={this.richEditorUtils.onSave}
resetContentChange={this.richEditorUtils.resetContentChange}
value={this.state.value}
onChange={this.richEditorUtils.onChange}
editorUtilities={editorUtilities}
/>
{this.state.showShareLinkDialog &&
<ModalPortal>
<ShareDialog
itemType="file"
itemName={this.state.fileInfo.name}
itemPath={filePath}
repoID={repoID}
toggleDialog={this.toggleCancel}
isGroupOwnedRepo={false}
repoEncrypted={false}
/>
</ModalPortal>
}
{
this.state.isShowTypeChooser &&
<CDOCTypeChooser
showTypeChooser={this.state.isShowTypeChooser}
setDocument={this.richEditorUtils.setDocument}
/>
}
</React.Fragment>
) )
} }
} }
const TranslatedCDOCEditor = translate('translations')(CDOCEditor)
ReactDOM.render ( ReactDOM.render(
<CDOCEditor/>, <I18nextProvider i18n={i18n} initialLanguage={lang} >
<TranslatedCDOCEditor />
</I18nextProvider>
,
document.getElementById('wrapper') document.getElementById('wrapper')
); );