mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-07 09:51:26 +00:00
update markdown-editor
This commit is contained in:
@@ -1,21 +1,21 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import io from 'socket.io-client';
|
||||
import { serialize, deserialize } from '@seafile/seafile-editor';
|
||||
import { EXTERNAL_EVENTS, EventBus, RichMarkdownEditor } from '@seafile/seafile-editor';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { gettext, isDocs, mediaUrl } from '../../utils/constants';
|
||||
import toaster from '../../components/toast';
|
||||
import ShareDialog from '../../components/dialog/share-dialog';
|
||||
import InsertFileDialog from '../../components/dialog/insert-file-dialog';
|
||||
import LocalDraftDialog from '../../components/dialog/local-draft-dialog';
|
||||
import HeaderToolbar from './header-toolbar';
|
||||
import SeafileEditor from './seafile-editor';
|
||||
import editorApi from './editor-api';
|
||||
import DetailListView from './detail-list-view';
|
||||
|
||||
import '../../css/markdown-viewer/markdown-editor.css';
|
||||
|
||||
const CryptoJS = require('crypto-js');
|
||||
const URL = require('url-parse');
|
||||
|
||||
const { repoID, filePath, fileName, draftID, isDraft, hasDraft, isLocked, lockedByMe } = window.app.pageOptions;
|
||||
const { siteRoot, serviceUrl, seafileCollabServer } = window.app.config;
|
||||
const userInfo = window.app.userInfo;
|
||||
@@ -75,6 +75,9 @@ class MarkdownEditor extends React.Component {
|
||||
this.socket_id = socket.id;
|
||||
});
|
||||
}
|
||||
this.editorRef = React.createRef();
|
||||
this.isParticipant = false;
|
||||
this.editorSelection = null;
|
||||
}
|
||||
|
||||
toggleLockFile = () => {
|
||||
@@ -121,6 +124,8 @@ class MarkdownEditor extends React.Component {
|
||||
|
||||
|
||||
receivePresenceData(data) {
|
||||
let collabUsers = [];
|
||||
let editingUsers = [];
|
||||
switch(data.response) {
|
||||
case 'user_join':
|
||||
toaster.notify(`user ${data.user.name} joined`, {
|
||||
@@ -142,7 +147,13 @@ class MarkdownEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setState({collabUsers: Object.values(data.users)});
|
||||
collabUsers = Object.values(data.users);
|
||||
editingUsers = collabUsers.filter(ele => ele.is_editing === true && ele.myself === undefined);
|
||||
if (editingUsers.length > 0) {
|
||||
const message = gettext('Another user is editing this file!');
|
||||
toaster.danger(message, {duration: 3});
|
||||
}
|
||||
this.setState({ collabUsers });
|
||||
return;
|
||||
case 'user_editing':
|
||||
toaster.danger(`user ${data.user.name} is editing this file!`, {
|
||||
@@ -176,14 +187,6 @@ class MarkdownEditor extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
setContent = (str) => {
|
||||
let value = deserialize(str);
|
||||
this.setState({
|
||||
markdownContent: str,
|
||||
value: value,
|
||||
});
|
||||
};
|
||||
|
||||
checkDraft = () => {
|
||||
let draftKey = editorApi.getDraftKey();
|
||||
let draft = localStorage.getItem(draftKey);
|
||||
@@ -227,9 +230,6 @@ class MarkdownEditor extends React.Component {
|
||||
|
||||
openDialogs = (option) => {
|
||||
switch (option) {
|
||||
case 'help':
|
||||
window.richMarkdownEditor.showHelpDialog();
|
||||
break;
|
||||
case 'share_link':
|
||||
this.setState({
|
||||
showMarkdownEditorDialog: true,
|
||||
@@ -247,18 +247,6 @@ class MarkdownEditor extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this.socket.emit('repo_update', {
|
||||
request: 'unwatch_update',
|
||||
repo_id: editorApi.repoID,
|
||||
user: {
|
||||
name: editorApi.name,
|
||||
username: editorApi.username,
|
||||
contact_email: editorApi.contact_email,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
|
||||
const fileIcon = Utils.getFileIconUrl(fileName, 192);
|
||||
@@ -276,7 +264,6 @@ class MarkdownEditor extends React.Component {
|
||||
// get file content
|
||||
const fileContentRes = await seafileAPI.getFileContent(downloadUrl);
|
||||
const markdownContent = fileContentRes.data;
|
||||
const value = deserialize(markdownContent);
|
||||
|
||||
// init permission
|
||||
let hasPermission = permission === 'rw' || permission === 'cloud-edit';
|
||||
@@ -300,7 +287,7 @@ class MarkdownEditor extends React.Component {
|
||||
loading: false,
|
||||
fileInfo: {...fileInfo, mtime, size, starred, permission, lastModifier, id},
|
||||
markdownContent,
|
||||
value,
|
||||
value: '',
|
||||
readOnly: !hasPermission || hasDraft,
|
||||
});
|
||||
|
||||
@@ -333,8 +320,36 @@ class MarkdownEditor extends React.Component {
|
||||
window.location.href = url;
|
||||
}
|
||||
}, 100);
|
||||
window.addEventListener('beforeunload', this.onUnload);
|
||||
const eventBus = EventBus.getInstance();
|
||||
this.unsubscribeInsertSeafileImage = eventBus.subscribe(EXTERNAL_EVENTS.ON_INSERT_IMAGE, this.onInsertImageToggle);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('beforeunload', this.onUnload);
|
||||
this.unsubscribeInsertSeafileImage();
|
||||
if (!this.socket) return;
|
||||
this.socket.emit('repo_update', {
|
||||
request: 'unwatch_update',
|
||||
repo_id: editorApi.repoID,
|
||||
user: {
|
||||
name: editorApi.name,
|
||||
username: editorApi.username,
|
||||
contact_email: editorApi.contact_email,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onUnload = (event) => {
|
||||
const { contentChanged } = this.state;
|
||||
if (!contentChanged) return;
|
||||
this.clearTimer();
|
||||
|
||||
const confirmationMessage = gettext('Leave this page? The system may not save your changes.');
|
||||
event.returnValue = confirmationMessage;
|
||||
return confirmationMessage;
|
||||
};
|
||||
|
||||
listFileTags = () => {
|
||||
seafileAPI.listFileTags(repoID, filePath).then(res => {
|
||||
let fileTagList = res.data.file_tags;
|
||||
@@ -381,84 +396,82 @@ class MarkdownEditor extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
autoSaveDraft = () => {
|
||||
let that = this;
|
||||
if (that.timer) {
|
||||
return;
|
||||
}
|
||||
that.timer = setTimeout(() => {
|
||||
let str = '';
|
||||
if (this.state.editorMode == 'rich') {
|
||||
let value = this.draftRichValue;
|
||||
str = serialize(value);
|
||||
}
|
||||
else if (this.state.editorMode == 'plain') {
|
||||
str = this.draftPlainValue;
|
||||
}
|
||||
let draftKey = editorApi.getDraftKey();
|
||||
localStorage.setItem(draftKey, str);
|
||||
that.setState({
|
||||
showDraftSaved: true
|
||||
});
|
||||
setTimeout(() => {
|
||||
that.setState({
|
||||
showDraftSaved: false
|
||||
});
|
||||
}, 3000);
|
||||
that.timer = null;
|
||||
}, 60000);
|
||||
};
|
||||
|
||||
openParentDirectory = () => {
|
||||
window.location.href = editorApi.getParentDectionaryUrl();
|
||||
};
|
||||
|
||||
onEdit = (editorMode) => {
|
||||
if (editorMode === 'rich') {
|
||||
window.seafileEditor.switchToRichTextEditor();
|
||||
return;
|
||||
}
|
||||
if (editorMode === 'plain') {
|
||||
window.seafileEditor.switchToPlainTextEditor();
|
||||
}
|
||||
};
|
||||
|
||||
toggleShareLinkDialog = () => {
|
||||
this.openDialogs('share_link');
|
||||
};
|
||||
|
||||
onInsertImageToggle = (selection) => {
|
||||
this.editorSelection = selection;
|
||||
this.openDialogs('insert_file');
|
||||
};
|
||||
|
||||
toggleHistory = () => {
|
||||
window.location.href = siteRoot + 'repo/file_revisions/' + repoID + '/?p=' + Utils.encodePath(filePath);
|
||||
};
|
||||
|
||||
getInsertLink = (repoID, filePath) => {
|
||||
const selection = this.editorSelection;
|
||||
const fileName = Utils.getFileName(filePath);
|
||||
const suffix = fileName.slice(fileName.indexOf('.') + 1);
|
||||
const eventBus = EventBus.getInstance();
|
||||
if (IMAGE_SUFFIXES.includes(suffix)) {
|
||||
let innerURL = serviceUrl + '/lib/' + repoID + '/file' + Utils.encodePath(filePath) + '?raw=1';
|
||||
window.richMarkdownEditor.addLink(fileName, innerURL, true);
|
||||
eventBus.dispatch(EXTERNAL_EVENTS.INSERT_IMAGE, { title: fileName, url: innerURL, isImage: true, selection });
|
||||
return;
|
||||
}
|
||||
let innerURL = serviceUrl + '/lib/' + repoID + '/file' + Utils.encodePath(filePath);
|
||||
window.richMarkdownEditor.addLink(fileName, innerURL);
|
||||
eventBus.dispatch(EXTERNAL_EVENTS.INSERT_IMAGE, { title: fileName, url: innerURL, selection});
|
||||
};
|
||||
|
||||
onContentChanged = (value) => {
|
||||
this.setState({ contentChanged: value });
|
||||
addParticipants = () => {
|
||||
if (this.isParticipant || !window.showParticipants) return;
|
||||
const { userName } = editorApi;
|
||||
const { participants } = this.state;
|
||||
if (participants && participants.length !== 0) {
|
||||
const isParticipant = participants.some((participant) => {
|
||||
return participant.email === userName;
|
||||
});
|
||||
if (isParticipant) return;
|
||||
}
|
||||
|
||||
const emails = [userName];
|
||||
editorApi.addFileParticipants(emails).then((res) => {
|
||||
this.isParticipant = true;
|
||||
this.listFileParticipants();
|
||||
});
|
||||
};
|
||||
|
||||
onSaving = (value) => {
|
||||
this.setState({ saving: value });
|
||||
onContentChanged = () => {
|
||||
this.setState({ contentChanged: true });
|
||||
};
|
||||
|
||||
onSaveEditorContent = () => {
|
||||
this.setState({ saving: true });
|
||||
const content = this.editorRef.current.getValue();
|
||||
editorApi.saveContent(content).then(() => {
|
||||
this.setState({
|
||||
saving: false,
|
||||
contentChanged: false,
|
||||
});
|
||||
|
||||
this.lastModifyTime = new Date();
|
||||
const message = gettext('Successfully saved');
|
||||
toaster.success(message, {duration: 2,});
|
||||
|
||||
editorApi.getFileInfo().then((res) => {
|
||||
this.setFileInfoMtime(res.data);
|
||||
});
|
||||
|
||||
this.addParticipants();
|
||||
}, () => {
|
||||
this.setState({ saving: false });
|
||||
const message = gettext('Failed to save');
|
||||
toaster.danger(message, {duration: 2});
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.loading) {
|
||||
return (
|
||||
<div className="empty-loading-page">
|
||||
<div className="lds-ripple page-centered"><div></div><div></div></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const { loading, editorMode, markdownContent, fileInfo, fileTagList } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -470,10 +483,9 @@ class MarkdownEditor extends React.Component {
|
||||
collabUsers={this.state.collabUsers}
|
||||
fileInfo={this.state.fileInfo}
|
||||
toggleStar={this.toggleStar}
|
||||
openParentDirectory={this.openParentDirectory}
|
||||
openDialogs={this.openDialogs}
|
||||
toggleShareLinkDialog={this.toggleShareLinkDialog}
|
||||
onEdit={this.onEdit}
|
||||
onEdit={this.setEditorMode}
|
||||
toggleNewDraft={editorApi.createDraftFile}
|
||||
showFileHistory={this.state.isShowHistory ? false : true }
|
||||
toggleHistory={this.toggleHistory}
|
||||
@@ -481,50 +493,28 @@ class MarkdownEditor extends React.Component {
|
||||
editorMode={this.state.editorMode}
|
||||
contentChanged={this.state.contentChanged}
|
||||
saving={this.state.saving}
|
||||
onSaveEditorContent={this.onSaveEditorContent}
|
||||
showDraftSaved={this.state.showDraftSaved}
|
||||
isLocked={this.state.isLocked}
|
||||
lockedByMe={this.state.lockedByMe}
|
||||
toggleLockFile={this.toggleLockFile}
|
||||
/>
|
||||
<SeafileEditor
|
||||
scriptSource={mediaUrl + 'js/mathjax/tex-svg.js'}
|
||||
fileInfo={this.state.fileInfo}
|
||||
markdownContent={this.state.markdownContent}
|
||||
editorApi={editorApi}
|
||||
collabUsers={this.state.collabUsers}
|
||||
setFileInfoMtime={this.setFileInfoMtime}
|
||||
setEditorMode={this.setEditorMode}
|
||||
setContent={this.setContent}
|
||||
draftID={draftID}
|
||||
isDraft={isDraft}
|
||||
mode={this.state.mode}
|
||||
emitSwitchEditor={this.emitSwitchEditor}
|
||||
hasDraft={hasDraft}
|
||||
editorMode={this.state.editorMode}
|
||||
siteRoot={siteRoot}
|
||||
autoSaveDraft={this.autoSaveDraft}
|
||||
setDraftValue={this.setDraftValue}
|
||||
clearTimer={this.clearTimer}
|
||||
openDialogs={this.openDialogs}
|
||||
deleteDraft={this.deleteDraft}
|
||||
readOnly={this.state.readOnly}
|
||||
onContentChanged={this.onContentChanged}
|
||||
onSaving={this.onSaving}
|
||||
contentChanged={this.state.contentChanged}
|
||||
fileTagList={this.state.fileTagList}
|
||||
onFileTagChanged={this.onFileTagChanged}
|
||||
participants={this.state.participants}
|
||||
onParticipantsChange={this.onParticipantsChange}
|
||||
markdownLint={fileName.toLowerCase() !== 'index.md'}
|
||||
/>
|
||||
{this.state.localDraftDialog &&
|
||||
<LocalDraftDialog
|
||||
localDraftDialog={this.state.localDraftDialog}
|
||||
deleteDraft={this.deleteDraft}
|
||||
closeDraftDialog={this.closeDraftDialog}
|
||||
useDraft={this.useDraft}
|
||||
/>
|
||||
}
|
||||
<div className='sf-md-viewer-content'>
|
||||
<RichMarkdownEditor
|
||||
ref={this.editorRef}
|
||||
mode={editorMode}
|
||||
isFetching={loading}
|
||||
initValue={fileName}
|
||||
value={markdownContent}
|
||||
editorApi={editorApi}
|
||||
onSave={this.onSaveEditorContent}
|
||||
onContentChanged={this.onContentChanged}
|
||||
mathJaxSource={mediaUrl + 'js/mathjax/tex-svg.js'}
|
||||
isSupportInsertSeafileImage={true}
|
||||
>
|
||||
<DetailListView fileInfo={fileInfo} fileTagList={fileTagList} onFileTagChanged={this.onFileTagChanged}/>
|
||||
</RichMarkdownEditor>
|
||||
</div>
|
||||
{this.state.showMarkdownEditorDialog && (
|
||||
<React.Fragment>
|
||||
{this.state.showInsertFileDialog &&
|
||||
|
Reference in New Issue
Block a user