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:
parent
6151c80b93
commit
748670b1a2
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
@ -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
@ -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":[]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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 |
25
frontend/package-lock.json
generated
25
frontend/package-lock.json
generated
@ -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"
|
||||
|
@ -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": [
|
||||
|
@ -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":[]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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"],
|
||||
"拖拽任意图片上传":[]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -53,7 +53,6 @@
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
margin-left: 8px;
|
||||
float: left;
|
||||
}
|
||||
.topbar-file-info .file-name {
|
||||
font-size: 1.2rem;
|
||||
|
@ -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%);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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">
|
||||
|
@ -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 (
|
||||
|
@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
});
|
||||
|
@ -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 }
|
||||
|
21
frontend/src/lib/topbarcomponent/file-info.js
Normal file
21
frontend/src/lib/topbarcomponent/file-info.js
Normal 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
|
76
frontend/src/lib/user-help.js
Normal file
76
frontend/src/lib/user-help.js
Normal 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
|
@ -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}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 }}',
|
||||
|
Loading…
Reference in New Issue
Block a user