1
0
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:
杨顺强
2023-12-08 17:58:27 +08:00
parent bb5ce5e233
commit 9b21cbc51d
15 changed files with 10405 additions and 2497 deletions

View File

@@ -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 &&