mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-13 05:39:59 +00:00
add rich editor top bar
This commit is contained in:
91
frontend/src/components/toolbar/cdoc-editor-topbar.js
Normal file
91
frontend/src/components/toolbar/cdoc-editor-topbar.js
Normal 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;
|
@@ -5,14 +5,25 @@ import i18n from './i18n';
|
||||
import { seafileAPI } from './utils/seafile-api';
|
||||
import io from 'socket.io-client';
|
||||
import { gettext } from './utils/constants';
|
||||
import ModalPortal from './components/modal-portal';
|
||||
import { RichEditor } from './src/editor/editor';
|
||||
import CDOCTypeChooser from './src/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 { RichEditor } from '@seafile/seafile-editor';
|
||||
import { EditorUtilities } from '@seafile/seafile-editor/dist/editorUtilities';
|
||||
import toaster from './components/toast';
|
||||
import './css/markdown-viewer/markdown-editor.css';
|
||||
import './assets/css/fa-solid.css';
|
||||
import './assets/css/fa-regular.css';
|
||||
import './assets/css/fontawesome.css';
|
||||
import './index.css';
|
||||
const JSZip = require('jszip');
|
||||
const request = require('request');
|
||||
|
||||
const CryptoJS = require('crypto-js');
|
||||
|
||||
const lang = window.app.config.lang;
|
||||
@@ -30,6 +41,7 @@ class CDOCEditor extends React.Component {
|
||||
super(props);
|
||||
this.collabServer = seafileCollabServer ? seafileCollabServer : null
|
||||
this.state = {
|
||||
value: Value.create({}),
|
||||
collabUsers: userInfo ?
|
||||
[{user: userInfo, is_editing: false}] : [],
|
||||
fileInfo: {
|
||||
@@ -39,6 +51,11 @@ class CDOCEditor extends React.Component {
|
||||
lastModifier: '',
|
||||
id: '',
|
||||
},
|
||||
isShowTypeChooser: false,
|
||||
isSaving: false,
|
||||
contentChanged: false,
|
||||
showShareLinkDialog: false,
|
||||
isShowHistory: false,
|
||||
}
|
||||
|
||||
if (this.state.collabServer) {
|
||||
@@ -52,23 +69,13 @@ class CDOCEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}));
|
||||
resetContentChange = () => {
|
||||
this.setState({
|
||||
contentChanged: false
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (userInfo && this.socket) {
|
||||
const { repoID, path } = this.state.fileInfo;
|
||||
this.socket.emit('presence', {
|
||||
@@ -141,20 +148,260 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
initialContent(){
|
||||
const that = this;
|
||||
editorUtilities.getFileDownloadLink().then((res) => {
|
||||
request({ method : 'GET', url : res.data, encoding: null}, function (error, response, body) {
|
||||
if (error || response.statusCode !== 200) {
|
||||
console.log(error);
|
||||
return;
|
||||
}
|
||||
if (body.length === 0 ) {
|
||||
that.setState({
|
||||
isShowTypeChooser: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
JSZip.loadAsync(body).then((zip) => {
|
||||
if (zip.file('content.json')) {
|
||||
zip.file('content.json').async('string').then((res) => {
|
||||
const value = { object: 'value', document: JSON.parse(res) };
|
||||
that.setState({
|
||||
value: Value.create(value),
|
||||
});
|
||||
}, (err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
if (zip.file('info.json')) {
|
||||
zip.file('info.json').async('string').then((res) => {
|
||||
that.setState({
|
||||
zipVersion: JSON.parse(res).version,
|
||||
});
|
||||
this.documentType = JSON.parse(res).type;
|
||||
}, (err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
// const assets = zip.folder('assets/'); // get images in the future
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.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,
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onChange = (editor) => {
|
||||
const ops = editor.operations
|
||||
.filter(o => o.type !== 'set_selection' && o.type !== 'set_value');
|
||||
if (ops.size !== 0) {
|
||||
this.setState({
|
||||
contentChanged: true,
|
||||
value: editor.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onSave = (editor) => {
|
||||
const info = { 'version': 1, type: this.documentType };
|
||||
let zip = new JSZip();
|
||||
this.setState({
|
||||
isSaving: true
|
||||
});
|
||||
zip.file('info.json', JSON.stringify(info));
|
||||
zip.file('content.json', JSON.stringify(this.state.value.document));
|
||||
zip.folder('assets');
|
||||
zip.generateAsync({ type: 'blob' }).then((blob) => {
|
||||
editorUtilities.saveContent(blob).then(() =>{
|
||||
this.setState({
|
||||
isSaving: false,
|
||||
contentChanged: false
|
||||
});
|
||||
toaster.success(this.props.t('file_saved'), {
|
||||
duration: 2,
|
||||
});
|
||||
editorUtilities.getFileInfo().then((res) => {
|
||||
this.setFileInfoMtime(res.data);
|
||||
});
|
||||
}, (err) => {
|
||||
this.setState({
|
||||
isSaving: false
|
||||
});
|
||||
});
|
||||
}, function (err) {
|
||||
this.setState({
|
||||
isSaving: false
|
||||
});
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
setFileInfoMtime = (fileInfo) => {
|
||||
this.setState({
|
||||
fileInfo: Object.assign({}, this.state.fileInfo, { mtime: fileInfo.mtime, id: fileInfo.id, lastModifier: fileInfo.last_modifier_name })
|
||||
});
|
||||
};
|
||||
|
||||
setDocument = (type, value) => {
|
||||
this.setState({
|
||||
value: value,
|
||||
});
|
||||
this.documentType = type;
|
||||
// save the document type
|
||||
{
|
||||
const info = { 'version': 1, type: type};
|
||||
let zip = new JSZip();
|
||||
zip.file('info.json', JSON.stringify(info));
|
||||
zip.file('content.json', JSON.stringify(value.document));
|
||||
zip.folder('assets');
|
||||
zip.generateAsync({ type: 'blob' }).then((blob) => {
|
||||
editorUtilities.saveContent(blob).then(() =>{
|
||||
editorUtilities.getFileInfo().then((res) => {
|
||||
this.setFileInfoMtime(res.data);
|
||||
});
|
||||
}, (err) => {
|
||||
});
|
||||
}, function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
this.toggleTypeChooser();
|
||||
}
|
||||
|
||||
toggleTypeChooser = () => {
|
||||
this.setState({
|
||||
isShowTypeChooser: !this.state.isShowTypeChooser
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<I18nextProvider i18n={ i18n } initialLanguage={ lang } >
|
||||
<React.Fragment>
|
||||
<CDOCTopbar
|
||||
fileInfo={this.state.fileInfo}
|
||||
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.onSave}
|
||||
contentChanged={this.state.contentChanged}
|
||||
/>
|
||||
<RichEditor
|
||||
collabUsers = {this.state.collabUsers}
|
||||
onSave = {this.onSave}
|
||||
resetContentChange = {this.resetContentChange}
|
||||
value = {this.state.value}
|
||||
onChange = {this.onChange}
|
||||
editorUtilities = {editorUtilities}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
{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.setDocument}
|
||||
/>
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const TranslatedCDOCEditor = translate('translations')(CDOCEditor)
|
||||
|
||||
ReactDOM.render (
|
||||
<CDOCEditor/>,
|
||||
<I18nextProvider i18n={ i18n } initialLanguage={ lang } >
|
||||
<TranslatedCDOCEditor/>
|
||||
</I18nextProvider>
|
||||
,
|
||||
document.getElementById('wrapper')
|
||||
);
|
||||
|
Reference in New Issue
Block a user