1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-06 17:33:18 +00:00
Files
seahub/frontend/src/markdown-editor.js

931 lines
27 KiB
JavaScript
Raw Normal View History

import React, { Fragment } from 'react';
import { SeafileEditor } from '@seafile/seafile-editor/dist/editor/editor.js';
2018-05-02 14:09:58 +08:00
import 'whatwg-fetch';
2019-02-14 02:41:59 +00:00
import { seafileAPI } from './utils/seafile-api';
import { Utils } from './utils/utils';
import { gettext, isDocs } from './utils/constants';
2019-03-14 18:14:11 +08:00
import io from 'socket.io-client';
import toaster from './components/toast';
import ModalPortal from './components/modal-portal';
2019-02-25 18:17:04 +08:00
import ShareDialog from './components/dialog/share-dialog';
2019-03-26 11:18:41 +08:00
import InsertFileDialog from './components/dialog/insert-file-dialog';
2019-04-18 16:42:27 +08:00
import InsertRepoImageDialog from './components/dialog/insert-repo-image-dialog';
import FileParticipantDialog from './components/dialog/file-participant-dialog';
2019-03-14 18:14:11 +08:00
import { serialize, deserialize } from '@seafile/seafile-editor/dist/utils/slate2markdown';
2019-03-18 08:40:31 +08:00
import LocalDraftDialog from './components/dialog/local-draft-dialog';
2019-03-11 21:08:25 +08:00
import MarkdownViewerToolbar from './components/toolbar/markdown-viewer-toolbar';
2019-06-19 13:36:57 +08:00
import EditFileTagDialog from './components/dialog/edit-filetag-dialog';
import RelatedFileDialogs from './components/dialog/related-file-dialogs';
import './css/markdown-viewer/markdown-editor.css';
2019-03-11 21:08:25 +08:00
const CryptoJS = require('crypto-js');
2019-03-15 12:11:32 +08:00
const URL = require('url-parse');
2019-05-05 12:05:29 +08:00
const { repoID, repoName, filePath, fileName, mode, draftID, isDraft, hasDraft, isLocked, lockedByMe } = window.app.pageOptions;
2019-02-25 18:17:04 +08:00
const { siteRoot, serviceUrl, seafileCollabServer } = window.app.config;
const userInfo = window.app.userInfo;
2019-02-25 18:17:04 +08:00
const userName = userInfo.username;
let dirPath = '/';
2018-05-02 14:09:58 +08:00
function getImageFileNameWithTimestamp() {
var d = Date.now();
return 'image-' + d.toString() + '.png';
2018-05-02 14:09:58 +08:00
}
2018-10-15 15:51:29 +08:00
class EditorApi {
constructor () {
this.repoID = repoID;
this.filePath = filePath;
this.serviceUrl = serviceUrl;
2019-03-05 13:50:12 +08:00
this.name = userInfo.name;
this.contact_email = userInfo.contact_email;
2019-02-27 12:22:13 +08:00
this.fileName = fileName;
this.userName = userName;
}
2019-07-22 17:09:10 +08:00
2018-05-02 14:09:58 +08:00
saveContent(content) {
return (
seafileAPI.getUpdateLink(repoID, dirPath).then((res) => {
const uploadLink = res.data;
return seafileAPI.updateFile(uploadLink, filePath, fileName, content);
2018-05-02 14:09:58 +08:00
})
);
}
2019-03-27 11:28:00 +08:00
unstarItem () {
return (
2019-03-27 11:28:00 +08:00
seafileAPI.unstarItem(this.repoID, this.filePath)
);
2018-05-02 14:09:58 +08:00
}
2019-02-18 20:26:55 +08:00
starItem() {
return (
2019-02-18 20:26:55 +08:00
seafileAPI.starItem(this.repoID, this.filePath)
);
}
getParentDectionaryUrl() {
let parentPath = this.filePath.substring(0, this.filePath.lastIndexOf('/'));
let libName = encodeURIComponent(repoName);
let path = Utils.encodePath(parentPath);
2019-02-11 15:10:57 +08:00
return this.serviceUrl + '/library/' + this.repoID + '/' + libName + path;
}
2019-07-22 17:09:10 +08:00
2018-05-02 14:09:58 +08:00
_getImageURL(fileName) {
const url = this.serviceUrl + '/lib/' + repoID + '/file/images/auto-upload/' + fileName + '?raw=1';
2018-05-02 14:09:58 +08:00
return url;
}
uploadImage = (imageFile) => {
return (
seafileAPI.getFileServerUploadLink(repoID, dirPath).then((res) => {
let uploadLinkComponent = res.data;
const uploadLink = uploadLinkComponent + '?ret-json=1';
2018-05-02 14:09:58 +08:00
const name = getImageFileNameWithTimestamp();
const blob = imageFile.slice(0, -1, 'image/png');
const newFile = new File([blob], name, {type: 'image/png'});
const formData = new FormData();
formData.append('parent_dir', '/');
formData.append('relative_path', 'images/auto-upload');
formData.append('file', newFile);
return {uploadLink, formData};
}).then(({ uploadLink, formData}) => {
return seafileAPI.uploadImage(uploadLink, formData);
}).then ((res) => {
let resArr = res.data[0];
let filename = resArr.name;
2018-05-02 14:09:58 +08:00
return this._getImageURL(filename);
})
);
}
2018-05-02 14:09:58 +08:00
2018-11-05 14:03:13 +00:00
uploadLocalImage = (imageFile) => {
return (
2020-01-11 13:29:04 +08:00
seafileAPI.getFileServerUploadLink(repoID, dirPath).then((res) => {
2018-11-05 14:03:13 +00:00
const uploadLink = res.data + '?ret-json=1';
2019-12-18 16:53:21 +08:00
const name = getImageFileNameWithTimestamp();
const newFile = new File([imageFile], name, {type: imageFile.type});
2018-11-05 14:03:13 +00:00
const formData = new FormData();
formData.append('parent_dir', '/');
formData.append('relative_path', 'images/auto-upload');
2018-11-05 14:03:13 +00:00
formData.append('file', newFile);
return seafileAPI.uploadImage(uploadLink, formData);
}).then ((res) => {
return this._getImageURL(res.data[0].name);
})
);
}
2018-05-02 14:09:58 +08:00
getFileURL(fileNode) {
var url;
2018-08-28 11:34:03 +08:00
if (fileNode.type === 'file') {
if (fileNode.isImage()) {
2019-01-11 10:58:10 +08:00
url = serviceUrl + '/lib/' + repoID + '/file' + Utils.encodePath(fileNode.path()) + '?raw=1';
2018-08-28 11:34:03 +08:00
} else {
2019-01-11 10:58:10 +08:00
url = serviceUrl + '/lib/' + repoID + '/file' + Utils.encodePath(fileNode.path());
2018-08-28 11:34:03 +08:00
}
2018-05-02 14:09:58 +08:00
} else {
2019-01-11 10:58:10 +08:00
url = serviceUrl + '/library/' + repoID + '/' + encodeURIComponent(repoName) + Utils.encodePath(fileNode.path());
2018-05-02 14:09:58 +08:00
}
return url;
}
2019-07-22 17:09:10 +08:00
2018-05-02 14:09:58 +08:00
isInternalFileLink(url) {
2019-01-11 10:58:10 +08:00
var re = new RegExp(this.serviceUrl + '/lib/[0-9a-f-]{36}/file.*');
2018-05-02 14:09:58 +08:00
return re.test(url);
}
2019-07-22 17:09:10 +08:00
2018-08-28 11:34:03 +08:00
isInternalDirLink(url) {
2019-01-11 10:58:10 +08:00
var re = new RegExp(serviceUrl + '/library/' + '[0-9a-f\-]{36}.*');
2018-08-28 11:34:03 +08:00
return re.test(url);
}
2018-05-02 14:09:58 +08:00
getFiles() {
return seafileAPI.listDir(repoID, dirPath, { recursive: true} ).then((response) => {
var files = response.data.dirent_list.map((item) => {
return {
name: item.name,
type: item.type === 'dir' ? 'dir' : 'file',
parent_path: item.parent_dir
};
});
return files;
});
2018-05-02 14:09:58 +08:00
}
2018-08-06 18:29:12 +08:00
getFileHistory() {
return seafileAPI.getFileHistory(repoID, filePath);
2018-08-06 18:29:12 +08:00
}
getFileInfo() {
return seafileAPI.getFileInfo(repoID, filePath);
2018-08-06 18:29:12 +08:00
}
2018-12-28 14:25:25 +08:00
getRepoInfo(newRepoID) {
return seafileAPI.getRepoInfo(newRepoID);
2018-12-28 14:25:25 +08:00
}
getInternalLink() {
return seafileAPI.getInternalLink(repoID, filePath);
}
getShareLink() {
return seafileAPI.getShareLink(repoID, filePath);
}
2018-12-19 02:17:07 +00:00
createShareLink (repoID, filePath, userPassword, userValidDays, permissions) {
return seafileAPI.createShareLink(repoID, filePath, userPassword, userValidDays, permissions);
}
deleteShareLink(token){
return seafileAPI.deleteShareLink(token);
}
2018-09-19 10:47:56 +08:00
getDraftKey() {
return (repoID + filePath);
}
2018-09-27 12:01:56 +08:00
getFileContent(url) {
return seafileAPI.getFileContent(url);
}
listFileHistoryRecords(page, perPage) {
return seafileAPI.listFileHistoryRecords(repoID, filePath, page, perPage);
2018-09-27 12:01:56 +08:00
}
2019-03-04 19:50:11 +08:00
getFileHistoryVersion(commitID, filePath) {
2018-09-27 12:01:56 +08:00
return seafileAPI.getFileRevision(repoID, commitID, filePath);
}
2018-10-15 15:51:29 +08:00
2018-10-30 11:07:01 +08:00
getCommentsNumber() {
2019-02-27 12:22:13 +08:00
return seafileAPI.getCommentsNumber(this.repoID, filePath);
2018-10-30 11:07:01 +08:00
}
2018-11-16 09:57:35 +00:00
postComment(comment, detail) {
return seafileAPI.postComment(this.repoID, this.filePath, comment, detail);
2018-10-30 11:07:01 +08:00
}
listComments() {
return seafileAPI.listComments(this.repoID, this.filePath);
}
updateComment(commentID, resolved, detail, newComment) {
return seafileAPI.updateComment(this.repoID, commentID, resolved, detail, newComment);
2018-10-30 11:07:01 +08:00
}
deleteComment(commentID) {
return seafileAPI.deleteComment(this.repoID, commentID);
}
2019-07-22 17:09:10 +08:00
2018-10-30 11:07:01 +08:00
getUserAvatar(size) {
return seafileAPI.getUserAvatar(userName, size);
}
2018-11-01 17:52:59 +08:00
goDraftPage() {
window.location.href = serviceUrl + '/drafts/' + draftID + '/';
2018-11-01 17:52:59 +08:00
}
2018-11-02 11:03:10 +08:00
createDraftFile() {
return seafileAPI.createDraft(repoID, filePath).then(res => {
2019-03-07 17:15:30 +08:00
window.location.href = serviceUrl + '/lib/' + res.data.origin_repo_id + '/file' + Utils.encodePath(res.data.draft_file_path) + '?mode=edit';
2018-11-22 17:00:23 +08:00
});
2018-11-02 11:03:10 +08:00
}
2018-05-02 14:09:58 +08:00
2019-03-05 15:37:51 +08:00
publishDraftFile() {
return seafileAPI.publishDraft(draftID).then(res => {
2019-03-07 17:15:30 +08:00
window.location.href = serviceUrl + '/lib/' + repoID + '/file' + Utils.encodePath(res.data.published_file_path);
2019-03-05 15:37:51 +08:00
});
}
fileMetaData() {
return seafileAPI.fileMetaData(repoID, filePath);
}
listFileTags = () => {
return seafileAPI.listFileTags(repoID, filePath);
}
listRepoTags = () => {
return seafileAPI.listRepoTags(repoID);
}
2019-07-22 17:09:10 +08:00
2019-02-28 18:33:35 +08:00
markdownLint(slateValue) {
return seafileAPI.markdownLint(slateValue);
}
listFileParticipant() {
return seafileAPI.listFileParticipants(repoID, filePath);
}
2019-07-27 09:40:26 +08:00
addFileParticipants(emails) {
return seafileAPI.addFileParticipants(repoID, filePath, emails);
}
listRepoRelatedUsers() {
return seafileAPI.listRepoRelatedUsers(repoID);
}
}
2018-05-02 14:09:58 +08:00
const editorApi = new EditorApi();
2018-05-02 14:09:58 +08:00
2018-09-21 14:16:15 +08:00
class MarkdownEditor extends React.Component {
2018-05-02 14:09:58 +08:00
constructor(props) {
super(props);
2019-03-11 21:08:25 +08:00
this.timer = null;
this.localDraft = '';
this.autoSave = false;
this.draftRichValue = '';
this.draftPlainValue = '';
this.state = {
markdownContent: '',
loading: true,
mode: 'editor',
fileInfo: {
repoID: repoID,
name: fileName,
path: filePath,
mtime: null,
size: 0,
starred: false,
permission: '',
lastModifier: '',
id: '',
},
editorMode: 'rich',
collabServer: seafileCollabServer ? seafileCollabServer : null,
2019-03-11 21:08:25 +08:00
localDraftDialog: false,
showMarkdownEditorDialog: false,
2019-02-25 18:17:04 +08:00
showShareLinkDialog: false,
2019-03-26 11:18:41 +08:00
showInsertFileDialog: false,
2019-04-18 16:42:27 +08:00
showInsertRepoImageDialog: false,
showFileParticipantDialog: false,
2019-03-11 21:08:25 +08:00
showDraftSaved: false,
collabUsers: userInfo ?
[{user: userInfo, is_editing: false}] : [],
value: null,
2019-03-15 12:11:32 +08:00
isShowHistory: false,
readOnly: true,
2019-04-13 14:51:41 +08:00
contentChanged: false,
saving: false,
2019-05-05 12:05:29 +08:00
isLocked: isLocked,
lockedByMe: lockedByMe,
2019-06-19 13:36:57 +08:00
relatedFiles: [],
fileTagList: [],
showRelatedFileDialog: false,
showEditFileTagDialog: false,
viewMode: 'list_related_file',
participants: [],
};
2019-03-11 21:08:25 +08:00
if (this.state.collabServer) {
const socket = io(this.state.collabServer);
this.socket = socket;
socket.on('presence', (data) => this.receivePresenceData(data));
socket.on('repo_update', (data) => this.receiveUpdateData(data));
socket.on('connect', () => {
this.socket_id = socket.id;
});
}
}
2019-05-05 12:05:29 +08:00
toggleLockFile = () => {
const { repoID, path } = this.state.fileInfo;
if (this.state.isLocked) {
seafileAPI.unlockfile(repoID, path).then((res) => {
this.setState({ isLocked: false, lockedByMe: false });
});
} else {
seafileAPI.lockfile(repoID, path).then((res) => {
this.setState({ isLocked: true, lockedByMe: true });
});
}
}
2019-03-11 21:08:25 +08:00
emitSwitchEditor = (is_editing=false) => {
if (userInfo && this.state.collabServer) {
const { repoID, path } = this.state.fileInfo;
this.socket.emit('presence', {
request: 'editing',
2019-03-14 18:14:11 +08:00
doc_id: CryptoJS.MD5(repoID+path).toString(),
user: userInfo,
is_editing,
2019-03-11 21:08:25 +08:00
});
}
}
receiveUpdateData (data) {
let currentTime = new Date();
if ((parseFloat(currentTime - this.lastModifyTime)/1000) <= 5) {
return;
}
editorApi.fileMetaData().then((res) => {
2019-03-11 21:08:25 +08:00
if (res.data.id !== this.state.fileInfo.id) {
toaster.notify(
<span>
{gettext('This file has been updated.')}
<a href='' >{' '}{gettext('Refresh')}</a>
2019-03-11 21:08:25 +08:00
</span>,
{id: 'repo_updated', duration: 3600});
}
});
}
receivePresenceData(data) {
switch(data.response) {
case 'user_join':
toaster.notify(`user ${data.user.name} joined`, {
duration: 3
});
return;
case 'user_left':
toaster.notify(`user ${data.user.name} left`, {
duration: 3
});
return;
case 'update_users':
for (var prop in data.users) {
if (data.users.hasOwnProperty(prop)) {
if (prop === this.socket_id) {
data.users[prop]['myself'] = true;
break;
}
}
}
this.setState({collabUsers: Object.values(data.users)});
return;
case 'user_editing':
toaster.danger(`user ${data.user.name} is editing this file!`, {
duration: 3
});
return;
default:
console.log('unknown response type: ' + data.response);
return;
}
}
2018-05-02 14:09:58 +08:00
toggleCancel = () => {
this.setState({
showMarkdownEditorDialog: false,
2019-02-25 18:17:04 +08:00
showShareLinkDialog: false,
2019-03-26 11:18:41 +08:00
showInsertFileDialog: false,
2019-04-18 16:42:27 +08:00
showInsertRepoImageDialog: false,
2019-06-19 13:36:57 +08:00
showRelatedFileDialog: false,
showEditFileTagDialog: false,
showFileParticipantDialog: false,
});
}
2019-03-11 21:08:25 +08:00
setEditorMode = (type) => {
this.setState({
editorMode: type
2019-03-14 18:14:11 +08:00
});
2019-03-11 21:08:25 +08:00
}
setDraftValue = (type, value) => {
if (type === 'rich') {
2019-03-14 18:14:11 +08:00
this.draftRichValue = value;
2019-03-11 21:08:25 +08:00
} else {
this.draftPlainValue = value;
}
}
2019-03-11 21:08:25 +08:00
setContent = (str) => {
let value = deserialize(str);
2019-03-11 21:08:25 +08:00
this.setState({
markdownContent: str,
value: value,
2019-03-11 21:08:25 +08:00
});
}
checkDraft = () => {
let draftKey = editorApi.getDraftKey();
2019-03-11 21:08:25 +08:00
let draft = localStorage.getItem(draftKey);
let that = this;
if (draft) {
that.setState({
localDraftDialog: true,
});
that.localDraft = draft;
localStorage.removeItem(draftKey);
}
}
useDraft = () => {
this.setState({
localDraftDialog: false,
loading: false,
markdownContent: this.localDraft,
editorMode: 'rich',
});
this.emitSwitchEditor(true);
}
deleteDraft = () => {
if (this.state.localDraftDialog) {
this.setState({
localDraftDialog: false,
loading: false,
});
}
let draftKey = editorApi.getDraftKey();
2019-04-15 12:09:58 +08:00
localStorage.removeItem(draftKey);
2019-03-11 21:08:25 +08:00
}
2019-03-18 08:40:31 +08:00
closeDraftDialog = () => {
this.setState({
localDraftDialog: false
});
}
2019-03-11 21:08:25 +08:00
clearTimer = () => {
clearTimeout(this.timer);
this.timer = null;
}
openDialogs = (option) => {
switch(option)
{
case 'help':
window.richMarkdownEditor.showHelpDialog();
break;
2019-02-25 18:17:04 +08:00
case 'share_link':
this.setState({
showMarkdownEditorDialog: true,
showShareLinkDialog: true,
});
break;
2019-03-26 11:18:41 +08:00
case 'insert_file':
this.setState({
showMarkdownEditorDialog: true,
showInsertFileDialog: true,
});
break;
2019-04-18 16:42:27 +08:00
case 'insert_repo_image':
this.setState({
showMarkdownEditorDialog: true,
showInsertRepoImageDialog: true,
});
break;
2019-06-19 13:36:57 +08:00
case 'related_files':
if (this.state.relatedFiles.length > 0) {
this.setState({
showRelatedFileDialog: true,
showMarkdownEditorDialog: true,
viewMode: 'list_related_file',
});
}
else {
this.setState({
showRelatedFileDialog: true,
showMarkdownEditorDialog: true,
viewMode: 'add_related_file',
});
}
break;
case 'file_tags':
this.setState({
showEditFileTagDialog: true,
showMarkdownEditorDialog: true,
});
break;
case 'add-participant':
this.setState({
showMarkdownEditorDialog: true,
showFileParticipantDialog: true,
});
break;
default:
return;
}
}
2019-03-11 21:08:25 +08:00
componentWillUnmount() {
2019-04-08 13:51:34 +08:00
2019-03-11 21:08:25 +08:00
this.socket.emit('repo_update', {
request: 'unwatch_update',
repo_id: editorApi.repoID,
2019-03-14 18:14:11 +08:00
user: {
name: editorApi.name,
username: editorApi.username,
contact_email: editorApi.contact_email,
2019-03-14 18:14:11 +08:00
},
2019-03-11 21:08:25 +08:00
});
}
2018-05-02 14:09:58 +08:00
componentDidMount() {
seafileAPI.getFileInfo(repoID, filePath).then((res) => {
let { mtime, size, starred, permission, last_modifier_name, id } = res.data;
let lastModifier = last_modifier_name;
2018-08-06 18:29:12 +08:00
this.setState((prevState, props) => ({
fileInfo: {
...prevState.fileInfo,
mtime,
size,
2018-08-06 18:29:12 +08:00
starred,
permission,
lastModifier,
id
}
}));
seafileAPI.getFileDownloadLink(repoID, filePath).then((res) => {
const downLoadUrl = res.data;
seafileAPI.getFileContent(downLoadUrl).then((res) => {
2019-03-11 21:08:25 +08:00
const contentLength = res.data.length;
let isBlankFile = (contentLength === 0 || contentLength === 1);
2019-04-19 14:44:24 +08:00
let permission = this.state.fileInfo.permission;
let hasPermission = (permission === 'rw' || permission === 'cloud-edit');
let value = deserialize(res.data);
this.setState({
markdownContent: res.data,
2019-03-11 21:08:25 +08:00
loading: false,
// Goto rich edit page
// First, the user has the relevant permissions, otherwise he can only enter the viewer interface or cannot access
// case1: If file is draft file
// case2: If mode == 'edit' and the file has no draft
// case3: The length of markDownContent is 1 when clear all content in editor and the file has no draft
2019-04-13 14:51:41 +08:00
readOnly: !hasPermission || hasDraft,
value: value,
});
});
});
});
2019-03-11 21:08:25 +08:00
if (userInfo && this.socket) {
const { repoID, path } = this.state.fileInfo;
this.socket.emit('presence', {
request: 'join_room',
2019-03-14 18:14:11 +08:00
doc_id: CryptoJS.MD5(repoID+path).toString(),
user: userInfo
2019-03-11 21:08:25 +08:00
});
this.socket.emit('repo_update', {
request: 'watch_update',
repo_id: editorApi.repoID,
2019-03-14 18:14:11 +08:00
user: {
name: editorApi.name,
username: editorApi.username,
contact_email: editorApi.contact_email,
2019-03-14 18:14:11 +08:00
},
2019-03-11 21:08:25 +08:00
});
}
this.checkDraft();
2019-06-19 13:36:57 +08:00
this.listRelatedFiles();
this.listFileTags();
this.listFileParticipants();
2019-06-29 11:24:18 +08:00
window.showParticipants = true;
setTimeout(() => {
let url = new URL(window.location.href);
if (url.hash) {
window.location.href = window.location.href;
}
}, 100);
}
2018-12-28 14:25:25 +08:00
2019-06-19 13:36:57 +08:00
listRelatedFiles = () => {
seafileAPI.listRelatedFiles(repoID, filePath).then(res => {
this.setState({ relatedFiles: res.data.related_files });
});
}
listFileTags = () => {
seafileAPI.listFileTags(repoID, filePath).then(res => {
let fileTagList = res.data.file_tags;
for (let i = 0; i < fileTagList.length; i++) {
fileTagList[i].id = fileTagList[i].file_tag_id;
}
this.setState({ fileTagList: fileTagList });
});
}
onRelatedFileChange = () => {
this.listRelatedFiles();
}
onFileTagChanged = () => {
this.listFileTags();
}
listFileParticipants = () => {
editorApi.listFileParticipant().then((res) => {
this.setState({ participants: res.data.participant_list });
});
}
onParticipantsChange = () => {
this.listFileParticipants();
}
2019-03-11 21:08:25 +08:00
setFileInfoMtime = (fileInfo) => {
this.setState({
fileInfo: Object.assign({}, this.state.fileInfo, { mtime: fileInfo.mtime, id: fileInfo.id, lastModifier: fileInfo.last_modifier_name })
});
};
toggleStar = () => {
let starrd = this.state.fileInfo.starred;
if (starrd) {
editorApi.unstarItem().then((response) => {
2019-03-11 21:08:25 +08:00
this.setState({
fileInfo: Object.assign({}, this.state.fileInfo, {starred: !starrd})
});
});
} else if (!starrd) {
editorApi.starItem().then((response) => {
2019-03-11 21:08:25 +08:00
this.setState({
fileInfo: Object.assign({}, this.state.fileInfo, {starred: !starrd})
});
});
}
}
autoSaveDraft = () => {
let that = this;
if (that.timer) {
return;
2019-03-14 18:14:11 +08:00
}
else {
2019-03-11 21:08:25 +08:00
that.timer = setTimeout(() => {
let str = '';
if (this.state.editorMode == 'rich') {
let value = this.draftRichValue;
str = serialize(value);
2019-03-11 21:08:25 +08:00
}
else if (this.state.editorMode == 'plain') {
str = this.draftPlainValue;
}
let draftKey = editorApi.getDraftKey();
2019-03-11 21:08:25 +08:00
localStorage.setItem(draftKey, str);
that.setState({
2019-03-14 18:14:11 +08:00
showDraftSaved: true
2019-03-11 21:08:25 +08:00
});
setTimeout(() => {
that.setState({
2019-03-14 18:14:11 +08:00
showDraftSaved: false
2019-03-11 21:08:25 +08:00
});
2019-03-14 18:14:11 +08:00
}, 3000);
2019-03-11 21:08:25 +08:00
that.timer = null;
2019-04-08 13:51:34 +08:00
}, 60000);
}
2019-03-11 21:08:25 +08:00
}
2019-08-13 10:42:21 +08:00
openParentDirectory = () => {
window.location.href = editorApi.getParentDectionaryUrl();
2019-03-11 21:08:25 +08:00
}
onEdit = (mode) => {
if (mode === 'rich') {
window.seafileEditor.switchToRichTextEditor();
} else if (mode === 'plain') {
window.seafileEditor.switchToPlainTextEditor();
}
2019-03-11 21:08:25 +08:00
}
toggleShareLinkDialog = () => {
this.openDialogs('share_link');
}
onCommentAdded = () => {
this.toggleCancel();
}
addComment = (e) => {
e.stopPropagation();
this.openDialogs('comment');
}
2019-03-15 12:11:32 +08:00
scrollToNode = (node) => {
let url = new URL(window.location.href);
url.set('hash', 'user-content-' + node.text);
window.location.href = url.toString();
}
toggleHistory = () => {
window.location.href = siteRoot + 'repo/file_revisions/' + repoID + '/?p=' + Utils.encodePath(filePath);
2019-03-15 12:11:32 +08:00
}
2019-03-26 11:18:41 +08:00
getInsertLink = (repoID, filePath) => {
2019-04-24 16:37:42 +08:00
const innerURL = serviceUrl + '/lib/' + repoID + '/file' + Utils.encodePath(filePath);
const fileName = Utils.getFileName(filePath);
window.richMarkdownEditor.addLink(fileName, innerURL);
2019-03-26 11:18:41 +08:00
}
2019-04-13 14:51:41 +08:00
onContentChanged = (value) => {
this.setState({ contentChanged: value });
}
onSaving = (value) => {
this.setState({ saving: value });
}
2018-05-02 14:09:58 +08:00
render() {
2019-03-11 21:08:25 +08:00
let component;
2018-05-02 14:09:58 +08:00
if (this.state.loading) {
return (
<div className="empty-loading-page">
<div className="lds-ripple page-centered"><div></div><div></div></div>
</div>
);
} else if (this.state.mode === 'editor') {
component = (
<Fragment>
<MarkdownViewerToolbar
isDocs={isDocs}
hasDraft={hasDraft}
isDraft={isDraft}
editorApi={editorApi}
collabUsers={this.state.collabUsers}
fileInfo={this.state.fileInfo}
toggleStar={this.toggleStar}
2019-08-13 10:42:21 +08:00
openParentDirectory={this.openParentDirectory}
openDialogs={this.openDialogs}
toggleShareLinkDialog={this.toggleShareLinkDialog}
onEdit={this.onEdit}
toggleNewDraft={editorApi.createDraftFile}
showFileHistory={this.state.isShowHistory ? false : true }
toggleHistory={this.toggleHistory}
readOnly={this.state.readOnly}
mode={this.state.mode}
editorMode={this.state.editorMode}
2019-04-13 14:51:41 +08:00
contentChanged={this.state.contentChanged}
saving={this.state.saving}
2019-04-15 12:09:58 +08:00
showDraftSaved={this.state.showDraftSaved}
2019-05-05 12:05:29 +08:00
isLocked={this.state.isLocked}
lockedByMe={this.state.lockedByMe}
toggleLockFile={this.toggleLockFile}
/>
<SeafileEditor
fileInfo={this.state.fileInfo}
markdownContent={this.state.markdownContent}
editorApi={editorApi}
collabUsers={this.state.collabUsers}
setFileInfoMtime={this.setFileInfoMtime}
toggleStar={this.toggleStar}
showFileHistory={true}
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}
2019-04-13 14:51:41 +08:00
onContentChanged={this.onContentChanged}
onSaving={this.onSaving}
contentChanged={this.state.contentChanged}
saving={this.state.saving}
2019-06-19 13:36:57 +08:00
fileTagList={this.state.fileTagList}
relatedFiles={this.state.relatedFiles}
participants={this.state.participants}
onParticipantsChange={this.onParticipantsChange}
2019-07-22 15:26:00 +08:00
markdownLint={fileName.toLowerCase() !== 'index.md'}
/>
</Fragment>
);
2019-03-11 21:08:25 +08:00
return (
<React.Fragment>
2019-04-08 13:51:34 +08:00
{this.state.localDraftDialog ?
2019-03-18 08:40:31 +08:00
<ModalPortal>
<LocalDraftDialog
localDraftDialog={this.state.localDraftDialog}
deleteDraft={this.deleteDraft}
closeDraftDialog={this.closeDraftDialog}
useDraft={this.useDraft}
/>
</ModalPortal>
: null}
2019-03-11 21:08:25 +08:00
{component}
{this.state.showMarkdownEditorDialog && (
2019-02-25 18:17:04 +08:00
<React.Fragment>
2019-03-26 11:18:41 +08:00
{this.state.showInsertFileDialog &&
<ModalPortal>
<InsertFileDialog
repoID={repoID}
filePath={filePath}
toggleCancel={this.toggleCancel}
getInsertLink={this.getInsertLink}
/>
</ModalPortal>
}
2019-04-18 16:42:27 +08:00
{this.state.showInsertRepoImageDialog &&
<ModalPortal>
<InsertRepoImageDialog
repoID={repoID}
filePath={filePath}
toggleCancel={this.toggleCancel}
/>
</ModalPortal>
}
2019-02-25 18:17:04 +08:00
{this.state.showShareLinkDialog &&
<ModalPortal>
<ShareDialog
itemType="file"
itemName={this.state.fileInfo.name}
itemPath={filePath}
repoID={repoID}
toggleDialog={this.toggleCancel}
isGroupOwnedRepo={false}
repoEncrypted={false}
/>
</ModalPortal>
}
2019-06-19 13:36:57 +08:00
{this.state.showEditFileTagDialog &&
<ModalPortal>
<EditFileTagDialog
repoID={repoID}
filePath={filePath}
fileTagList={this.state.fileTagList}
toggleCancel={this.toggleCancel}
onFileTagChanged={this.onFileTagChanged}
/>
</ModalPortal>
}
{this.state.showRelatedFileDialog &&
<ModalPortal>
<RelatedFileDialogs
repoID={repoID}
filePath={filePath}
relatedFiles={this.state.relatedFiles}
toggleCancel={this.toggleCancel}
onRelatedFileChange={this.onRelatedFileChange}
dirent={this.state.fileInfo}
viewMode={this.state.viewMode}
/>
</ModalPortal>
}
{this.state.showFileParticipantDialog &&
<ModalPortal>
<FileParticipantDialog
repoID={repoID}
filePath={filePath}
toggleFileParticipantDialog={this.toggleCancel}
fileParticipantList={this.state.participants}
onParticipantsChange={this.onParticipantsChange}
/>
</ModalPortal>
}
2019-02-25 18:17:04 +08:00
</React.Fragment>
)}
</React.Fragment>
2018-05-02 14:09:58 +08:00
);
2019-07-22 17:09:10 +08:00
}
2018-05-02 14:09:58 +08:00
}
}
2019-03-11 21:08:25 +08:00
export default MarkdownEditor;