mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-15 06:44:16 +00:00
Add sdoc viewer (#5867)
* add seafile sdoc viewer * optimize code * optimize code * update sdoc-editor version * optimize code
This commit is contained in:
14
frontend/package-lock.json
generated
14
frontend/package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
"@gatsbyjs/reach-router": "1.3.9",
|
||||
"@seafile/react-image-lightbox": "2.0.2",
|
||||
"@seafile/resumablejs": "1.1.16",
|
||||
"@seafile/sdoc-editor": "0.4.4",
|
||||
"@seafile/sdoc-editor": "0.4.6",
|
||||
"@seafile/seafile-calendar": "0.0.12",
|
||||
"@seafile/seafile-editor": "1.0.16",
|
||||
"@uiw/codemirror-extensions-langs": "^4.19.4",
|
||||
@@ -4607,9 +4607,9 @@
|
||||
"integrity": "sha512-8rBbmAEuuwOAGHYGCtEzpx+bxAcGS+V30otMmhRe7bPAdh4E57RWgCa8x7pkzHGFlY1t5d+ILz1gojvPVMYQig=="
|
||||
},
|
||||
"node_modules/@seafile/sdoc-editor": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sdoc-editor/-/sdoc-editor-0.4.4.tgz",
|
||||
"integrity": "sha512-S4Z8SZ+P7sSSv6QIgrAM69UZkXLRS+PKaRpIuT1eG/zFJH6VMKfZ/8MrT7jiwxcjUst/Z0UHnuw9YA8M40nu1Q==",
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sdoc-editor/-/sdoc-editor-0.4.6.tgz",
|
||||
"integrity": "sha512-FmSG9n5WOxJO/oglJcKl2gezE2C18krLp60LAYaixRevdKwyR8JZvKth/3o5dUWTSAeBOUPnVGNAwHzhacbfMw==",
|
||||
"dependencies": {
|
||||
"@seafile/react-image-lightbox": "2.0.4",
|
||||
"@seafile/slate": "0.91.8",
|
||||
@@ -31643,9 +31643,9 @@
|
||||
"integrity": "sha512-8rBbmAEuuwOAGHYGCtEzpx+bxAcGS+V30otMmhRe7bPAdh4E57RWgCa8x7pkzHGFlY1t5d+ILz1gojvPVMYQig=="
|
||||
},
|
||||
"@seafile/sdoc-editor": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sdoc-editor/-/sdoc-editor-0.4.4.tgz",
|
||||
"integrity": "sha512-S4Z8SZ+P7sSSv6QIgrAM69UZkXLRS+PKaRpIuT1eG/zFJH6VMKfZ/8MrT7jiwxcjUst/Z0UHnuw9YA8M40nu1Q==",
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sdoc-editor/-/sdoc-editor-0.4.6.tgz",
|
||||
"integrity": "sha512-FmSG9n5WOxJO/oglJcKl2gezE2C18krLp60LAYaixRevdKwyR8JZvKth/3o5dUWTSAeBOUPnVGNAwHzhacbfMw==",
|
||||
"requires": {
|
||||
"@seafile/react-image-lightbox": "2.0.4",
|
||||
"@seafile/slate": "0.91.8",
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"@gatsbyjs/reach-router": "1.3.9",
|
||||
"@seafile/react-image-lightbox": "2.0.2",
|
||||
"@seafile/resumablejs": "1.1.16",
|
||||
"@seafile/sdoc-editor": "0.4.4",
|
||||
"@seafile/sdoc-editor": "0.4.6",
|
||||
"@seafile/seafile-calendar": "0.0.12",
|
||||
"@seafile/seafile-editor": "1.0.16",
|
||||
"@uiw/codemirror-extensions-langs": "^4.19.4",
|
||||
|
162
frontend/src/components/sdoc-wiki-page-viewer/index.js
Normal file
162
frontend/src/components/sdoc-wiki-page-viewer/index.js
Normal file
@@ -0,0 +1,162 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { WikiViewer } from '@seafile/sdoc-editor';
|
||||
import { appAvatarURL, assetsUrl, gettext, name, repoID, serviceURL, sharedToken, siteRoot, slug, username } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import Loading from '../loading';
|
||||
|
||||
import './style.css';
|
||||
|
||||
const propTypes = {
|
||||
isWiki: PropTypes.bool,
|
||||
path: PropTypes.string,
|
||||
repoID: PropTypes.string,
|
||||
isTOCShow: PropTypes.bool,
|
||||
children: PropTypes.object,
|
||||
isFileLoading: PropTypes.bool.isRequired,
|
||||
containerClassName: PropTypes.string,
|
||||
markdownContent: PropTypes.string.isRequired,
|
||||
latestContributor: PropTypes.string.isRequired,
|
||||
lastModified: PropTypes.string.isRequired,
|
||||
onLinkClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class SdocWikiPageViewer extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.scrollRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// const eventBus = EventBus.getInstance();
|
||||
// this.unsubscribeLinkClick = eventBus.subscribe(EXTERNAL_EVENTS.ON_LINK_CLICK, this.onLinkClick);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// this.unsubscribeLinkClick();
|
||||
}
|
||||
|
||||
onLinkClick = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
let link = '';
|
||||
let target = event.target;
|
||||
while (!target.dataset || !target.dataset.url) {
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (!target) return;
|
||||
link = target.dataset.url;
|
||||
this.props.onLinkClick(link);
|
||||
};
|
||||
|
||||
changeInlineNode = (item) => {
|
||||
const { repoID } = this.props;
|
||||
let url, imagePath;
|
||||
// isPublicWiki: in the old version, only public wiki need replace image url
|
||||
if (item.type == 'image') { // change image url
|
||||
url = item.data.src;
|
||||
const re = new RegExp(serviceURL + '/lib/' + repoID +'/file.*raw=1');
|
||||
// different repo
|
||||
if (re.test(url)) {
|
||||
// get image path
|
||||
let index = url.indexOf('/file');
|
||||
let index2 = url.indexOf('?');
|
||||
imagePath = url.substring(index + 5, index2);
|
||||
} else if (/^\.\.\/*/.test(url) || /^\.\/*/.test(url)) {
|
||||
const path = this.props.path;
|
||||
const originalPath = path.slice(0, path.lastIndexOf('/')) + '/' + url;
|
||||
imagePath = Utils.pathNormalize(originalPath);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
item.data.src = serviceURL + '/view-image-via-public-wiki/?slug=' + slug + '&path=' + imagePath;
|
||||
} else if (item.type == 'link') { // change link url
|
||||
url = item.url;
|
||||
if (Utils.isInternalFileLink(url, repoID)) { // change file url
|
||||
if (Utils.isInternalMarkdownLink(url, repoID)) {
|
||||
let path = Utils.getPathFromInternalMarkdownLink(url, repoID);
|
||||
// replace url
|
||||
item.url = serviceURL + '/published/' + slug + path;
|
||||
} else {
|
||||
item.url = url.replace(/(.*)lib\/([-0-9a-f]{36})\/file(.*)/g, (match, p1, p2, p3) => {
|
||||
return `${p1}d/${sharedToken}/files/?p=${p3}&dl=1`;
|
||||
});
|
||||
}
|
||||
} else if (Utils.isInternalDirLink(url, repoID)) { // change dir url
|
||||
let path = Utils.getPathFromInternalDirLink(url, repoID);
|
||||
// replace url
|
||||
item.url = serviceURL + '/published/' + slug + path;
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
modifyValueBeforeRender = (value) => {
|
||||
let newNodes = Utils.changeMarkdownNodes(value, this.changeInlineNode);
|
||||
return newNodes;
|
||||
};
|
||||
|
||||
renderMarkdown = () => {
|
||||
let { isTOCShow, markdownContent } = this.props;
|
||||
if (!markdownContent) return null;
|
||||
|
||||
let document = JSON.parse(markdownContent);
|
||||
// if (isWiki) {
|
||||
// const newChildren = this.modifyValueBeforeRender(document.children);
|
||||
// document.children = newChildren;
|
||||
// }
|
||||
|
||||
if (!window.seafile) {
|
||||
window.seafile = {
|
||||
serviceUrl: serviceURL,
|
||||
username: username,
|
||||
name: name,
|
||||
avatarURL: appAvatarURL,
|
||||
repoID: repoID,
|
||||
siteRoot: siteRoot,
|
||||
assetsUrl: assetsUrl,
|
||||
};
|
||||
}
|
||||
|
||||
const props = {
|
||||
document: document,
|
||||
showOutline: isTOCShow,
|
||||
scrollRef: this.scrollRef,
|
||||
};
|
||||
|
||||
return <WikiViewer {...props} />;
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.props.isFileLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
const { isWiki, containerClassName = '' } = this.props;
|
||||
const containerClass = `wiki-page-container ${containerClassName}`;
|
||||
// In dir-column-file width is 100%;
|
||||
// In wiki-viewer width isn't 100%
|
||||
const contentClassName = `wiki-page-content ${!isWiki ? + 'w-100' : ''}`;
|
||||
return (
|
||||
<div ref={this.scrollRef} className={containerClass}>
|
||||
<div className={contentClassName}>
|
||||
{this.props.children}
|
||||
{this.renderMarkdown()}
|
||||
<p id="wiki-page-last-modified">{gettext('Last modified by')} {this.props.latestContributor}, <span>{this.props.lastModified}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
isWiki: false,
|
||||
isTOCShow: true,
|
||||
};
|
||||
|
||||
SdocWikiPageViewer.propTypes = propTypes;
|
||||
SdocWikiPageViewer.defaultProps = defaultProps;
|
||||
|
||||
export default SdocWikiPageViewer;
|
25
frontend/src/components/sdoc-wiki-page-viewer/style.css
Normal file
25
frontend/src/components/sdoc-wiki-page-viewer/style.css
Normal file
@@ -0,0 +1,25 @@
|
||||
.wiki-page-container .sdoc-wiki-scroll-container {
|
||||
background-color: #fff !important;
|
||||
padding: 0px !important;
|
||||
overflow: inherit;
|
||||
}
|
||||
|
||||
.wiki-page-container .sdoc-wiki-scroll-container .sdoc-article-container {
|
||||
margin: 0 auto !important;
|
||||
}
|
||||
|
||||
.wiki-page-container .sdoc-article-container .article .sdoc-header-title {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.wiki-page-container .sdoc-article-container .article {
|
||||
margin: 0;
|
||||
padding: 0 10px;
|
||||
max-width: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.wiki-page-container .sdoc-wiki-outline-container {
|
||||
top: 79px;
|
||||
width: 200px !important;
|
||||
}
|
@@ -71,12 +71,17 @@ class Wiki extends Component {
|
||||
}
|
||||
|
||||
// load dir list
|
||||
initialPath = isDir === 'None' ? '/' : initialPath;
|
||||
initialPath = (isDir === 'None' || Utils.isSdocFile(initialPath)) ? '/' : initialPath;
|
||||
this.loadNodeAndParentsByPath(initialPath);
|
||||
};
|
||||
|
||||
loadWikiData = (initialPath) => {
|
||||
this.pythonWrapper = document.getElementById('wiki-file-content');
|
||||
if (isDir === 'False' && Utils.isSdocFile(initialPath)) {
|
||||
this.showDir('/');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDir === 'False') {
|
||||
// this.showFile(initialPath);
|
||||
this.setState({path: initialPath});
|
||||
@@ -400,7 +405,7 @@ class Wiki extends Component {
|
||||
if (node.object.isDir()) { // isDir
|
||||
this.showDir(node.path);
|
||||
} else {
|
||||
if (Utils.isMarkdownFile(node.path)) {
|
||||
if (Utils.isMarkdownFile(node.path) || Utils.isSdocFile(node.path)) {
|
||||
if (node.path !== this.state.path) {
|
||||
this.showFile(node.path);
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import { Utils } from '../../utils/utils';
|
||||
import Search from '../../components/search/search';
|
||||
import Notification from '../../components/common/notification';
|
||||
import Account from '../../components/common/account';
|
||||
import SdocWikiPageViewer from '../../components/sdoc-wiki-page-viewer';
|
||||
|
||||
const propTypes = {
|
||||
path: PropTypes.string.isRequired,
|
||||
@@ -120,7 +121,7 @@ class MainPanel extends Component {
|
||||
<div className={`cur-view-content ${isViewingFile ? 'o-hidden' : ''}`}>
|
||||
{!this.props.pathExist && errMessage}
|
||||
{this.props.pathExist && this.props.isDataLoading && <Loading />}
|
||||
{isViewingFile && (
|
||||
{isViewingFile && Utils.isMarkdownFile(this.props.path) && (
|
||||
<SeafileMarkdownViewer
|
||||
isWiki={true}
|
||||
path={this.props.path}
|
||||
@@ -132,6 +133,18 @@ class MainPanel extends Component {
|
||||
onLinkClick={this.props.onLinkClick}
|
||||
/>
|
||||
)}
|
||||
{isViewingFile && Utils.isSdocFile(this.props.path) && (
|
||||
<SdocWikiPageViewer
|
||||
isWiki={true}
|
||||
path={this.props.path}
|
||||
repoID={repoID}
|
||||
markdownContent={this.props.content}
|
||||
isFileLoading={this.props.isDataLoading}
|
||||
lastModified = {this.props.lastModified}
|
||||
latestContributor={this.props.latestContributor}
|
||||
onLinkClick={this.props.onLinkClick}
|
||||
/>
|
||||
)}
|
||||
{(!this.props.isDataLoading && !this.props.isViewFile) && (
|
||||
<WikiDirListView
|
||||
path={this.props.path}
|
||||
|
@@ -119,6 +119,7 @@ export const isPublicWiki = window.wiki ? window.wiki.config.isPublicWiki === 'T
|
||||
export const sharedToken = window.wiki ? window.wiki.config.sharedToken : '';
|
||||
export const sharedType = window.wiki ? window.wiki.config.sharedType : '';
|
||||
export const hasIndex = window.wiki ? window.wiki.config.hasIndex : '';
|
||||
export const assetsUrl = window.wiki ? window.wiki.config.assetsUrl : '';
|
||||
|
||||
// file history
|
||||
export const PER_PAGE = 25;
|
||||
|
@@ -48,6 +48,7 @@
|
||||
isPublicWiki: "{{ is_public_wiki }}",
|
||||
isDir: "{{ is_dir }}",
|
||||
hasIndex: {% if has_index %} true {% else %} false {% endif %},
|
||||
assetsUrl: "{{ assets_url }}"
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@@ -25,7 +25,8 @@ from seahub.views import check_folder_permission
|
||||
from seahub.utils import get_file_type_and_ext, render_permission_error, \
|
||||
gen_inner_file_get_url, render_error, get_service_url
|
||||
from seahub.views.file import send_file_access_msg
|
||||
from seahub.utils.file_types import IMAGE, MARKDOWN
|
||||
from seahub.utils.file_types import IMAGE, MARKDOWN, SEADOC
|
||||
from seahub.seadoc.utils import get_seadoc_file_uuid
|
||||
|
||||
# Get an instance of a logger
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -197,6 +198,7 @@ def slug(request, slug, file_path="home.md"):
|
||||
outlines = []
|
||||
latest_contributor = ''
|
||||
last_modified = 0
|
||||
assets_url = ''
|
||||
|
||||
if is_dir is False and file_type == MARKDOWN:
|
||||
send_file_access_msg(request, repo, file_path, 'web')
|
||||
@@ -230,6 +232,10 @@ def slug(request, slug, file_path="home.md"):
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
|
||||
if is_dir is False and file_type == SEADOC:
|
||||
file_uuid = get_seadoc_file_uuid(repo, file_path)
|
||||
assets_url = '/api/v2.1/seadoc/download-image/' + file_uuid
|
||||
|
||||
last_modified = datetime.fromtimestamp(last_modified)
|
||||
|
||||
return render(request, "wiki/wiki.html", {
|
||||
@@ -252,6 +258,7 @@ def slug(request, slug, file_path="home.md"):
|
||||
"is_public_wiki": is_public_wiki,
|
||||
"is_dir": is_dir,
|
||||
"has_index": has_index,
|
||||
"assets_url": assets_url,
|
||||
})
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user