ui: use vditor as default markdown editor

This commit is contained in:
undefined committed 2021-04-17 23:01:13 +08:00
1 parent 981b21160f
commit d7a6e30743
13 files changed
+109 -266

No files matched your search

@@ -114,13 +114,9 @@ export default class AutoComplete extends DOMAttachedObject {
}
async getItems(val) {
if (this.cache[val] !== undefined) {
return this.cache[val];
}
if (this.cache[val] !== undefined) return this.cache[val];
const data = await this.options.items(val);
if (this.options.cache) {
this.cache[val] = data;
}
if (this.options.cache) this.cache[val] = data;
return data;
}
@@ -148,9 +144,7 @@ export default class AutoComplete extends DOMAttachedObject {
}
open() {
if (this.isOpen) {
return;
}
if (this.isOpen) return;
this.dropInstance.open();
this.isOpen = true;
}
@@ -0,0 +1,38 @@
import React from 'react';
import Vditor from 'vditor';
import { config } from './index';
export default class MarkdownEditor extends React.PureComponent {
constructor(props) {
super(props);
this.ref = React.createRef();
}
async componentDidMount() {
await new Promise((resolve) => {
this.editor = new Vditor(this.ref.current, {
...config,
after: resolve,
input(value) { this.props.onChange(value); },
cache: { id: Math.random().toString() },
});
});
}
componentWillUnmount() {
this.editor.destory();
this.editor.detach();
}
value() {
return this.editor.getValue();
}
focus() {
return this.editor.focus();
}
render() {
return <div ref={this.ref}></div>;
}
}
@@ -1,91 +1,2 @@
.simplemde
border: 1px solid $editor-border-color
margin: 0 0 1rem
position: relative
height: rem(200px)
box-sizing: border-box
overflow: hidden
.cm-s-simplemde
border: 0
min-height: 0
height: auto
padding: 0
position: absolute
left: 0
top: 40px
width: 100%
bottom: 0
color: inherit
.simplemde-toolbar
position: absolute
left: 0
top: 0
width: 100%
border-radius: 0
border: 0
opacity: 0
background: $toolbar-bg
transform: translateY(-20px)
transition: opacity .5s, transform .5s
transition-timing-function: ease-out-cubic
&:hover
opacity: 1
.toolbar--visible .simplemde-toolbar
opacity: 1
transform: translateY(0)
.cm-s-simplemde .CodeMirror-scroll
height: 100%
min-height: 0
font-family: $code-font-family
color: $text-1-color
.simplemde-toolbar:before,
.simplemde-toolbar:after
display: none
.simplemde-toolbar
padding: rem(5px)
.simplemde-toolbar i.separator
border-right: 0
.simplemde-toolbar a
color: $toolbar-fore !important
border: 0
background: none
border-radius: 0
font-size: rem($font-size-icon)
outline: 0
&:hover
background: $toolbar-bg-hover
color: $toolbar-fore-hover !important
border: 0
&.active
background: $toolbar-bg-selected
color: $toolbar-fore-selected !important
border: 0
.simplemde-toolbar.disabled-for-preview a:not(.no-disable)
background: none
opacity: $toolbar-disabled-opacity
.cm-s-simplemde .CodeMirror-code .cm-comment
background: none
.simplemde-statusbar
display: none
.simplemde-preview
background: #FFF
.no-heading .cm-s-simplemde .CodeMirror-code
.cm-header-1, .cm-header-2, .cm-header-3, .cm-header-4
font-size: 1em
line-height: 1.5
.vditor
margin-bottom: rem(10px)
@@ -1,65 +1,83 @@
import _ from 'lodash';
import DOMAttachedObject from 'vj/components/DOMAttachedObject';
export const config = {
toolbar: [
'emoji', 'headings', 'bold', 'italic', 'strike', 'link', '|',
'list', 'ordered-list', 'check', 'outdent', 'indent', '|',
'quote', 'line', 'code', 'inline-code', '|',
'upload', 'record', 'table', '|',
'edit-mode', 'content-theme', 'code-theme', 'export', 'preview',
],
mode: 'ir',
toolbarConfig: {
pin: true,
},
counter: {
enable: true,
max: 65536,
},
};
export default class CmEditor extends DOMAttachedObject {
static DOMAttachKey = 'vjCmEditorInstance';
constructor($dom, options = {}) {
super($dom);
this.editor = null;
this.options = options;
this.init();
}
async init() {
const { default: VjCmEditor } = await import('./vjcmeditor');
const hasFocus = this.$dom.is(':focus');
const srcHeight = this.$dom.outerHeight();
this.editor = new VjCmEditor({
...this.options,
element: this.$dom.get(0),
const { default: Vditor } = await import('vditor');
const { $dom } = this;
const hasFocus = $dom.is(':focus');
const origin = $dom.get(0);
const ele = document.createElement('div');
const value = $dom.text();
await new Promise((resolve) => {
this.editor = new Vditor(ele, {
...config,
after: resolve,
input(v) { $dom.text(v); },
value,
cache: { id: Math.random().toString() },
upload: {
accept: 'image/*,.mp3, .wav, .zip',
url: '/api/upload/editor',
linkToImgUrl: '/api/upload/fetch',
filename(name) {
return name.replace(/[^(a-zA-Z0-9\u4e00-\u9fa5.)]/g, '')
.replace(/[?\\/:|<>*[\]()$%{}@~]/g, '')
.replace('/\\s/g', '');
},
},
});
});
this.moveToEnd();
const $editor = $(this.editor.wrapper);
$editor.css('height', srcHeight);
$editor.addClass('toolbar--visible');
if (hasFocus) {
this.focus();
}
}
isValid() {
return (this.editor !== null);
$(ele).addClass('textbox');
$dom.hide();
origin.parentElement.appendChild(ele);
if (hasFocus) this.focus();
this.isValid = true;
}
ensureValid() {
if (!this.isValid()) {
throw new Error('VjCmEditor is not loaded');
}
if (!this.isValid) throw new Error('Editor is not loaded');
}
value(...argv) {
this.ensureValid();
const ret = this.editor.value(...argv);
if (argv.length > 0) {
this.moveToEnd();
}
return ret;
}
destory() {
this.editor.destory();
}
focus() {
this.ensureValid();
this.editor.codemirror.focus();
}
moveToEnd() {
this.ensureValid();
const cm = this.editor.codemirror;
cm.setCursor(cm.lineCount(), 0);
this.editor.focus();
}
}
@@ -1,120 +0,0 @@
import _ from 'lodash';
import SimpleMDE from 'vj-simplemde';
import request from 'vj/utils/request';
import i18n from 'vj/utils/i18n';
export default class VjCmEditor extends SimpleMDE {
constructor(options = {}) {
super({
autoDownloadFontAwesome: false,
spellChecker: false,
forceSync: true,
toolbar: [
{
name: 'bold',
action: SimpleMDE.toggleBold,
className: 'icon icon-bold',
title: i18n('Bold'),
},
{
name: 'italic',
action: SimpleMDE.toggleItalic,
className: 'icon icon-italic',
title: i18n('Italic'),
},
'|',
{
name: 'quote',
action: SimpleMDE.toggleBlockquote,
className: 'icon icon-quote',
title: i18n('Quote'),
},
{
name: 'unordered-list',
action: SimpleMDE.toggleUnorderedList,
className: 'icon icon-unordered_list',
title: i18n('Unordered List'),
},
{
name: 'ordered-list',
action: SimpleMDE.toggleOrderedList,
className: 'icon icon-ordered_list',
title: i18n('Ordered List'),
},
'|',
{
name: 'code',
action: () => this.insertCodeBlock(),
className: 'icon icon-code',
title: i18n('Insert Code'),
default: true,
},
{
name: 'link',
action: SimpleMDE.drawLink,
className: 'icon icon-link',
title: i18n('Create Link'),
default: true,
},
{
name: 'image',
action: SimpleMDE.drawImage,
className: 'icon icon-insert--image',
title: i18n('Insert Image'),
default: true,
},
'|',
{
name: 'preview',
action: SimpleMDE.togglePreview,
preAction: SimpleMDE.preRenderPreview,
className: 'icon icon-preview no-disable',
title: i18n('Toggle Preview'),
default: true,
},
],
commonmark: {
safe: true,
},
...options,
});
}
async markdown(text) {
// FIXME preview wont update after first toggle
const data = await request.ajax({
url: '/markdown',
method: 'post',
data: $.param({ text }, true),
dataType: 'text',
});
_.defer(this.preparePreview.bind(this));
return data;
}
preparePreview() {
const $preview = $(this.wrapper).find('.simplemde-preview');
$preview.addClass('typo');
$preview.attr('data-emoji-enabled', 'true');
$preview.trigger('vjContentNew');
}
insertCodeBlock() {
const text = this.codemirror.getSelection();
const startPoint = this.codemirror.getCursor('start');
const endPoint = this.codemirror.getCursor('end');
const leadingLines = (startPoint.line === 0 && startPoint.ch === 0) ? 0 : 2;
// eslint-disable-next-line prefer-template
const startText = _.repeat('\n', leadingLines) + '```cpp\n';
const endText = '\n```\n';
this.codemirror.replaceSelection(`${startText}${text}${endText}`);
startPoint.line += leadingLines + 1;
startPoint.ch = 0;
if (startPoint !== endPoint) {
endPoint.line += leadingLines + 1;
}
this.codemirror.setSelection(startPoint, endPoint);
this.codemirror.focus();
}
}
@@ -8,11 +8,11 @@ $downvote-color = red
display: none
.dczcomments__box .textbox
height: rem(180px)
min-height: rem(180px)
resize: vertical
.textbox[name="dczcomments__dummy-box"]
height: rem(60px)
min-height: rem(60px)
resize: none
font-size: rem($font-size-title)
color: $comment-placeholder-color
@@ -23,6 +23,9 @@ input.textbox, textarea.textbox
textarea.textbox
height: rem(200px)
div.textbox
min-height: rem(200px)
.data-table
input.textbox, textarea.textbox
margin-bottom: 0
@@ -7,6 +7,4 @@ span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; }
span.cm-variable { color: black; }
span.cm-meta { color: #2b91af; }
span.cm-link { color: #3a3; }
.CodeMirror-activeline-background { background: #e8f2ff; }
.CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }
}
+1 -2
View File
@@ -34,7 +34,6 @@
"chalk": "^4.1.0",
"classnames": "^2.3.1",
"clipboard": "^2.0.8",
"codemirror": "^5.59.4",
"copy-webpack-plugin": "^6.1.1",
"css-loader": "^4.2.2",
"diff-dom": "^4.2.2",
@@ -95,8 +94,8 @@
"timeago-react": "^3.0.2",
"timeago.js": "^4.0.2",
"uuid": "^8.3.2",
"vditor": "^3.8.4",
"vinyl-buffer": "^1.0.1",
"vj-simplemde": "0.0.8",
"wastyle": "^0.0.5",
"webpack": "^4.44.2",
"webpack-bundle-analyzer": "^4.4.0",
@@ -57,7 +57,6 @@ function parseCategorySelection() {
$txt.val().split(',').map((name) => name.trim()).forEach((name) => {
if (!name) return;
const [category, subcategory] = findCategory(name);
console.log(name, category, subcategory);
if (!category) tags.push(name);
else if (!subcategory) {
categories[category].select = true;
-2
View File
@@ -1,6 +1,4 @@
import 'normalize.css/normalize.css';
import 'codemirror/lib/codemirror.css';
import 'vj-simplemde/src/css/simplemde.css';
import 'pickadate/lib/themes/classic.css';
import 'pickadate/lib/themes/classic.date.css';
import 'pickadate/lib/themes/classic.time.css';
+1 -2
View File
@@ -1,6 +1,5 @@
import 'normalize.css/normalize.css';
import 'codemirror/lib/codemirror.css';
import 'vj-simplemde/src/css/simplemde.css';
import 'vditor/dist/index.css';
import 'pickadate/lib/themes/classic.css';
import 'pickadate/lib/themes/classic.date.css';
import 'pickadate/lib/themes/classic.time.css';
@@ -0,0 +1,6 @@
export default function cloneAttributes(from, to) {
const attributes = from.prop('attributes');
$.each(attributes, function () {
to.attr(this.name, this.value);
});
}