mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-17 14:37:58 +00:00
Add excl draw module 2 (#7658)
* init exceldraw module * excalidraw demo * i18n fixed bug * exceldraw change to excalidraw * lang setting --------- Co-authored-by: 杨顺强 <978987373@qq.com> Co-authored-by: First <first@FirstdeMacBook-Pro.local>
This commit is contained in:
parent
ef9be3fed1
commit
ff7fd0f0d5
@ -627,6 +627,12 @@ module.exports = function (webpackEnv) {
|
|||||||
// Make sure to add the new loader(s) before the "file" loader.
|
// Make sure to add the new loader(s) before the "file" loader.
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.m?js$/,
|
||||||
|
resolve: {
|
||||||
|
fullySpecified: false
|
||||||
|
}
|
||||||
|
}
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
@ -2,6 +2,7 @@ const paths = require('./paths');
|
|||||||
|
|
||||||
const entryFiles = {
|
const entryFiles = {
|
||||||
tldrawEditor: '/tldrawEditor.js',
|
tldrawEditor: '/tldrawEditor.js',
|
||||||
|
excalidrawEditor: '/excalidraw-editor.js',
|
||||||
markdownEditor: '/index.js',
|
markdownEditor: '/index.js',
|
||||||
plainMarkdownEditor: '/pages/plain-markdown-editor/index.js',
|
plainMarkdownEditor: '/pages/plain-markdown-editor/index.js',
|
||||||
TCAccept: '/tc-accept.js',
|
TCAccept: '/tc-accept.js',
|
||||||
|
1294
frontend/package-lock.json
generated
1294
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,7 @@
|
|||||||
"@codemirror/view": "^6.34.1",
|
"@codemirror/view": "^6.34.1",
|
||||||
"@emoji-mart/data": "^1.2.1",
|
"@emoji-mart/data": "^1.2.1",
|
||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
|
"@excalidraw/excalidraw": "^0.18.0",
|
||||||
"@gatsbyjs/reach-router": "2.0.1",
|
"@gatsbyjs/reach-router": "2.0.1",
|
||||||
"@seafile/react-image-lightbox": "4.0.2",
|
"@seafile/react-image-lightbox": "4.0.2",
|
||||||
"@seafile/resumablejs": "1.1.16",
|
"@seafile/resumablejs": "1.1.16",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { siteRoot, username, enableSeadoc, thumbnailDefaultSize, thumbnailSizeForOriginal, gettext, fileServerRoot, enableWhiteboard, useGoFileserver } from '../../utils/constants';
|
import { siteRoot, username, enableSeadoc, thumbnailDefaultSize, thumbnailSizeForOriginal, gettext, fileServerRoot, enableWhiteboard, useGoFileserver, enableExcalidraw } from '../../utils/constants';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import URLDecorator from '../../utils/url-decorator';
|
import URLDecorator from '../../utils/url-decorator';
|
||||||
@ -412,6 +412,9 @@ class DirentGridView extends React.Component {
|
|||||||
case 'New Whiteboard File':
|
case 'New Whiteboard File':
|
||||||
this.onCreateFileToggle('.draw');
|
this.onCreateFileToggle('.draw');
|
||||||
break;
|
break;
|
||||||
|
case 'New Excalidraw File':
|
||||||
|
this.onCreateFileToggle('.exdraw');
|
||||||
|
break;
|
||||||
case 'New SeaDoc File':
|
case 'New SeaDoc File':
|
||||||
this.onCreateFileToggle('.sdoc');
|
this.onCreateFileToggle('.sdoc');
|
||||||
break;
|
break;
|
||||||
@ -734,13 +737,15 @@ class DirentGridView extends React.Component {
|
|||||||
if (!['admin', 'rw'].includes(this.props.userPerm)) return;
|
if (!['admin', 'rw'].includes(this.props.userPerm)) return;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
NEW_FOLDER, NEW_FILE,
|
NEW_FOLDER,
|
||||||
|
NEW_FILE,
|
||||||
NEW_MARKDOWN_FILE,
|
NEW_MARKDOWN_FILE,
|
||||||
NEW_EXCEL_FILE,
|
NEW_EXCEL_FILE,
|
||||||
NEW_POWERPOINT_FILE,
|
NEW_POWERPOINT_FILE,
|
||||||
NEW_WORD_FILE,
|
NEW_WORD_FILE,
|
||||||
NEW_SEADOC_FILE,
|
NEW_SEADOC_FILE,
|
||||||
NEW_TLDRAW_FILE
|
NEW_TLDRAW_FILE,
|
||||||
|
NEW_EXCALIDRAW_FILE
|
||||||
} = TextTranslation;
|
} = TextTranslation;
|
||||||
|
|
||||||
let direntsContainerMenuList = [
|
let direntsContainerMenuList = [
|
||||||
@ -763,6 +768,10 @@ class DirentGridView extends React.Component {
|
|||||||
direntsContainerMenuList.push(NEW_TLDRAW_FILE);
|
direntsContainerMenuList.push(NEW_TLDRAW_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enableExcalidraw) {
|
||||||
|
direntsContainerMenuList.push(NEW_EXCALIDRAW_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedDirentList.length === 0) {
|
if (selectedDirentList.length === 0) {
|
||||||
if (!hasCustomPermission('create')) return;
|
if (!hasCustomPermission('create')) return;
|
||||||
this.handleContextClick(event, DIRENT_GRID_CONTAINER_MENU_ID, direntsContainerMenuList);
|
this.handleContextClick(event, DIRENT_GRID_CONTAINER_MENU_ID, direntsContainerMenuList);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { siteRoot, gettext, username, enableSeadoc, thumbnailSizeForOriginal, thumbnailDefaultSize, fileServerRoot, enableWhiteboard, useGoFileserver } from '../../utils/constants';
|
import { siteRoot, gettext, username, enableSeadoc, thumbnailSizeForOriginal, thumbnailDefaultSize, fileServerRoot, enableWhiteboard, useGoFileserver, enableExcalidraw } from '../../utils/constants';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import TextTranslation from '../../utils/text-translation';
|
import TextTranslation from '../../utils/text-translation';
|
||||||
import URLDecorator from '../../utils/url-decorator';
|
import URLDecorator from '../../utils/url-decorator';
|
||||||
@ -431,7 +431,8 @@ class DirentListView extends React.Component {
|
|||||||
NEW_POWERPOINT_FILE,
|
NEW_POWERPOINT_FILE,
|
||||||
NEW_WORD_FILE,
|
NEW_WORD_FILE,
|
||||||
NEW_SEADOC_FILE,
|
NEW_SEADOC_FILE,
|
||||||
NEW_TLDRAW_FILE
|
NEW_TLDRAW_FILE,
|
||||||
|
NEW_EXCALIDRAW_FILE,
|
||||||
} = TextTranslation;
|
} = TextTranslation;
|
||||||
|
|
||||||
const direntsContainerMenuList = [
|
const direntsContainerMenuList = [
|
||||||
@ -452,6 +453,10 @@ class DirentListView extends React.Component {
|
|||||||
direntsContainerMenuList.push(NEW_TLDRAW_FILE);
|
direntsContainerMenuList.push(NEW_TLDRAW_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enableExcalidraw) {
|
||||||
|
direntsContainerMenuList.push(NEW_EXCALIDRAW_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.props.selectedDirentList.length === 0) {
|
if (this.props.selectedDirentList.length === 0) {
|
||||||
let id = 'dirent-container-menu';
|
let id = 'dirent-container-menu';
|
||||||
|
|
||||||
@ -529,6 +534,9 @@ class DirentListView extends React.Component {
|
|||||||
case 'New Whiteboard File':
|
case 'New Whiteboard File':
|
||||||
this.onCreateFileToggle('.draw');
|
this.onCreateFileToggle('.draw');
|
||||||
break;
|
break;
|
||||||
|
case 'New Excalidraw File':
|
||||||
|
this.onCreateFileToggle('.exdraw');
|
||||||
|
break;
|
||||||
case 'New SeaDoc File':
|
case 'New SeaDoc File':
|
||||||
this.onCreateFileToggle('.sdoc');
|
this.onCreateFileToggle('.sdoc');
|
||||||
break;
|
break;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { enableSeadoc, gettext, enableWhiteboard } from '../../utils/constants';
|
import { enableSeadoc, gettext, enableWhiteboard, enableExcalidraw } from '../../utils/constants';
|
||||||
import Loading from '../loading';
|
import Loading from '../loading';
|
||||||
import ModalPortal from '../modal-portal';
|
import ModalPortal from '../modal-portal';
|
||||||
import CreateFile from '../../components/dialog/create-file-dialog';
|
import CreateFile from '../../components/dialog/create-file-dialog';
|
||||||
@ -74,7 +74,8 @@ class DirentNoneView extends React.Component {
|
|||||||
NEW_POWERPOINT_FILE,
|
NEW_POWERPOINT_FILE,
|
||||||
NEW_WORD_FILE,
|
NEW_WORD_FILE,
|
||||||
NEW_SEADOC_FILE,
|
NEW_SEADOC_FILE,
|
||||||
NEW_TLDRAW_FILE
|
NEW_TLDRAW_FILE,
|
||||||
|
NEW_EXCALIDRAW_FILE
|
||||||
} = TextTranslation;
|
} = TextTranslation;
|
||||||
const direntsContainerMenuList = [
|
const direntsContainerMenuList = [
|
||||||
NEW_FOLDER, NEW_FILE, 'Divider',
|
NEW_FOLDER, NEW_FILE, 'Divider',
|
||||||
@ -92,6 +93,9 @@ class DirentNoneView extends React.Component {
|
|||||||
if (enableWhiteboard) {
|
if (enableWhiteboard) {
|
||||||
direntsContainerMenuList.push(NEW_TLDRAW_FILE);
|
direntsContainerMenuList.push(NEW_TLDRAW_FILE);
|
||||||
}
|
}
|
||||||
|
if (enableExcalidraw) {
|
||||||
|
direntsContainerMenuList.push(NEW_EXCALIDRAW_FILE);
|
||||||
|
}
|
||||||
let id = 'dirent-container-menu';
|
let id = 'dirent-container-menu';
|
||||||
if (isCustomPermission) {
|
if (isCustomPermission) {
|
||||||
const { create: canCreate } = customPermission.permission;
|
const { create: canCreate } = customPermission.permission;
|
||||||
|
@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import { enableSeadoc, enableWhiteboard, gettext } from '../../utils/constants';
|
import { enableExcalidraw, enableSeadoc, enableWhiteboard, gettext } from '../../utils/constants';
|
||||||
import ModalPortal from '../modal-portal';
|
import ModalPortal from '../modal-portal';
|
||||||
import CreateFolder from '../../components/dialog/create-folder-dialog';
|
import CreateFolder from '../../components/dialog/create-folder-dialog';
|
||||||
import CreateFile from '../../components/dialog/create-file-dialog';
|
import CreateFile from '../../components/dialog/create-file-dialog';
|
||||||
@ -115,6 +115,13 @@ class DirOperationToolbar extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onCreateExcalidrawToggle = () => {
|
||||||
|
this.setState({
|
||||||
|
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
|
||||||
|
fileType: '.exdraw'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onCreateSeaDocToggle = () => {
|
onCreateSeaDocToggle = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
|
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
|
||||||
@ -266,6 +273,9 @@ class DirOperationToolbar extends React.Component {
|
|||||||
if (enableWhiteboard) {
|
if (enableWhiteboard) {
|
||||||
newSubOpList.push({ 'text': gettext('New Whiteboard File'), 'onClick': this.onCreateTldrawToggle });
|
newSubOpList.push({ 'text': gettext('New Whiteboard File'), 'onClick': this.onCreateTldrawToggle });
|
||||||
}
|
}
|
||||||
|
if (enableExcalidraw) {
|
||||||
|
newSubOpList.push({ 'text': gettext('New Excalidraw File'), 'onClick': this.onCreateExcalidrawToggle });
|
||||||
|
}
|
||||||
opList.push({
|
opList.push({
|
||||||
'icon': 'new',
|
'icon': 'new',
|
||||||
'text': gettext('New'),
|
'text': gettext('New'),
|
||||||
|
11
frontend/src/excalidraw-editor.js
Normal file
11
frontend/src/excalidraw-editor.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React, { Suspense } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import Loading from './components/loading';
|
||||||
|
import ExcaliEditor from './pages/excalidraw-editor';
|
||||||
|
|
||||||
|
const root = createRoot(document.getElementById('wrapper'));
|
||||||
|
root.render(
|
||||||
|
<Suspense fallback={<Loading />}>
|
||||||
|
<ExcaliEditor />
|
||||||
|
</Suspense>
|
||||||
|
);
|
15
frontend/src/pages/excalidraw-editor/constants.js
Normal file
15
frontend/src/pages/excalidraw-editor/constants.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export const SAVE_INTERVAL_TIME = 3 * 60 * 1000;
|
||||||
|
|
||||||
|
export const langList = {
|
||||||
|
'zh-cn': 'zh-CN',
|
||||||
|
'en': 'en',
|
||||||
|
'zh-tw': 'zh-TW',
|
||||||
|
'ru': 'ru-RU',
|
||||||
|
'it': 'it-IT',
|
||||||
|
'fr': 'fr-FR',
|
||||||
|
'es-ms': 'en',
|
||||||
|
'es-ar': 'en',
|
||||||
|
'es': 'es-ES',
|
||||||
|
'de': 'de-DE',
|
||||||
|
'cs': 'cs-CZ',
|
||||||
|
};
|
26
frontend/src/pages/excalidraw-editor/editor-api.js
Normal file
26
frontend/src/pages/excalidraw-editor/editor-api.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import { Utils } from '../../utils/utils';
|
||||||
|
|
||||||
|
const { repoID, filePath, fileName } = window.app.pageOptions;
|
||||||
|
let dirPath = Utils.getDirName(filePath);
|
||||||
|
|
||||||
|
class EditorApi {
|
||||||
|
|
||||||
|
saveContent(content) {
|
||||||
|
return seafileAPI.getUpdateLink(repoID, dirPath).then((res) => {
|
||||||
|
const uploadLink = res.data;
|
||||||
|
return seafileAPI.updateFile(uploadLink, filePath, fileName, content);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileContent = () => {
|
||||||
|
return seafileAPI.getFileDownloadLink(repoID, filePath).then(res => {
|
||||||
|
const downLoadUrl = res.data;
|
||||||
|
return seafileAPI.getFileContent(downLoadUrl);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const editorApi = new EditorApi();
|
||||||
|
|
||||||
|
export default editorApi;
|
8
frontend/src/pages/excalidraw-editor/index.css
Normal file
8
frontend/src/pages/excalidraw-editor/index.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.dropdown-menu {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excalidraw .dropdown-menu {
|
||||||
|
--bs-dropdown-padding-x: auto;
|
||||||
|
border: unset;
|
||||||
|
}
|
86
frontend/src/pages/excalidraw-editor/index.js
Normal file
86
frontend/src/pages/excalidraw-editor/index.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import React, { useCallback, useState, useEffect, useRef } from 'react';
|
||||||
|
import SimpleEditor from './simple-editor';
|
||||||
|
import editorApi from './editor-api';
|
||||||
|
import isHotkey from 'is-hotkey';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
import toaster from '../../components/toast';
|
||||||
|
import { SAVE_INTERVAL_TIME } from './constants';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
const ExcaliEditor = () => {
|
||||||
|
const [fileContent, setFileContent] = useState(null);
|
||||||
|
const editorRef = useRef(null);
|
||||||
|
const isChangedRef = useRef(false);
|
||||||
|
const [isFetching, setIsFetching] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
editorApi.getFileContent().then(res => {
|
||||||
|
if (res.data?.appState?.collaborators && !Array.isArray(res.data.appState.collaborators)) {
|
||||||
|
// collaborators.forEach is not a function
|
||||||
|
res.data['appState']['collaborators'] = [];
|
||||||
|
}
|
||||||
|
setFileContent(res.data);
|
||||||
|
setIsFetching(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const saveSceneContent = useCallback(async () => {
|
||||||
|
if (isChangedRef.current) {
|
||||||
|
try {
|
||||||
|
await editorApi.saveContent(JSON.stringify(editorRef.current));
|
||||||
|
isChangedRef.current = false;
|
||||||
|
toaster.success(gettext('Successfully saved'), { duration: 2 });
|
||||||
|
} catch {
|
||||||
|
toaster.danger(gettext('Failed to save'), { duration: 2 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleHotkeySave = (event) => {
|
||||||
|
if (isHotkey('mod+s', event)) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('keydown', handleHotkeySave, true);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleHotkeySave, true);
|
||||||
|
};
|
||||||
|
}, [saveSceneContent]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const saveInterval = setInterval(() => {
|
||||||
|
if (isChangedRef.current) {
|
||||||
|
editorApi.saveContent(JSON.stringify(editorRef.current)).then(res => {
|
||||||
|
isChangedRef.current = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, SAVE_INTERVAL_TIME);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(saveInterval);
|
||||||
|
};
|
||||||
|
}, [saveSceneContent]);
|
||||||
|
|
||||||
|
const onSaveContent = useCallback(() => {
|
||||||
|
saveSceneContent();
|
||||||
|
}, [saveSceneContent]);
|
||||||
|
|
||||||
|
const onChangeContent = useCallback((elements) => {
|
||||||
|
editorRef.current = { elements };
|
||||||
|
isChangedRef.current = true;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SimpleEditor
|
||||||
|
isFetching={isFetching}
|
||||||
|
sceneContent={fileContent}
|
||||||
|
onSaveContent={onSaveContent}
|
||||||
|
onChangeContent={onChangeContent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExcaliEditor;
|
75
frontend/src/pages/excalidraw-editor/simple-editor.js
Normal file
75
frontend/src/pages/excalidraw-editor/simple-editor.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Excalidraw, MainMenu } from '@excalidraw/excalidraw';
|
||||||
|
import isHotkey from 'is-hotkey';
|
||||||
|
import CodeMirrorLoading from '../../components/code-mirror-loading';
|
||||||
|
import { langList } from './constants';
|
||||||
|
|
||||||
|
import '@excalidraw/excalidraw/index.css';
|
||||||
|
|
||||||
|
const SimpleEditor = ({
|
||||||
|
sceneContent = null,
|
||||||
|
onChangeContent,
|
||||||
|
onSaveContent,
|
||||||
|
isFetching
|
||||||
|
}) => {
|
||||||
|
const [excalidrawAPI, setExcalidrawAPI] = useState(null);
|
||||||
|
const UIOptions = {
|
||||||
|
canvasActions: {
|
||||||
|
saveToActiveFile: false,
|
||||||
|
LoadScene: false
|
||||||
|
},
|
||||||
|
tools: { image: false },
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = () => {
|
||||||
|
const elements = excalidrawAPI.getSceneElements();
|
||||||
|
onChangeContent(elements);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleHotkeySave = (event) => {
|
||||||
|
if (isHotkey('mod+s', event)) {
|
||||||
|
event.preventDefault();
|
||||||
|
onSaveContent(excalidrawAPI.getSceneElements());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('keydown', handleHotkeySave, true);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleHotkeySave, true);
|
||||||
|
};
|
||||||
|
}, [excalidrawAPI, onSaveContent]);
|
||||||
|
|
||||||
|
if (isFetching) {
|
||||||
|
return (
|
||||||
|
<div className='excali-container'>
|
||||||
|
<CodeMirrorLoading />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='excali-container' style={{ height: '100vh', width: '100vw' }}>
|
||||||
|
<Excalidraw
|
||||||
|
initialData={sceneContent}
|
||||||
|
excalidrawAPI={(api) => setExcalidrawAPI(api)}
|
||||||
|
onChange={handleChange}
|
||||||
|
UIOptions={UIOptions}
|
||||||
|
langCode={langList[window.app.config.lang] || 'en'}
|
||||||
|
>
|
||||||
|
<MainMenu>
|
||||||
|
<MainMenu.DefaultItems.Export />
|
||||||
|
<MainMenu.DefaultItems.SaveAsImage />
|
||||||
|
<MainMenu.DefaultItems.Help />
|
||||||
|
<MainMenu.DefaultItems.ClearCanvas />
|
||||||
|
<MainMenu.DefaultItems.ToggleTheme />
|
||||||
|
<MainMenu.DefaultItems.ChangeCanvasBackground />
|
||||||
|
</MainMenu>
|
||||||
|
</Excalidraw>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SimpleEditor;
|
@ -99,6 +99,7 @@ export const enableSSOToThirdpartWebsite = window.app.pageOptions.enableSSOToThi
|
|||||||
export const enableSeadoc = window.app.pageOptions.enableSeadoc;
|
export const enableSeadoc = window.app.pageOptions.enableSeadoc;
|
||||||
export const enableSeafileAI = window.app.pageOptions.enableSeafileAI;
|
export const enableSeafileAI = window.app.pageOptions.enableSeafileAI;
|
||||||
export const enableWhiteboard = window.app.pageOptions.enableWhiteboard;
|
export const enableWhiteboard = window.app.pageOptions.enableWhiteboard;
|
||||||
|
export const enableExcalidraw = window.app.pageOptions.enableExcalidraw;
|
||||||
export const enableMultipleOfficeSuite = window.app.pageOptions.enableMultipleOfficeSuite;
|
export const enableMultipleOfficeSuite = window.app.pageOptions.enableMultipleOfficeSuite;
|
||||||
|
|
||||||
export const curNoteMsg = window.app.pageOptions.curNoteMsg;
|
export const curNoteMsg = window.app.pageOptions.curNoteMsg;
|
||||||
|
@ -31,6 +31,10 @@ const TextTranslation = {
|
|||||||
key: 'New Whiteboard File',
|
key: 'New Whiteboard File',
|
||||||
value: gettext('New Whiteboard File')
|
value: gettext('New Whiteboard File')
|
||||||
},
|
},
|
||||||
|
NEW_EXCALIDRAW_FILE: {
|
||||||
|
key: 'New Excalidraw File',
|
||||||
|
value: gettext('New Excalidraw File')
|
||||||
|
},
|
||||||
NEW_SEADOC_FILE: {
|
NEW_SEADOC_FILE: {
|
||||||
key: 'New SeaDoc File',
|
key: 'New SeaDoc File',
|
||||||
value: gettext('New SeaDoc File')
|
value: gettext('New SeaDoc File')
|
||||||
|
@ -25,7 +25,7 @@ from seahub.settings import SEAFILE_VERSION, SITE_DESCRIPTION, \
|
|||||||
LOGIN_BG_IMAGE_PATH, THUMBNAIL_DEFAULT_SIZE, \
|
LOGIN_BG_IMAGE_PATH, THUMBNAIL_DEFAULT_SIZE, \
|
||||||
CUSTOM_LOGIN_BG_PATH, ENABLE_SHARE_LINK_REPORT_ABUSE, \
|
CUSTOM_LOGIN_BG_PATH, ENABLE_SHARE_LINK_REPORT_ABUSE, \
|
||||||
PRIVACY_POLICY_LINK, TERMS_OF_SERVICE_LINK, ENABLE_SEADOC, THUMBNAIL_SIZE_FOR_GRID, \
|
PRIVACY_POLICY_LINK, TERMS_OF_SERVICE_LINK, ENABLE_SEADOC, THUMBNAIL_SIZE_FOR_GRID, \
|
||||||
FILE_SERVER_ROOT, ENABLE_WHITEBOARD, ENABLE_SEAFILE_AI
|
FILE_SERVER_ROOT, ENABLE_WHITEBOARD, ENABLE_SEAFILE_AI, ENABLE_EXCALIDRAW
|
||||||
|
|
||||||
from seahub.organizations.models import OrgAdminSettings
|
from seahub.organizations.models import OrgAdminSettings
|
||||||
from seahub.organizations.settings import ORG_ENABLE_ADMIN_CUSTOM_LOGO
|
from seahub.organizations.settings import ORG_ENABLE_ADMIN_CUSTOM_LOGO
|
||||||
@ -181,6 +181,7 @@ def base(request):
|
|||||||
'enable_seadoc': ENABLE_SEADOC,
|
'enable_seadoc': ENABLE_SEADOC,
|
||||||
'enable_seafile_ai': ENABLE_SEAFILE_AI,
|
'enable_seafile_ai': ENABLE_SEAFILE_AI,
|
||||||
'enable_whiteboard': ENABLE_WHITEBOARD,
|
'enable_whiteboard': ENABLE_WHITEBOARD,
|
||||||
|
'enable_excalidraw': ENABLE_EXCALIDRAW,
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.user.is_staff:
|
if request.user.is_staff:
|
||||||
|
@ -961,6 +961,12 @@ FILE_CONVERTER_SERVER_URL = 'http://127.0.0.1:8888'
|
|||||||
|
|
||||||
ENABLE_WHITEBOARD = False
|
ENABLE_WHITEBOARD = False
|
||||||
|
|
||||||
|
##########################
|
||||||
|
# Settings for excalidraw #
|
||||||
|
##########################
|
||||||
|
|
||||||
|
ENABLE_EXCALIDRAW = False
|
||||||
|
|
||||||
############################
|
############################
|
||||||
# Settings for Seahub Priv #
|
# Settings for Seahub Priv #
|
||||||
############################
|
############################
|
||||||
|
@ -159,6 +159,7 @@
|
|||||||
officeWebAppEditFileExtension: {% if office_web_app_edit_file_extension %} {{office_web_app_edit_file_extension|safe}} {% else %} [] {% endif %},
|
officeWebAppEditFileExtension: {% if office_web_app_edit_file_extension %} {{office_web_app_edit_file_extension|safe}} {% else %} [] {% endif %},
|
||||||
enableSeadoc: {% if enable_seadoc %} true {% else %} false {% endif %},
|
enableSeadoc: {% if enable_seadoc %} true {% else %} false {% endif %},
|
||||||
enableWhiteboard: {% if enable_whiteboard %} true {% else %} false {% endif %},
|
enableWhiteboard: {% if enable_whiteboard %} true {% else %} false {% endif %},
|
||||||
|
enableExcalidraw: {% if enable_excalidraw %} true {% else %} false {% endif %},
|
||||||
isOrgContext: {% if org is not None %} true {% else %} false {% endif %},
|
isOrgContext: {% if org is not None %} true {% else %} false {% endif %},
|
||||||
enableMetadataManagement: {% if enable_metadata_management %} true {% else %} false {% endif %},
|
enableMetadataManagement: {% if enable_metadata_management %} true {% else %} false {% endif %},
|
||||||
isMultiTenacy: {% if multi_tenancy %} true {% else %} false {% endif %},
|
isMultiTenacy: {% if multi_tenancy %} true {% else %} false {% endif %},
|
||||||
|
19
seahub/templates/excalidraw_file_view_react.html
Normal file
19
seahub/templates/excalidraw_file_view_react.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{% extends 'file_view_react.html' %}
|
||||||
|
{% load render_bundle from webpack_loader %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block extra_style %}
|
||||||
|
{% render_bundle 'excalidrawEditor' 'css' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_data %}
|
||||||
|
docPath: '{{ path|escapejs }}',
|
||||||
|
docName: '{{ filename|escapejs }}',
|
||||||
|
docUuid: '{{ file_uuid }}',
|
||||||
|
lang: '{{ lang }}',
|
||||||
|
rawPath: '{{ raw_path|escapejs }}',
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block render_bundle %}
|
||||||
|
{% render_bundle 'excalidrawEditor' 'js' %}
|
||||||
|
{% endblock %}
|
@ -144,6 +144,7 @@ PREVIEW_FILEEXT = {
|
|||||||
XMIND: ('xmind',),
|
XMIND: ('xmind',),
|
||||||
SEADOC: ('sdoc',),
|
SEADOC: ('sdoc',),
|
||||||
TLDRAW: ('draw',),
|
TLDRAW: ('draw',),
|
||||||
|
EXCALIDRAW: ('exdraw',),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_non_sdoc_file_exts():
|
def get_non_sdoc_file_exts():
|
||||||
|
@ -11,6 +11,7 @@ SPREADSHEET = 'SpreadSheet'
|
|||||||
XMIND = 'XMind'
|
XMIND = 'XMind'
|
||||||
SEADOC = 'SDoc'
|
SEADOC = 'SDoc'
|
||||||
TLDRAW = 'Tldraw'
|
TLDRAW = 'Tldraw'
|
||||||
|
EXCALIDRAW = 'Excalidraw'
|
||||||
|
|
||||||
|
|
||||||
MARKDOWN_SUPPORT_CONVERT_TYPES = ['sdoc']
|
MARKDOWN_SUPPORT_CONVERT_TYPES = ['sdoc']
|
||||||
|
@ -61,7 +61,7 @@ from seahub.utils import render_error, is_org_context, \
|
|||||||
from seahub.utils.ip import get_remote_ip
|
from seahub.utils.ip import get_remote_ip
|
||||||
from seahub.utils.file_types import (IMAGE, PDF, SVG,
|
from seahub.utils.file_types import (IMAGE, PDF, SVG,
|
||||||
DOCUMENT, SPREADSHEET, AUDIO,
|
DOCUMENT, SPREADSHEET, AUDIO,
|
||||||
MARKDOWN, TEXT, VIDEO, XMIND, SEADOC, TLDRAW)
|
MARKDOWN, TEXT, VIDEO, XMIND, SEADOC, TLDRAW, EXCALIDRAW)
|
||||||
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
|
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
|
||||||
from seahub.utils.star import is_file_starred
|
from seahub.utils.star import is_file_starred
|
||||||
from seahub.utils.http import json_response, \
|
from seahub.utils.http import json_response, \
|
||||||
@ -850,6 +850,27 @@ def view_lib_file(request, repo_id, path):
|
|||||||
return_dict['raw_path'] = raw_path
|
return_dict['raw_path'] = raw_path
|
||||||
|
|
||||||
|
|
||||||
|
can_edit_file = True
|
||||||
|
if parse_repo_perm(permission).can_edit_on_web is False:
|
||||||
|
can_edit_file = False
|
||||||
|
elif is_locked and not locked_by_me:
|
||||||
|
can_edit_file = False
|
||||||
|
return_dict['can_edit_file'] = can_edit_file
|
||||||
|
|
||||||
|
return render(request, template, return_dict)
|
||||||
|
|
||||||
|
if filetype == EXCALIDRAW:
|
||||||
|
|
||||||
|
return_dict['protocol'] = request.is_secure() and 'https' or 'http'
|
||||||
|
return_dict['domain'] = get_current_site(request).domain
|
||||||
|
return_dict['serviceUrl'] = get_service_url().rstrip('/')
|
||||||
|
return_dict['language_code'] = get_language()
|
||||||
|
return_dict['share_link_expire_days_Default'] = SHARE_LINK_EXPIRE_DAYS_DEFAULT
|
||||||
|
return_dict['share_link_expire_days_min'] = SHARE_LINK_EXPIRE_DAYS_MIN
|
||||||
|
return_dict['share_link_expire_days_max'] = SHARE_LINK_EXPIRE_DAYS_MAX
|
||||||
|
return_dict['raw_path'] = raw_path
|
||||||
|
|
||||||
|
|
||||||
can_edit_file = True
|
can_edit_file = True
|
||||||
if parse_repo_perm(permission).can_edit_on_web is False:
|
if parse_repo_perm(permission).can_edit_on_web is False:
|
||||||
can_edit_file = False
|
can_edit_file = False
|
||||||
|
Loading…
Reference in New Issue
Block a user