1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-02 15:57:31 +00:00

update seafile editor

This commit is contained in:
ilearnit 2018-06-07 15:01:41 +08:00
parent 6151c80b93
commit 748670b1a2
32 changed files with 800 additions and 311 deletions

2
frontend/.gitignore vendored
View File

@ -7,7 +7,7 @@
/coverage
# production
# build
/build
#Idea

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,19 +1,25 @@
{
"bold": "Bold",
"italic": "Italic",
"H1": "H1",
"H2": "H2",
"H3": "H3",
"inline_code": "Code",
"header_one": "Header1",
"header_two": "Header2",
"header_three": "Header3",
"header_four": "Header4",
"header_five": "Header5",
"header_six": "Header6",
"paragraph": "paragraph",
"quote": "Quote",
"ordered_list": "Ordered List",
"unordered_list": "Unordered List",
"check_list_item": "Check List Item",
"insert_image": "Insert Image",
"code": "Code",
"code": "Code Block",
"insert_link": "Insert Link",
"insert_table": "Insert Table",
"save": "Save",
"more": "More",
"edit": "Edit",
"invalid_url": "Invalid URL",
"enter_the_url_of_the_link": "Enter the URL of the Link",
"enter_the_url_of_the_image": "Enter the URL of the image",
@ -21,6 +27,8 @@
"cancel": "Cancel",
"switch_to_plain_text_editor": "Switch to Plain Text Editor",
"switch_to_rich_text_editor": "Switch to Rich Text Editor",
"switch_to_viewer": "Switch to Markdown Viewer",
"help": "help",
"remove_table": "Remove Table",
"column": "Column",
"row": "Row",
@ -30,5 +38,43 @@
"right": "Right",
"file_saved": "File saved.",
"file_failed_to_save": "File failed to save.",
"edit": "Edit"
"userHelp": {
"title":"Keyboard shortcuts",
"userHelpData":[
{
"shortcutType":"Block shortcuts",
"shortcutData" :{
"Blockquote": [">", "space"],
"Header1":["# / *", "space"],
"Header2":["## / **", "space"],
"Header3":["### / ***", "space"],
"List":["* / + / -", "space"],
"Ordered list":["1.", "space"]
}
},
{
"shortcutType":"Inline shortcuts",
"shortcutData" :{
"Bold": ["**bold** / __bold__", "space"],
"Italic":["*italic* / _italic_", "space"],
"Italic + Bold":["***italic*** / ___italic___", "space"],
"Inline code":["`code`", "space"]
}
},
{
"shortcutType":"Save shortcuts",
"shortcutData" :{
"Save file": [["Ctrl", "Cmd"], "S"]
}
},
{
"shortcutType":"Image shortcuts",
"shortcutData" :{
"Upload Screenshot": [["Ctrl", "Cmd"], "V"],
"Drag image from anywhere to upload it":[]
}
}
]
}
}

View File

@ -1,9 +1,14 @@
{
"bold": "粗体",
"italic": "斜体",
"H1": "一级标题",
"H2": "二级标题",
"H3": "三级标题",
"inline_code": "代码",
"header_one": "一级标题",
"header_two": "二级标题",
"header_three": "三级标题",
"header_four": "四级标题",
"header_five": "五级标题",
"header_six": "六级标题",
"paragraph": "段落",
"quote": "引用",
"ordered_list": "有序列表",
"unordered_list": "无序列表",
@ -14,6 +19,7 @@
"insert_table": "添加表格",
"save": "保存",
"more": "更多",
"edit": "编辑",
"invalid_url": "无效链接",
"enter_the_url_of_the_image": "请输入图片的链接",
"enter_the_url_of_the_link": "请输入链接地址",
@ -21,6 +27,8 @@
"cancel": "取消",
"switch_to_plain_text_editor": "切换至普通文本编辑器",
"switch_to_rich_text_editor": "切换至富文本编辑器",
"switch_to_viewer": "切换到只读模式",
"help": "帮助",
"remove_table": "清除表格",
"column": "列",
"row": "行",
@ -30,5 +38,42 @@
"right": "右对齐",
"file_saved": "保存文件成功",
"file_failed_to_save": "保存文件失败",
"edit": "编辑"
"userHelp": {
"title": "键盘快捷键",
"userHelpData":[
{
"shortcutType":"块级操作",
"shortcutData" :{
"块级引用": [">", "space"],
"一级标题":["# / *", "space"],
"二级标题":["## / **", "space"],
"三级标题":["### / ***", "space"],
"列表":["* / + / -", "space"],
"有序列表":["1.", "space"]
}
},
{
"shortcutType":"行级操作",
"shortcutData" :{
"加粗": ["**bold** / __bold__", "space"],
"斜体":["*italic* / _italic_", "space"],
"斜体加粗":["***italic*** / ___italic___", "space"],
"行级代码":["`code`", "space"]
}
},
{
"shortcutType":"文件保存",
"shortcutData" :{
"保存修改": [["Ctrl","Cmd"], "S"]
}
},
{
"shortcutType":"图片操作",
"shortcutData" :{
"上传截图": [["Ctrl","Cmd"], "V"],
"拖拽任意图片上传":[]
}
}
]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1704,7 +1704,7 @@
"requires": {
"anymatch": "1.3.2",
"async-each": "1.0.1",
"fsevents": "1.2.2",
"fsevents": "1.2.4",
"glob-parent": "2.0.0",
"inherits": "2.0.3",
"is-binary-path": "1.0.1",
@ -2419,6 +2419,11 @@
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
},
"deepmerge": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.0.tgz",
"integrity": "sha512-Q89Z26KAfA3lpPGhbF6XMfYAm3jIV3avViy6KOJ2JLzFbeWHOvPQUu5aSJIWXap3gDZC2y1eF5HXEPI2wGqgvw=="
},
"default-require-extensions": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz",
@ -3662,13 +3667,13 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.2.tgz",
"integrity": "sha512-iownA+hC4uHFp+7gwP/y5SzaiUo7m2vpa0dhpzw8YuKtiZsz7cIXsFbXpLEeBM6WuCQyw1MH4RRe6XI8GFUctQ==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz",
"integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==",
"optional": true,
"requires": {
"nan": "2.10.0",
"node-pre-gyp": "0.9.1"
"node-pre-gyp": "0.10.0"
},
"dependencies": {
"abbrev": {
@ -3737,7 +3742,7 @@
}
},
"deep-extend": {
"version": "0.4.2",
"version": "0.5.1",
"bundled": true,
"optional": true
},
@ -3893,7 +3898,7 @@
}
},
"node-pre-gyp": {
"version": "0.9.1",
"version": "0.10.0",
"bundled": true,
"optional": true,
"requires": {
@ -3903,7 +3908,7 @@
"nopt": "4.0.1",
"npm-packlist": "1.1.10",
"npmlog": "4.1.2",
"rc": "1.2.6",
"rc": "1.2.7",
"rimraf": "2.6.2",
"semver": "5.5.0",
"tar": "4.4.1"
@ -3989,11 +3994,11 @@
"optional": true
},
"rc": {
"version": "1.2.6",
"version": "1.2.7",
"bundled": true,
"optional": true,
"requires": {
"deep-extend": "0.4.2",
"deep-extend": "0.5.1",
"ini": "1.3.5",
"minimist": "1.2.0",
"strip-json-comments": "2.0.1"

View File

@ -15,6 +15,7 @@
"css-loader": "0.28.7",
"dayjs": "^1.6.2",
"deep-equal": "^1.0.1",
"deepmerge": "^2.1.0",
"dotenv": "4.0.0",
"dotenv-expand": "4.2.0",
"eslint": "4.10.0",
@ -75,8 +76,8 @@
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"dev": "export NODE_ENV=development && node config/WebpackDevServer.js",
"test": "node scripts/test.js --env=jsdom"
"test": "node scripts/test.js --env=jsdom",
"dev": "export NODE_ENV=development && node config/WebpackDevServer.js"
},
"jest": {
"collectCoverageFrom": [

View File

@ -1,19 +1,25 @@
{
"bold": "Bold",
"italic": "Italic",
"H1": "H1",
"H2": "H2",
"H3": "H3",
"inline_code": "Code",
"header_one": "Header1",
"header_two": "Header2",
"header_three": "Header3",
"header_four": "Header4",
"header_five": "Header5",
"header_six": "Header6",
"paragraph": "paragraph",
"quote": "Quote",
"ordered_list": "Ordered List",
"unordered_list": "Unordered List",
"check_list_item": "Check List Item",
"insert_image": "Insert Image",
"code": "Code",
"code": "Code Block",
"insert_link": "Insert Link",
"insert_table": "Insert Table",
"save": "Save",
"more": "More",
"edit": "Edit",
"invalid_url": "Invalid URL",
"enter_the_url_of_the_link": "Enter the URL of the Link",
"enter_the_url_of_the_image": "Enter the URL of the image",
@ -21,6 +27,8 @@
"cancel": "Cancel",
"switch_to_plain_text_editor": "Switch to Plain Text Editor",
"switch_to_rich_text_editor": "Switch to Rich Text Editor",
"switch_to_viewer": "Switch to Markdown Viewer",
"help": "help",
"remove_table": "Remove Table",
"column": "Column",
"row": "Row",
@ -30,5 +38,43 @@
"right": "Right",
"file_saved": "File saved.",
"file_failed_to_save": "File failed to save.",
"edit": "Edit"
"userHelp": {
"title":"Keyboard shortcuts",
"userHelpData":[
{
"shortcutType":"Block shortcuts",
"shortcutData" :{
"Blockquote": [">", "space"],
"Header1":["# / *", "space"],
"Header2":["## / **", "space"],
"Header3":["### / ***", "space"],
"List":["* / + / -", "space"],
"Ordered list":["1.", "space"]
}
},
{
"shortcutType":"Inline shortcuts",
"shortcutData" :{
"Bold": ["**bold** / __bold__", "space"],
"Italic":["*italic* / _italic_", "space"],
"Italic + Bold":["***italic*** / ___italic___", "space"],
"Inline code":["`code`", "space"]
}
},
{
"shortcutType":"Save shortcuts",
"shortcutData" :{
"Save file": [["Ctrl", "Cmd"], "S"]
}
},
{
"shortcutType":"Image shortcuts",
"shortcutData" :{
"Upload Screenshot": [["Ctrl", "Cmd"], "V"],
"Drag image from anywhere to upload it":[]
}
}
]
}
}

View File

@ -1,9 +1,14 @@
{
"bold": "粗体",
"italic": "斜体",
"H1": "一级标题",
"H2": "二级标题",
"H3": "三级标题",
"inline_code": "代码",
"header_one": "一级标题",
"header_two": "二级标题",
"header_three": "三级标题",
"header_four": "四级标题",
"header_five": "五级标题",
"header_six": "六级标题",
"paragraph": "段落",
"quote": "引用",
"ordered_list": "有序列表",
"unordered_list": "无序列表",
@ -14,6 +19,7 @@
"insert_table": "添加表格",
"save": "保存",
"more": "更多",
"edit": "编辑",
"invalid_url": "无效链接",
"enter_the_url_of_the_image": "请输入图片的链接",
"enter_the_url_of_the_link": "请输入链接地址",
@ -21,6 +27,8 @@
"cancel": "取消",
"switch_to_plain_text_editor": "切换至普通文本编辑器",
"switch_to_rich_text_editor": "切换至富文本编辑器",
"switch_to_viewer": "切换到只读模式",
"help": "帮助",
"remove_table": "清除表格",
"column": "列",
"row": "行",
@ -30,5 +38,42 @@
"right": "右对齐",
"file_saved": "保存文件成功",
"file_failed_to_save": "保存文件失败",
"edit": "编辑"
"userHelp": {
"title": "键盘快捷键",
"userHelpData":[
{
"shortcutType":"块级操作",
"shortcutData" :{
"块级引用": [">", "space"],
"一级标题":["# / *", "space"],
"二级标题":["## / **", "space"],
"三级标题":["### / ***", "space"],
"列表":["* / + / -", "space"],
"有序列表":["1.", "space"]
}
},
{
"shortcutType":"行级操作",
"shortcutData" :{
"加粗": ["**bold** / __bold__", "space"],
"斜体":["*italic* / _italic_", "space"],
"斜体加粗":["***italic*** / ___italic___", "space"],
"行级代码":["`code`", "space"]
}
},
{
"shortcutType":"文件保存",
"shortcutData" :{
"保存修改": [["Ctrl","Cmd"], "S"]
}
},
{
"shortcutType":"图片操作",
"shortcutData" :{
"上传截图": [["Ctrl","Cmd"], "V"],
"拖拽任意图片上传":[]
}
}
]
}
}

View File

@ -3,7 +3,6 @@ import ReactDOM from 'react-dom';
import SeafileEditor from './lib/seafile-editor';
import MarkdownViewer from './lib/markdown-viewer';
import 'whatwg-fetch';
import dayjs from 'dayjs';
let repoID = window.app.pageOptions.repoID;
let filePath = window.app.pageOptions.filePath;
@ -80,9 +79,9 @@ class EditorUtilities {
getFileURL(fileNode) {
var url;
if (fileNode.isImage()) {
url = protocol + '://' + domain + siteRoot + "lib/" + repoID + "/file" + fileNode.path() + "?raw=1";
url = protocol + '://' + domain + siteRoot + "lib/" + repoID + "/file" + encodeURIComponent(fileNode.path()) + "?raw=1";
} else {
url = protocol + '://' + domain + siteRoot + "lib/" + repoID + "/file" + fileNode.path();
url = protocol + '://' + domain + siteRoot + "lib/" + repoID + "/file" + encodeURIComponent(fileNode.path());
}
return url;
}
@ -120,7 +119,7 @@ class App extends React.Component {
this.state = {
markdownContent: "",
loading: true,
mode: "view"
mode: "editor",
};
this.fileInfo = {
name: fileName,
@ -129,8 +128,9 @@ class App extends React.Component {
}
componentDidMount() {
const url = `${siteRoot}api2/repos/${repoID}/file/?p=${filePath}&reuse=1`;
const infoPath =`${siteRoot}api2/repos/${repoID}/file/detail/?p=${filePath}`;
const path = encodeURIComponent(filePath)
const url = `${siteRoot}api2/repos/${repoID}/file/?p=${path}&reuse=1`;
const infoPath =`${siteRoot}api2/repos/${repoID}/file/detail/?p=${path}`;
fetch(infoPath, {credentials:'same-origin'})
.then((response) => response.json())
@ -153,12 +153,6 @@ class App extends React.Component {
})
}
switchToEditor = () => {
this.setState({
mode: "edit"
})
}
render() {
if (this.state.loading) {
return (
@ -166,7 +160,7 @@ class App extends React.Component {
<div className="lds-ripple page-centered"><div></div><div></div></div>
</div>
)
} else if (this.state.mode == "edit") {
} else if (this.state.mode === "editor") {
return (
<SeafileEditor
fileInfo={this.fileInfo}
@ -174,16 +168,7 @@ class App extends React.Component {
editorUtilities={editorUtilities}
/>
);
} else if (this.state.mode == "view") {
return (
<MarkdownViewer
fileInfo={this.fileInfo}
markdownContent={this.state.markdownContent}
switchToEditor={this.switchToEditor}
editorUtilities={this.props.editorUtilities}
/>
)
}
}
}
}

View File

@ -1,3 +1,18 @@
/*set scroll bar*/
body ::-webkit-scrollbar{
width: 8px;
height: 8px;
}
body ::-webkit-scrollbar-button {
display: none;
}
body ::-webkit-scrollbar-thumb {
background-color: rgb(206, 206, 212);
border-radius: 10px;
}
.seafile-editor {
position: fixed;
width: 100%;
@ -33,6 +48,8 @@
height: 100%;
background-color: rgb(250,250,249);
overflow-x: hidden;
display: flex;
flex: 0 0 1;
position: relative; /* for seafile-editor-resize */
}

View File

@ -1,56 +1,90 @@
.seafile-viewer {
position: fixed;
width: 100%;
height: 100%;
min-width: 960px;
#root {
height:100%;
}
.seafile-viewer-topbar {
width: 100%;
height: 68px;
display: flex;
justify-content: space-between;
.seafile-md-viewer {
height: 100%;
}
.seafile-md-viewer-topbar {
padding: 10px;
background-color: #fff;
padding: 0px 10px;
border-bottom: 1px solid #e5e5e5;
box-shadow: 0 3px 2px -2px rgba(200,200,200,.15);
user-select: none;
flex-shrink:0;
}
.seafile-viewer-main {
height: calc(100% - 68px);
width: 100%;
.seafile-md-viewer-filename {
font-size:1.2rem;
margin-bottom:4px;
}
.viewer-left-panel {
height: 100%;
width: 25%;
background-color: #fafafa;
border-right: 1px solid rgb(230,230,221);
overflow-y: auto;
font-size: 0.875rem;
.seafile-md-viewer-file-modify-time {
font-size: .8rem;
color: #666;
}
.viewer-left-panel .side-panel .nav {
padding-left: 6px;
color: #a0a0a0;
.seafile-md-viewer-main {
flex:auto;
overflow:auto;
background:#fafaf9;
}
.viewer-right-panel {
height: 100%;
width: 75%;
background-color: rgb(250,250,249);
overflow-x: hidden;
.seafile-md-viewer-main-panel {
flex:auto;
}
.viewer-right-panel .preview-container {
height: 100%;
overflow-y: auto;
}
.viewer-right-panel .rendered-markdown {
min-height: calc(100% - 40px);
.seafile-md-viewer-rendered-content {
background: #fff;
padding:30px 30px 15px;
margin: 20px 40px;
border: 1px solid rgb(230,230,221);
border:1px solid #e6e6dd;
min-height: calc(100% - 60px);
margin-top:20px;
margin-bottom:20px;
}
.seafile-md-viewer-side-panel {
position:fixed;
margin-top:20px;
max-height:calc(100% - 120px);
overflow:hidden;
user-select: none;
}
.seafile-md-viewer-side-panel:hover {
overflow:auto;
}
@media (max-width:991.8px) {
.seafile-md-viewer-side-panel {
display:none;
}
}
@media (min-width:992px) {
.seafile-md-viewer-main-panel {
margin-left:5%;
max-width:calc(90% - 260px);
}
.seafile-md-viewer-side-panel {
width:260px;
right:5%;
padding-left: 1.25rem;
padding-right: 0.75rem;
}
}
.seafile-md-viewer-side-panel-heading {
padding:7px 0;
border-bottom: 1px solid #eee;
color: #a0a0a0;
}
.seafile-md-viewer-side-panel-content {
padding:8px 0;
font-size: 0.875rem;
}
.seafile-md-viewer-outline-heading2,
.seafile-md-viewer-outline-heading3 {
margin-left: .75rem;
line-height: 2.5;
color:#666;
white-space: nowrap;
overflow:hidden;
text-overflow:ellipsis;
cursor:pointer;
}
.seafile-md-viewer-outline-heading3 {
margin-left: 2rem;
}
.seafile-md-viewer-outline-heading2:hover,
.seafile-md-viewer-outline-heading3:hover {
color: #eb8205;
}

View File

@ -12,6 +12,7 @@
/* this container is needed to show the scroll bar */
height: 100%;
overflow-y: auto;
flex: 1 1 auto;
}
.editor {
min-height: calc(100% - 40px);
@ -20,3 +21,76 @@
margin: 20px 40px;
border: 1px solid rgb(230,230,221);
}
.seafile-editor-help {
width: 250px;
height: 100%;
flex: 0 0 350px;
background-color: #fff;
overflow-y: auto;
overflow-x: hidden;
border-left: 1px solid #eee;
}
.seafile-editor-help .help-header{
height: 50px;
background-color: rgb(250,250,249);
justify-content: space-between;
padding: 0 10px;
box-sizing: border-box;
border-bottom: 1px solid #eee;
line-height: 50px;
font-size: 1rem;
}
.seafile-editor-help .help-title {
font-weight: bolder;
margin-bottom: 0;
}
.help-close {
font-weight: normal;
color: #b9b9b9;
}
.help-close:hover {
color: #888;
}
.help-content {
height: calc(100% - 50px);
overflow-y: auto;
padding: 0 15px;
box-sizing: border-box;
font-size: 0.8rem;
}
.help-shortcut {
display: flex;
height: 50px;
border-bottom: 1px solid #eee;
justify-content: space-between;
line-height: 50px;
color: #2c2d30;
}
.help-content .help-shortcut-type {
border-bottom: 1px solid #eee;
/*height: 50px;*/
line-height: 2;
font-weight: bolder;
margin: 10px 0 0 0;
}
.help-shortcut .key {
display: inline-block;
border-radius: 3px;
background-color: #e8e8e8;
border: 1px solid #919191;
box-shadow: 0 1px 0 #919193;
padding: 3px 5px;
margin-left: 5px;
height: 30px;
line-height: 20px;
margin-top: 10px;
}

View File

@ -53,7 +53,6 @@
display: inline-block;
margin-top: 10px;
margin-left: 8px;
float: left;
}
.topbar-file-info .file-name {
font-size: 1.2rem;

View File

@ -2,11 +2,6 @@
margin: 0;
padding: 0;
}
/**editor style */
html body{
height: 100%;
}
html,
input,
textarea {
@ -16,7 +11,6 @@ textarea {
}
body {
margin: 0;
overflow-y: hidden;
}
@ -56,7 +50,8 @@ input[type="checkbox"] {
width: auto;
}
a, a:hover {
a,
a:hover {
color: #eb8205;
}
@ -104,4 +99,4 @@ a, a:hover {
left: 50%;
/* bring your own prefixes */
transform: translate(-50%, -50%);
}
}

View File

@ -1,6 +1,7 @@
import React from 'react';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import validUrl from 'valid-url';
import { translate } from "react-i18next";
class AddImageDialog extends React.Component {
@ -43,4 +44,4 @@ class AddImageDialog extends React.Component {
}
export default AddImageDialog;
export default translate("translations")(AddImageDialog);

View File

@ -1,6 +1,7 @@
import React from 'react';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import validUrl from 'valid-url';
import { translate } from "react-i18next";
class AddLinkDialog extends React.Component {
state = {
@ -41,4 +42,4 @@ class AddLinkDialog extends React.Component {
}
}
export default AddLinkDialog;
export default translate('translations')(AddLinkDialog);

View File

@ -43,13 +43,11 @@ class ViewerSidePanel extends React.Component {
}
return (
<div className="side-panel">
<ul className="nav">
<li className="nav-item">
Contents
</li>
</ul>
<div className="side-panel-content">
<div className="seafile-md-viewer-side-panel">
<div className="seafile-md-viewer-side-panel-heading">
Contents
</div>
<div className="seafile-md-viewer-side-panel-content">
{ this.state.navItem == "files" &&
<TreeView
editorUtilities={this.props.editorUtilities}
@ -97,7 +95,7 @@ class MarkdownViewer extends React.Component {
renderToolbar() {
const { t } = this.props
return (
<div className="menu toolbar-menu">
<div>
<ButtonGroup>
<IconButton text={t('edit')} id={'editButton'} icon={"fa fa-edit"} onMouseDown={this.onEdit} />
</ButtonGroup>
@ -108,35 +106,26 @@ class MarkdownViewer extends React.Component {
render() {
var html = processor.processSync(this.props.markdownContent).toString();
var treeRoot = processorGetAST.runSync(processorGetAST.parse(this.props.markdownContent));
var modifyTime = dayjs(this.props.fileInfo.mtime*1000).format("YYYY-MM-DD HH:mm:ss");
var modifyTime = dayjs(this.props.fileInfo.mtime*1000).format("YYYY-MM-DD HH:mm");
return (
<div className='seafile-viewer'>
<div className="seafile-viewer-topbar">
<div className="topbar-file-info">
<div className="file-name">
{this.props.fileInfo.name}
</div>
<div className="file-mtime">
{modifyTime}
</div>
<div className="seafile-md-viewer d-flex flex-column">
<div className="seafile-md-viewer-topbar d-flex justify-content-between">
<div>
<h1 className="seafile-md-viewer-filename">{this.props.fileInfo.name}</h1>
<p className="seafile-md-viewer-file-modify-time">{modifyTime}</p>
</div>
{this.renderToolbar()}
</div>
<div className="seafile-viewer-main d-flex">
<div className="viewer-left-panel align-self-start">
<ViewerSidePanel
treeRoot={treeRoot}
viewer={this}
editorUtilities={this.props.editorUtilities}
/>
</div>
<div className="viewer-right-panel align-self-end">
<div className="preview-container">
<div className="rendered-markdown article" dangerouslySetInnerHTML={{ __html: html }}>
</div>
</div>
<div className="seafile-md-viewer-main d-flex">
<div className="seafile-md-viewer-main-panel">
<div className="seafile-md-viewer-rendered-content article" dangerouslySetInnerHTML={{ __html: html }}></div>
</div>
<ViewerSidePanel
treeRoot={treeRoot}
viewer={this}
editorUtilities={this.props.editorUtilities}
/>
</div>
</div>
)

View File

@ -5,6 +5,8 @@ import { processor } from "./seafile-markdown2html"
import Alert from 'react-s-alert';
import isHotkey from 'is-hotkey';
import { translate } from "react-i18next";
import FileInfoView from './topbarcomponent/file-info';
const ReactDOM = require('react-dom');
const className = require('classnames');
const lodash = require('lodash');
@ -42,7 +44,7 @@ class CodeMirror extends React.Component {
this.codeMirror.on('focus', this.focusChanged.bind(this, true));
this.codeMirror.on('blur', this.focusChanged.bind(this, false));
this.codeMirror.on('scroll', this.scrollChanged);
this.codeMirror.setValue(this.props.defaultValue || this.props.initialValue || '');
}
componentWillUnmount () {
@ -53,6 +55,7 @@ class CodeMirror extends React.Component {
}
componentWillReceiveProps(nextProps) {
if (this.codeMirror && nextProps.initialValue !== undefined && nextProps.initialValue !== this.props.initialValue) {
if (this.props.preserveScrollPosition) {
var prevScrollPosition = this.codeMirror.getScrollInfo();
@ -62,25 +65,10 @@ class CodeMirror extends React.Component {
this.codeMirror.setValue(nextProps.initialValue);
}
}
if (typeof nextProps.options === 'object') {
for (let optionName in nextProps.options) {
if (nextProps.options.hasOwnProperty(optionName)) {
this.setOptionIfChanged(optionName, nextProps.options[optionName]);
}
}
}
}
setOptionIfChanged (optionName, newValue) {
const oldValue = this.codeMirror.getOption(optionName);
if (!lodash.isEqual(oldValue, newValue)) {
this.codeMirror.setOption(optionName, newValue);
}
}
getCodeMirror () {
return this.codeMirror;
}
focus = () => {
if (this.codeMirror) {
@ -120,7 +108,7 @@ class CodeMirror extends React.Component {
<textarea
ref={ref => this.textareaNode = ref}
name={this.props.name || this.props.path}
defaultValue={this.props.value}
defaultValue={this.props.initialValue}
autoComplete="off"
autoFocus={this.props.autoFocus}
/>
@ -160,6 +148,8 @@ class MoreMenu extends React.Component {
}
}
let TransMoreMenu = translate("translations")(MoreMenu);
/*
@ -182,6 +172,16 @@ On Save:
class PlainMarkdownEditor extends React.Component {
constructor (props){
super (props);
this.options = {
lineNumbers: true,
mode: "markdown",
lineWrapping: true,
scrollbarStyle: null
}
}
state = {
html: "",
leftIsBindScroll: false,
@ -240,7 +240,7 @@ class PlainMarkdownEditor extends React.Component {
<IconButton id={'saveButton'} text={t('save')} icon={"fa fa-save"} onMouseDown={this.props.onSave} disabled={!isSaveActive} isActive={isSaveActive} />
</ButtonGroup>
)}
<MoreMenu switchToRichTextEditor={this.props.switchToRichTextEditor} t={ t }/>
<TransMoreMenu switchToRichTextEditor={this.props.switchToRichTextEditor}/>
<Alert stack={{limit: 3}} />
</div>
);
@ -291,22 +291,17 @@ class PlainMarkdownEditor extends React.Component {
}
render() {
var options = {
lineNumbers: true,
mode: "markdown",
lineWrapping: true,
};
return (
<div className='seafile-editor'>
<div className="seafile-editor-topbar">
<div className="title"><img src={ require('../assets/seafile-logo.png') } alt=""/></div>
<FileInfoView fileInfo={this.props.fileInfo}/>
{this.renderToolbar()}
</div>
<div className="seafile-editor-main d-flex">
<div className="plain-editor-left-panel" onKeyDown={this.onHotKey} onMouseLeave={this.onLeaveLeftPanel} onMouseEnter={this.onEnterLeftPanel} onScroll={this.state.leftIsBindScroll ? this.onLeftScroll : null}>
<CodeMirror initialValue={this.props.initialValue}
onChange={this.updateCode} options={options} />
onChange={this.updateCode} options={this.options} />
</div>
<div className="plain-editor-right-panel" onMouseEnter={this.onEnterRightPanel} onMouseLeave={this.onLeaveRightPanel} onScroll={this.state.rightIsBindScroll ? this.onRightScroll :null}>
<div className="preview">

View File

@ -12,16 +12,17 @@ import CheckListItem from './check-list-item';
import { Inline, Text } from 'slate';
import AddImageDialog from './add-image-dialog';
import AddLinkDialog from './add-link-dialog';
import UserHelpDialog from './user-help'
import SeafileSlatePlugin from './seafile-slate-plugin';
import Alert from 'react-s-alert';
import '../css/richeditor/right-panel.css';
import '../css/richeditor/side-panel.css';
import 'react-s-alert/dist/s-alert-default.css';
import 'react-s-alert/dist/s-alert-css-effects/scale.css';
import { IconButton, TableToolBar, Button, ButtonGroup, MoreMenu } from "./topbarcomponent/editorToolBar";
import { IconButton, TableToolBar, Button, ButtonGroup, MoreMenu, HeaderList } from "./topbarcomponent/editorToolBar";
import { translate } from "react-i18next";
import FileInfoView from "./topbarcomponent/file-info";
const DEFAULT_NODE = 'paragraph';
const editCode = EditCode();
@ -64,7 +65,8 @@ const insertImages = InsertImages({
var seafileSlatePlugin = new SeafileSlatePlugin({
editCode,
editTable
editTable,
editBlockquote
});
const plugins = [
@ -86,7 +88,8 @@ class RichMarkdownEditor extends React.Component {
leftNavMode: "files",
showAddLinkDialog: false,
rightWidth: 75,
resizeFlag: false
resizeFlag: false,
isShowHelpDialog:false
};
constructor(props) {
@ -189,7 +192,19 @@ class RichMarkdownEditor extends React.Component {
this.setState({
showAddLinkDialog: !this.state.showAddLinkDialog
});
}
};
showHelpDialog = () => {
this.setState({
isShowHelpDialog: true
})
};
hideHelpDialog = () => {
this.setState({
isShowHelpDialog: false
});
};
/**
* Check if the any of the currently selected blocks are of `type`.
@ -289,7 +304,8 @@ class RichMarkdownEditor extends React.Component {
const value = this.props.value
const { selection } = value
const change = value.change()
this.onChange(editCode.changes.toggleCodeBlock(change))
// the second param is used to turn codeBlock to paragraph
this.onChange(editCode.changes.toggleCodeBlock(change, 'paragraph'));
}
/**
@ -491,13 +507,14 @@ class RichMarkdownEditor extends React.Component {
};
render() {
const value = this.props.value;
const onResizeMove = this.state.resizeFlag ? this.onResizeMouseMove : null;
const { t } = this.props;
return (
<div className='seafile-editor'>
<div className="seafile-editor-topbar">
<div className="title"><img src={ require('../assets/seafile-logo.png') } alt=""/></div>
{this.renderToolbar()}
<FileInfoView fileInfo={this.props.fileInfo}/>
{this.renderToolbar()}
</div>
<div className="seafile-editor-main d-flex" onMouseMove={onResizeMove} onMouseUp={this.onResizeMouseUp}>
<div className="seafile-editor-left-panel align-self-start" style={{width:(100-this.state.rightWidth)+'%'}}>
@ -507,11 +524,11 @@ class RichMarkdownEditor extends React.Component {
editorUtilities={this.props.editorUtilities}
/>
</div>
<div className="seafile-editor-right-panel align-self-end" style={{width:this.state.rightWidth+'%'}}>
<div className="seafile-editor-right-panel d-flex align-self-end" style={{width:this.state.rightWidth+'%'}}>
<div className="seafile-editor-resize" onMouseDown={this.onResizeMouseDown}></div>
<div className="editor-container">
<div className="editor article">
<Editor
<div className="editor-container align-self-start">
<div className="editor article">
<Editor
value={this.props.value}
autoFocus={true}
plugins={plugins}
@ -521,9 +538,12 @@ class RichMarkdownEditor extends React.Component {
renderMark={this.renderMark}
onDrop={this.onDrop}
editorUtilities={this.props.editorUtilities}
/>
</div>
/>
</div>
</div>
{
this.state.isShowHelpDialog ? <UserHelpDialog userHelp={t('userHelp',{returnObjects: true})} hideHelpDialog={this.hideHelpDialog}/>:null
}
</div>
</div>
</div>
@ -531,7 +551,7 @@ class RichMarkdownEditor extends React.Component {
}
renderToolbar = () => {
const { t } = this.props
const { t } = this.props;
const value = this.props.value;
var isTableActive = false;
var isCodeActive = false;
@ -543,6 +563,8 @@ class RichMarkdownEditor extends React.Component {
}
const isImageActive = this.hasSelectImage(value);
const isLinkActive = this.hasLinks(value);
// get the type of current block
const headerType = value.focusBlock.type;
let showMarkButton = true, showBlockButton = true, showCodeButton = true,
showImageButton = true, showAddTableButton = true, showLinkButton = true;
@ -571,14 +593,11 @@ class RichMarkdownEditor extends React.Component {
<ButtonGroup>
{this.renderMarkButton("BOLD", "fa fa-bold")}
{this.renderMarkButton('ITALIC', 'fa fa-italic')}
{this.renderMarkButton('CODE', 'fa fa-code')}
</ButtonGroup>
}
{ showBlockButton === true &&
<ButtonGroup>
{this.renderBlockButton('header_one', 'fa fa-h1')}
{this.renderBlockButton('header_two', 'fa fa-h2')}
{this.renderBlockButton('header_three', 'fa fa-h3')}
</ButtonGroup>
<HeaderList headerType={headerType} onClickBlock={this.onClickBlock}/>
}
{ showBlockButton === true &&
<ButtonGroup>
@ -595,7 +614,7 @@ class RichMarkdownEditor extends React.Component {
<IconButton text={t('insert_link')} id={'linkButton'} icon={'fa fa-link'} isActive={isLinkActive} onMouseDown={this.onToggleLink}/>
}
{ showCodeButton === true &&
<IconButton text={t('code')} id={'codeButton'} icon={"fa fa-code"} onMouseDown={this.onToggleCode} isActive={isCodeActive}/>
<IconButton text={t('code')} id={'codeButton'} icon={"fa fa-code fa-code"} onMouseDown={this.onToggleCode} isActive={isCodeActive}/>
}
{ showAddTableButton === true && this.renderAddTableButton()}
{ showImageButton === true &&
@ -605,8 +624,8 @@ class RichMarkdownEditor extends React.Component {
{ isTableActive === true && this.renderTableToolbar()}
{ this.props.saving ? (
<ButtonGroup>
<button type={"button"} className={"btn btn-icon btn-secondary btn-active btn-loading"} >
<i className={"fa fa-save"}/>
<button type={"button"} className={"btn btn-icon btn-secondary btn-active"} >
<i className={"fa fa-spin fa-spinner"}/>
</button>
</ButtonGroup>
) : (
@ -614,18 +633,17 @@ class RichMarkdownEditor extends React.Component {
<IconButton text={t('save')} id={'saveButton'} icon={"fa fa-save"} onMouseDown={this.onSave} disabled={!isSaveActive} isActive={isSaveActive}/>
</ButtonGroup>
)}
<MoreMenu id={'moreButton'} text={t('more')} switchToPlainTextEditor={this.props.switchToPlainTextEditor} t={ t }/>
<MoreMenu id={'moreButton'} text={t('more')} showHelpDialog={this.showHelpDialog} switchToMarkDownViewer ={this.props.switchToMarkDownViewer} switchToPlainTextEditor={this.props.switchToPlainTextEditor}/>
<AddImageDialog
showAddImageDialog={this.state.showAddImageDialog}
toggleImageDialog={this.toggleImageDialog}
onInsertImage={this.onInsertImage}
t = { t }
/>
<AddLinkDialog
showAddLinkDialog={this.state.showAddLinkDialog}
toggleLinkDialog={this.toggleLinkDialog}
onSetLink={this.onSetLink}
t = { t }
/>
<Alert stack={{limit: 3}} />
</div>
@ -641,7 +659,6 @@ class RichMarkdownEditor extends React.Component {
};
renderTableToolbar = () => {
const { t } = this.props
return (
<TableToolBar
onRemoveTable={this.onRemoveTable}
@ -650,7 +667,6 @@ class RichMarkdownEditor extends React.Component {
onInsertRow={this.onInsertRow}
onRemoveRow={this.onRemoveRow}
onSetAlign={this.onSetAlign}
t={ t }
/>
)
}
@ -697,8 +713,10 @@ class RichMarkdownEditor extends React.Component {
const onMouseDown = event => this.onClickMark(event, type);
if (type ==='BOLD') {
toolTipText = 'bold'
} else {
} else if (type === 'ITALIC') {
toolTipText = 'italic'
} else {
toolTipText = 'inline_code'
}
return (
// eslint-disable-next-line react/jsx-no-bind
@ -727,17 +745,6 @@ class RichMarkdownEditor extends React.Component {
} else if (type === 'block-quote') {
isActive = editBlockquote.utils.isSelectionInBlockquote(this.props.value);
toolTipText = 'quote'
} else {
if (this.props.value.isFocused) {
isActive = this.hasBlock(type);
}
if (type === 'header_two') {
toolTipText = 'H2'
} else if (type === 'header_one'){
toolTipText = 'H1'
} else if (type === 'header_three') {
toolTipText = 'H3'
}
}
const onMouseDown = event => this.onClickBlock(event, type);
return (

View File

@ -7,28 +7,37 @@ import '../css/topbar.css';
import RichMarkdownEditor from './rich-markdown-editor';
import PlainMarkdownEditor from './plain-markdown-editor';
import MarkdownViewer from './markdown-viewer';
import { serialize, deserialize } from '../slate2markdown';
const lodash = require('lodash');
class SeafileEditor extends React.Component {
state = {
isTreeDataLoaded: false,
editor: "rich",
initialPlainValue: "", // for plain editor
richValue: deserialize(""),
currentContent: "",
savedContent: "",
contentChanged: false,
saving: false
}
setFileInfoMtime = () => {
this.setState({
fileInfo: Object.assign({}, this.state.fileInfo, {mtime: (new Date().getTime()/1000)})
});
};
constructor(props) {
super(props);
this.checkNeedSave = lodash.throttle(this.onCheckNeedSave, 1000);
this.convertAndCheckNeedSave = lodash.throttle(
this.convertAndCheckNeedSave, 1000);
this.state = {
isTreeDataLoaded: false,
mode: "viewer",
initialPlainValue: "", // for plain editor
richValue: deserialize(""),
// currentContent is markdown object, the root value of viewer, richEditor and plainEditor
currentContent: this.props.markdownContent,
savedContent: "",
contentChanged: false,
saving: false,
fileInfo: this.props.fileInfo
}
}
setContent(markdownContent) {
@ -52,53 +61,61 @@ class SeafileEditor extends React.Component {
}
componentWillReceiveProps(nextProps) {
this.setContent(nextProps.markdownContent);
console.log(nextProps,'will receiveprops');
// this.setContent(nextProps.markdownContent);
}
onUnload = event => {
if (!this.state.contentChanged) return
if (!this.state.contentChanged) return;
const confirmationMessage = 'Leave this page? The system may not save your changes.';
event.returnValue = confirmationMessage;
return confirmationMessage;
}
};
switchToPlainTextEditor = () => {
this.setState({
editor: "plain",
mode: "plain",
initialPlainValue: this.state.currentContent
});
}
};
switchToMarkDownViewer = () => {
this.setState({
mode: "viewer"
})
};
switchToRichTextEditor = () => {
this.setState({
editor: "rich",
mode: "rich",
richValue: deserialize(this.state.currentContent)
});
}
};
convertAndCheckNeedSave = (newValue) => {
let currentContent = serialize(newValue.toJSON()).trim();
let contentChanged = currentContent != this.state.savedContent.trim();
let contentChanged = currentContent !== this.state.savedContent.trim();
this.setState({
currentContent: currentContent,
contentChanged: contentChanged
})
}
};
onCheckNeedSave = (newContent) => {
this.setState({
contentChanged: newContent != this.state.savedContent
contentChanged: newContent !== this.state.savedContent
})
}
};
onChange = (change) => {
if (this.state.editor === 'rich') {
if (this.state.mode === 'rich') {
this.setState({
richValue: change.value,
})
});
const ops = change.operations
.filter(o => o.type != 'set_selection' && o.type != 'set_value')
if (ops.size != 0) {
.filter(o => o.type !== 'set_selection' && o.type !== 'set_value');
if (ops.size !== 0) {
// we need to parse change.value to convertAndCheckNeedSave()
// because after setState, this.state will only be updated
// at the end of current event loop which may be later
@ -108,14 +125,14 @@ class SeafileEditor extends React.Component {
} else {
this.setState({
currentContent: change,
})
});
// save as above
this.checkNeedSave(change);
}
}
};
saveContent = (str) => {
var promise = this.props.editorUtilities.saveContent(str).then(() => {
let promise = this.props.editorUtilities.saveContent(str).then(() => {
this.setState({
saving: false,
savedContent: this.state.currentContent,
@ -139,21 +156,24 @@ class SeafileEditor extends React.Component {
this.setState({
saving: true
})
}
};
onRichEditorSave = () => {
const value = this.state.richValue;
const str = serialize(value.toJSON());
this.saveContent(str)
}
this.saveContent(str);
this.setFileInfoMtime();
};
onPlainEditorSave = () => {
const str = this.state.currentContent;
this.saveContent(str)
}
this.saveContent(str);
this.setFileInfoMtime();
};
render() {
if (this.state.editor === "rich") {
if (this.state.mode === "rich") {
return (
<RichMarkdownEditor
editorUtilities={this.props.editorUtilities}
@ -163,9 +183,11 @@ class SeafileEditor extends React.Component {
value={this.state.richValue}
contentChanged={this.state.contentChanged}
saving={this.state.saving}
switchToMarkDownViewer={this.switchToMarkDownViewer}
fileInfo={this.state.fileInfo}
/>
);
} else if (this.state.editor === "plain") {
} else if (this.state.mode === "plain") {
return (
<PlainMarkdownEditor
editorUtilities={this.props.editorUtilities}
@ -176,8 +198,18 @@ class SeafileEditor extends React.Component {
switchToRichTextEditor={this.switchToRichTextEditor}
onSave={this.onPlainEditorSave}
onChange={this.onChange}
fileInfo={this.state.fileInfo}
/>
);
} else if (this.state.mode === "viewer") {
return (
<MarkdownViewer
fileInfo={this.state.fileInfo}
markdownContent={this.state.currentContent}
switchToEditor={this.switchToRichTextEditor}
editorUtilities={this.props.editorUtilities}
/>
)
}
}
}

View File

@ -1,5 +1,4 @@
var unified = require('unified');
var remark = require('remark');
var markdown = require('remark-parse');
var slug = require('remark-slug');
var remark2rehype = require('remark-rehype');
@ -8,18 +7,32 @@ var raw = require('rehype-raw');
var xtend = require('xtend');
var toHTML = require('hast-util-to-html');
var sanitize = require('hast-util-sanitize');
var gh = require('hast-util-sanitize/lib/github');
var deepmerge = require('deepmerge').default;
// var github = require('hast-util-sanitize/lib/github');
function stringify(config) {
var settings = xtend(config, this.data('settings'));
var schema = deepmerge(gh, {
"attributes":{
"input": [
"type",
],
"li": [
"className"
],
},
"tagNames": [
"input"
]
});
this.Compiler = compiler;
function compiler(tree) {
// use sanity to remove dangerous html, the default is
// GitHub style sanitation
var hast = sanitize(tree);
var hast = sanitize(tree, schema);
return toHTML(hast, settings);
}
}

View File

@ -1,4 +1,4 @@
import { Editor, getEventTransfer, getEventRange } from 'slate-react';
import { getEventTransfer, getEventRange } from 'slate-react';
import isUrl from 'is-url';
import isHotkey from 'is-hotkey';
import { Inline, Text, Mark, Range } from 'slate';
@ -26,7 +26,7 @@ function removeAllMark(change) {
}
return change;
};
}
function matchCode(
currentTextNode,
@ -135,7 +135,8 @@ function matchBoldItalic(
function SeafileSlatePlugin(options) {
let {
editCode,
editTable
editTable,
editBlockquote,
} = options;
return {
@ -306,21 +307,21 @@ function SeafileSlatePlugin(options) {
case '-':
case '+':
case '1.':
return 'list_item'
return 'list_item';
case '>':
return 'block-quote'
return 'block-quote';
case '#':
return 'header_one'
return 'header_one';
case '##':
return 'header_two'
return 'header_two';
case '###':
return 'header_three'
return 'header_three';
case '####':
return 'header_four'
return 'header_four';
case '#####':
return 'header_five'
return 'header_five';
case '######':
return 'header_six'
return 'header_six';
default:
return null
}
@ -333,35 +334,35 @@ function SeafileSlatePlugin(options) {
*/
onEnter(event, change) {
const { value } = change
if (value.isExpanded) return
const { value } = change;
if (value.isExpanded) return;
const { startBlock, startOffset, endOffset } = value
const { startBlock, endOffset } = value;
/*
if (startOffset === 0 && startBlock.text.length === 0)
return this.onBackspace(event, change)
*/
//console.log(startBlock)
if (endOffset !== startBlock.text.length) return
if (endOffset !== startBlock.text.length) return;
/* enter code block if put ``` */
if (startBlock.text === '```') {
event.preventDefault()
editCode.changes.wrapCodeBlockByKey(change, startBlock.key)
event.preventDefault();
editCode.changes.wrapCodeBlockByKey(change, startBlock.key);
// move the cursor to the start of new code block
change.collapseToStartOf(change.value.document.getDescendant(startBlock.key))
change.collapseToStartOf(change.value.document.getDescendant(startBlock.key));
// remove string '```'
change.deleteForward(3)
change.deleteForward(3);
return true
}
/* enter hr block if put *** or --- */
if (startBlock.text === '***' || startBlock.text === '---') {
event.preventDefault()
event.preventDefault();
change.removeNodeByKey(startBlock.key).insertBlock({
type: 'hr',
isVoid: true
}).collapseToStartOfNextBlock()
}).collapseToStartOfNextBlock();
return true
}
@ -382,8 +383,8 @@ function SeafileSlatePlugin(options) {
return;
}
event.preventDefault()
change.splitBlock().setBlocks('paragraph')
event.preventDefault();
change.splitBlock().setBlocks('paragraph');
return true
},
@ -450,21 +451,22 @@ function SeafileSlatePlugin(options) {
if (this.editor.isInCode())
return;
const { value } = change
if (value.isExpanded) return
const { value } = change;
if (value.isExpanded) return;
const { startBlock, startOffset } = value
const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, '')
const type = this.getType(chars)
const { startBlock, startOffset } = value;
const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, '');
const type = this.getType(chars);
if (!type)
if (!type) {
return this.handleInlineMarks(event, change);
if (type === 'list_item' && startBlock.type === 'list_item')
}
if (type === 'list_item' && startBlock.type === 'list_item') {
return this.handleInlineMarks(event, change);
}
// handle block shortcut
event.preventDefault()
change.setBlocks(type)
event.preventDefault();
change.setBlocks(type);
if (type === 'list_item') {
if (chars === "1.") {
@ -472,9 +474,11 @@ function SeafileSlatePlugin(options) {
} else {
change.wrapBlock('unordered_list')
}
} else if (type === 'block-quote') {
editBlockquote.changes.wrapInBlockquote(change);
}
change.extendToStartOf(startBlock).delete()
change.extendToStartOf(startBlock).delete();
return true
},
@ -486,22 +490,22 @@ function SeafileSlatePlugin(options) {
* @param {Change} change
*/
onBackspace(event, change) {
const { value } = change
if (value.isExpanded) return
const { value } = change;
if (value.isExpanded) return;
// If the cursor not at the start of node, do nothing
if (value.startOffset !== 0) return
if (value.startOffset !== 0) return;
const { startBlock } = value
const { startBlock } = value;
// If the cursor at the start of a paragraph, do nothing
if (startBlock.type == 'paragraph') return
if (startBlock.type == 'code_line') return
if (startBlock.type === 'paragraph') return;
if (startBlock.type === 'code_line') return;
event.preventDefault()
change.setBlocks('paragraph')
event.preventDefault();
change.setBlocks('paragraph');
const { document } = value
const { document } = value;
if (startBlock.type === 'list-item') {
const pNode = document.getParent(startBlock.key)
const pNode = document.getParent(startBlock.key);
// unwrap the parent 'numbered-list' or 'bulleted-list'
change.unwrapBlock(pNode.type)
}
@ -558,10 +562,12 @@ function SeafileSlatePlugin(options) {
if (editor.props.editorUtilities.isInternalFileLink(text)) {
let index = text.lastIndexOf("/");
if (index == -1) {
if (index === -1) {
return;
}
var fileName = text.substring(index + 1);
var name = text.substring(index + 8);
var fileName = decodeURIComponent(name);
var t = Text.create({
text: fileName
});

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem, Tooltip } from 'reactstrap'
import { translate } from "react-i18next";
class DropDownBox extends React.Component {
constructor(props) {
@ -31,6 +32,8 @@ class DropDownBox extends React.Component {
}
}
let TransDropDownBox = translate("translations")(DropDownBox);
class MoreMenu extends React.Component {
constructor(props) {
@ -60,13 +63,15 @@ class MoreMenu extends React.Component {
return (
<Dropdown isOpen={this.state.dropdownOpen} toggle={this.DropDowntoggle}>
<DropdownToggle id={this.props.id}>
<i className="fa fa-ellipsis-v"></i>
<i className="fa fa-ellipsis-v"/>
<Tooltip toggle={this.ToolTipToggle} delay={{show: 0, hide: 0}} target={this.props.id} placement='bottom' isOpen={this.state.tooltipOpen}>
{this.props.text}
</Tooltip>
</DropdownToggle>
<DropdownMenu className={'drop-list'}>
<DropdownItem onMouseDown={this.props.switchToPlainTextEditor}>{this.props.t('switch_to_plain_text_editor')}</DropdownItem>
<DropdownItem onMouseDown={this.props.switchToMarkDownViewer}>{this.props.t('switch_to_viewer')}</DropdownItem>
<DropdownItem onMouseDown={this.props.showHelpDialog}>{this.props.t('help')}</DropdownItem>
</DropdownMenu>
</Dropdown>
)
@ -117,7 +122,7 @@ class IconButton extends React.Component {
className={"btn btn-icon btn-secondary btn-active"}
data-active={ this.props.isActive || false }
disabled={this.props.disabled}>
<i className={this.props.icon}></i>
<i className={this.props.icon}/>
<Tooltip toggle={this.toggle} delay={{show: 0, hide: 0}} target={this.props.id} placement='bottom' isOpen={this.state.tooltipOpen}>
{this.props.text}
</Tooltip>
@ -144,10 +149,48 @@ class TableToolBar extends React.Component {
<Button>{this.props.t('row')}</Button>
<Button onMouseDown={this.props.onRemoveRow}>-</Button>
</ButtonGroup>
<DropDownBox onSetAlign={this.props.onSetAlign} t={this.props.t}/>
<TransDropDownBox onSetAlign={this.props.onSetAlign}/>
</div>
)
}
}
export { IconButton, TableToolBar, Button, ButtonGroup, MoreMenu }
class HeaderList extends React.Component {
constructor(props) {
super(props);
this.state= {
dropdownOpen:false,
}
}
toggle= () => {
this.setState({
dropdownOpen:!this.state.dropdownOpen
});
}
render() {
return (
<Dropdown isOpen={this.state.dropdownOpen} toggle={this.toggle}>
<DropdownToggle caret>
{this.props.t(this.props.headerType)}
</DropdownToggle>
<DropdownMenu className={'drop-list'}>
<DropdownItem onMouseDown={event => {this.props.onClickBlock(event, "paragraph")}}>{this.props.t('paragraph')}</DropdownItem>
<DropdownItem onMouseDown={event => {this.props.onClickBlock(event, "header_one")}}>{this.props.t('header_one')}</DropdownItem>
<DropdownItem onMouseDown={event => {this.props.onClickBlock(event, "header_two")}}>{this.props.t('header_two')}</DropdownItem>
<DropdownItem onMouseDown={event => {this.props.onClickBlock(event, "header_three")}}>{this.props.t('header_three')}</DropdownItem>
<DropdownItem onMouseDown={event => {this.props.onClickBlock(event, "header_four")}}>{this.props.t('header_four')}</DropdownItem>
<DropdownItem onMouseDown={event => {this.props.onClickBlock(event, "header_five")}}>{this.props.t('header_five')}</DropdownItem>
<DropdownItem onMouseDown={event => {this.props.onClickBlock(event, "header_six")}}>{this.props.t('header_six')}</DropdownItem>
</DropdownMenu>
</Dropdown>
)
}
}
TableToolBar = translate("translations")(TableToolBar);
MoreMenu = translate("translations")(MoreMenu);
HeaderList = translate("translations")(HeaderList);
export { IconButton, TableToolBar, Button, ButtonGroup, MoreMenu, HeaderList }

View File

@ -0,0 +1,21 @@
import React from 'react';
import dayjs from 'dayjs';
class FileInfor extends React.Component {
render() {
let modifyTime = dayjs(this.props.fileInfo.mtime*1000).format("YYYY-MM-DD HH:mm");
return (
<div className="topbar-file-info">
<div className="file-name">
{this.props.fileInfo.name}
</div>
<div className="file-mtime">
{modifyTime}
</div>
</div>
)
}
}
export default FileInfor

View File

@ -0,0 +1,76 @@
import React from 'react';
class ShortCut extends React.Component {
constructor (props) {
super(props);
// get platform of browser
this.platfrom = navigator.platform.indexOf('Win')< 0 ? "Mac": "Win";
}
render () {
let shortcutFirKey = this.props.shortcutFirKey;
let shortcutSecnKey = this.props.shortcutSecnKey;
// shortcutFirstKey is an array when the key are diffrent in win and mac
if (typeof shortcutFirKey === 'object') {
if (this.platfrom === "Win") {
shortcutFirKey = shortcutFirKey[0]
} else {
shortcutFirKey = shortcutFirKey[1]
}
}
return (
<li className={'help-shortcut'}>
<div className={"help-shortcut-left"}>{this.props.shortcutName}</div>
{shortcutFirKey || shortcutSecnKey?
<div className={"help-shortcut-right"}>
{shortcutFirKey? <div className={'key shortcut-first'}>{shortcutFirKey}</div>: null}
{shortcutSecnKey? <div className={'key shortcut-second'}>{shortcutSecnKey}</div>: null}
</div>
: null
}
</li>
)
}
}
class HelpShortcutList extends React.Component {
render (){
let title = this.props.data.shortcutType;
let dataList = this.props.data.shortcutData;
let liArr = [];
for (let prop in dataList){
let shortcutKeyArr = dataList[prop];
liArr.push(<ShortCut key={'help '+ prop} shortcutName={prop} shortcutSecnKey={shortcutKeyArr? shortcutKeyArr[1]:null} shortcutFirKey={shortcutKeyArr? shortcutKeyArr[0]: null}/>);
}
return (
<div className={'help-content-container'}>
<h5 className={'help-shortcut-type'}>
{title}
</h5>
<ul className={'help-shortcut-list'}>
{ liArr }
</ul>
</div>
)
}
}
class UserHelpDialog extends React.Component {
render () {
return (
<div className={'seafile-editor-help align-self-end'}>
<div className={'help-header d-flex'}>
<div className={'help-title'}>{this.props.userHelp.title}</div>
<div className={'help-close'} onClick={this.props.hideHelpDialog}><i className={'fa fa-times-circle'}/></div>
</div>
<div className={'help-content'}>
{this.props.userHelp.userHelpData.map((item, index) => {return <HelpShortcutList key={'help-list'+ index} data={item}/>})
}
</div>
</div>
)
}
}
export default UserHelpDialog

View File

@ -10,9 +10,9 @@ class OutlineItem extends React.Component {
const node = this.props.node;
var c;
if (node.depth === 2) {
c = "outline-h2";
c = "seafile-md-viewer-outline-heading2";
} else if (node.depth === 3) {
c = "outline-h3";
c = "seafile-md-viewer-outline-heading3";
}
return (
@ -27,24 +27,24 @@ class OutlineView extends React.Component {
render() {
const root = this.props.treeRoot;
var headerList = root.children.filter(node => {
var headingList = root.children.filter(node => {
return (node.type === "heading" &&
(node.depth === 2 || node.depth === 3));
})
for (let i = 0; i < headerList.length; i++) {
for (let child of headerList[i].children) {
for (let i = 0; i < headingList.length; i++) {
for (let child of headingList[i].children) {
if (child.type === "text") {
headerList[i].text = child.value;
headingList[i].text = child.value;
break;
}
}
headerList[i].key = i;
headingList[i].key = i;
}
return (
<div className="seafile-viewer-outline">
{headerList.map(node => {
{headingList.map(node => {
return (
<OutlineItem
key={node.key}

View File

@ -234,6 +234,13 @@ function _slateNodeToMD(node) {
type: 'html',
value: node.data.html
};
default :
// turn the block to paragraph default when it`s type is unknown
mdNodes = parseChildren(node);
return {
type: 'paragraph',
children: mdNodes
}
}
} else if (node.object == "text") {
return _text2MdNodes(node);
@ -261,7 +268,13 @@ function _slateNodeToMD(node) {
type: 'html',
value: node.data.html
};
// turn inlines to text default, when there is no node.type
default:
return _text2MdNodes(node)
}
} else {
// turn the node to text default when it`s type neither is inline, block or text
return _text2MdNodes(node);
}
}

View File

@ -18,8 +18,8 @@
},
pageOptions: {
repoID: '{{ repo.id }}',
filePath: '{{ path }}',
fileName: '{{ filename }}',
filePath: '{{ path|escapejs }}',
fileName: '{{ filename|escapejs }}',
domain: '{{ domain }}',
protocol: '{{ protocol }}',
lang: '{{ language_code }}',