完成markdown编辑器的重构
parent
5dec1ebdb7
commit
b52c95f210
|
@ -6,14 +6,11 @@
|
|||
|
||||
import {
|
||||
css,
|
||||
raw,
|
||||
html,
|
||||
Component,
|
||||
bind,
|
||||
unbind,
|
||||
range,
|
||||
nextTick,
|
||||
styleMap,
|
||||
classMap,
|
||||
outsideClick,
|
||||
clearOutsideClick
|
||||
} from 'wkit'
|
||||
|
@ -201,49 +198,24 @@ class Editor extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
.scroll-outerbox {
|
||||
.wrapper {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
.scroll-innerbox {
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
scrollbar-width: 0;
|
||||
flex: 1;
|
||||
padding: 6px 12px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.is-vertical {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 10px;
|
||||
height: 100%;
|
||||
|
||||
.thumb {
|
||||
display: block;
|
||||
width: 6px;
|
||||
height: 0;
|
||||
border-radius: 5px;
|
||||
background: rgba(44, 47, 53, 0.25);
|
||||
cursor: default;
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
width: 10px;
|
||||
background: rgba(44, 47, 53, 0.5);
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
visibility: hidden;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
visibility: visible;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,6 +223,7 @@ class Editor extends Component {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 54px;
|
||||
line-height: 1.5;
|
||||
outline: none;
|
||||
text-wrap: wrap;
|
||||
word-break: break-all;
|
||||
|
@ -397,7 +370,6 @@ class Editor extends Component {
|
|||
]
|
||||
|
||||
#value = ''
|
||||
#cache = { bar: 0, y: 0 }
|
||||
#gridx = 0
|
||||
#gridy = 0
|
||||
|
||||
|
@ -419,33 +391,12 @@ class Editor extends Component {
|
|||
if (this.$refs.editor) {
|
||||
this.#value = val
|
||||
this.$refs.editor.innerHTML = val
|
||||
this.$emit('input')
|
||||
} else {
|
||||
nextTick(_ => (this.value = val))
|
||||
}
|
||||
}
|
||||
|
||||
__init__() {
|
||||
//
|
||||
let { outer, inner, thumb } = this.$refs
|
||||
let height = outer.offsetHeight
|
||||
let scrollHeight = inner.scrollHeight + 10
|
||||
|
||||
let bar = 50 // 滚动条的高度
|
||||
bar = (height * (height / scrollHeight)) >> 0
|
||||
|
||||
if (bar < 50) {
|
||||
bar = 50
|
||||
}
|
||||
|
||||
// 100%或主体高度比滚动条还短时不显示
|
||||
if (bar >= height) {
|
||||
bar = 0
|
||||
}
|
||||
|
||||
this.#cache.bar = bar
|
||||
thumb.style.height = bar + 'px'
|
||||
}
|
||||
|
||||
#updateStat() {
|
||||
if (this.$refs.editor) {
|
||||
if (this.readOnly || this.disabled) {
|
||||
|
@ -458,23 +409,6 @@ class Editor extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
#fetchScroll(moveY) {
|
||||
let { bar } = this.#cache
|
||||
let { outer, thumb, inner } = this.$refs
|
||||
let height = outer.offsetHeight
|
||||
let scrollHeight = inner.scrollHeight + 10
|
||||
|
||||
if (moveY < 0) {
|
||||
moveY = 0
|
||||
} else if (moveY > height - bar) {
|
||||
moveY = height - bar
|
||||
}
|
||||
|
||||
inner.scrollTop = (scrollHeight - height) * (moveY / (height - bar))
|
||||
thumb.style.transform = `translateY(${moveY}px)`
|
||||
return moveY
|
||||
}
|
||||
|
||||
#hideLayers() {
|
||||
this.$refs.font.classList.remove('fadein')
|
||||
this.$refs.color.classList.remove('fadein')
|
||||
|
@ -707,83 +641,11 @@ class Editor extends Component {
|
|||
}
|
||||
|
||||
mounted() {
|
||||
let startY,
|
||||
moveY,
|
||||
mousemoveFn = ev => {
|
||||
let { y } = this.#cache
|
||||
if (startY !== undefined) {
|
||||
moveY = this.#fetchScroll(y + ev.pageY - startY)
|
||||
}
|
||||
},
|
||||
mouseupFn = ev => {
|
||||
startY = undefined
|
||||
this.#cache.y = moveY || 0
|
||||
delete this._active
|
||||
unbind(document, 'mousemove', mousemoveFn)
|
||||
unbind(document, 'mouseup', mouseupFn)
|
||||
}
|
||||
|
||||
this.exec('styleWithCSS', true)
|
||||
|
||||
bind(this.$refs.thumb, 'mousedown', ev => {
|
||||
startY = ev.pageY
|
||||
|
||||
this._active = true
|
||||
this.#hideLayers()
|
||||
|
||||
bind(document, 'mousemove', mousemoveFn)
|
||||
bind(document, 'mouseup', mouseupFn)
|
||||
})
|
||||
|
||||
// 鼠标滚动事件
|
||||
bind(this.$refs.inner, 'scroll', ev => {
|
||||
// 拖拽时忽略滚动事件
|
||||
if (this._active) {
|
||||
return
|
||||
}
|
||||
|
||||
let { bar, y } = this.#cache
|
||||
let { outer, thumb, inner } = this.$refs
|
||||
let height = outer.offsetHeight
|
||||
let scrollHeight = inner.scrollHeight + 10
|
||||
let scrollTop = inner.scrollTop
|
||||
|
||||
this.#hideLayers()
|
||||
|
||||
// y轴 都为0时, 不作任何处理
|
||||
if (bar === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// 修正滚动条的位置
|
||||
// 滚动比例 y 滚动条的可移动距离
|
||||
let fixedY = ~~((scrollTop / (scrollHeight - height)) * (height - bar))
|
||||
|
||||
if (fixedY !== y) {
|
||||
this.#cache.y = fixedY
|
||||
thumb.style.transform = `translateY(${fixedY}px)`
|
||||
}
|
||||
})
|
||||
|
||||
this._clickoutsideFn = outsideClick(this, _ => this.#hideLayers())
|
||||
|
||||
this._scrollFn = new ResizeObserver(this.__init__.bind(this))
|
||||
this._scrollFn.observe(this.$refs.cont)
|
||||
|
||||
this._inputFn = new MutationObserver(_ => {
|
||||
this.$emit('input')
|
||||
})
|
||||
|
||||
this._inputFn.observe(this.$refs.editor, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true
|
||||
})
|
||||
}
|
||||
|
||||
unmounted() {
|
||||
this._scrollFn?.disconnect()
|
||||
this._inputFn?.disconnect()
|
||||
clearOutsideClick(this._clickoutsideFn)
|
||||
}
|
||||
|
||||
|
@ -809,22 +671,17 @@ class Editor extends Component {
|
|||
`
|
||||
)}
|
||||
</section>
|
||||
<div class="scroll-outerbox" ref="outer">
|
||||
<div class="scroll-innerbox" ref="inner">
|
||||
<div class="wrapper" ref="cont" @click=${this.#hideLayers}>
|
||||
<div
|
||||
ref="editor"
|
||||
contenteditable="true"
|
||||
class="typearea"
|
||||
@mouseleave=${this.saveSelection}
|
||||
@paste.prevent=${this.#handlePaste}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="is-vertical noselect">
|
||||
<span class="thumb" ref="thumb"></span>
|
||||
</div>
|
||||
|
||||
<div class="wrapper" ref="cont" @click=${this.#hideLayers}>
|
||||
<div
|
||||
ref="editor"
|
||||
contenteditable="true"
|
||||
class="typearea"
|
||||
@mouseleave=${this.saveSelection}
|
||||
@paste.prevent=${this.#handlePaste}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="font-layer noselect"
|
||||
ref="font"
|
||||
|
@ -836,6 +693,7 @@ class Editor extends Component {
|
|||
<span data-size="3">3号字体</span>
|
||||
<span data-size="2">2号字体</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="color-layer noselect"
|
||||
ref="color"
|
||||
|
@ -843,6 +701,7 @@ class Editor extends Component {
|
|||
>
|
||||
${COLORS.map(c => html`<span data-value=${c}></span>`)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="table-layer noselect"
|
||||
ref="table"
|
||||
|
@ -850,12 +709,11 @@ class Editor extends Component {
|
|||
@mousemove=${this.#tableSelect}
|
||||
@mouseleave=${this.#tableSelect}
|
||||
>
|
||||
${Array(81)
|
||||
.fill(0)
|
||||
.map((_, n) => html`<span data-idx=${n}></span>`)}
|
||||
${range(81).map((_, n) => html`<span data-idx=${n}></span>`)}
|
||||
</div>
|
||||
|
||||
<div class="link-layer noselect" ref="link">
|
||||
<wc-input ref="linkinput" label="请输入链接地址"></wc-input>
|
||||
<wc-input ref="linkinput" placeholder="请输入链接地址"></wc-input>
|
||||
<wc-button size="m" @click=${this.#insertLink}>插入</wc-button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,134 +4,94 @@
|
|||
* @date 2020/10/14 17:52:44
|
||||
*/
|
||||
|
||||
import { offset } from 'wkit'
|
||||
|
||||
var placeholder = '在此输入文本'
|
||||
const PLACEHOLDER = '在此输入文本'
|
||||
|
||||
function trim(str, sign) {
|
||||
return str.replace(new RegExp('^' + sign + '|' + sign + '$', 'g'), '')
|
||||
}
|
||||
|
||||
function docScroll(k = 'X') {
|
||||
return window[`page${k.toUpperCase()}Offset`]
|
||||
}
|
||||
|
||||
// 通用的弹层触发
|
||||
function showDialog(dialog, elem) {
|
||||
var { left, top } = offset(elem)
|
||||
left -= docScroll('X')
|
||||
top += 29 - docScroll('Y')
|
||||
left += 'px'
|
||||
top += 'px'
|
||||
dialog.moveTo({ top, left })
|
||||
dialog.show()
|
||||
return Promise.resolve(dialog)
|
||||
}
|
||||
|
||||
export default {
|
||||
header(elem) {
|
||||
header() {
|
||||
this.$refs.header.classList.add('fadein')
|
||||
},
|
||||
|
||||
h(level) {
|
||||
var wrap = this.selection(true) || placeholder
|
||||
var wrap = this.selection(true) || PLACEHOLDER
|
||||
wrap = wrap.replace(/^(#+ )?/, '#'.repeat(level) + ' ')
|
||||
this.insert(wrap, true)
|
||||
},
|
||||
|
||||
quote(elem) {
|
||||
var wrap = this.selection(true) || placeholder
|
||||
quote() {
|
||||
var wrap = this.selection(true) || PLACEHOLDER
|
||||
wrap = wrap.replace(/^(>+ )?/, '> ')
|
||||
|
||||
this.insert(wrap, true)
|
||||
},
|
||||
|
||||
bold(elem) {
|
||||
var wrap = this.selection() || placeholder
|
||||
bold() {
|
||||
var wrap = this.selection() || PLACEHOLDER
|
||||
var unwrap = trim(wrap, '\\*\\*')
|
||||
wrap = wrap === unwrap ? `**${wrap}**` : unwrap
|
||||
this.insert(wrap, true)
|
||||
},
|
||||
|
||||
italic(elem) {
|
||||
var wrap = this.selection() || placeholder
|
||||
italic() {
|
||||
var wrap = this.selection() || PLACEHOLDER
|
||||
var unwrap = trim(wrap, '_')
|
||||
wrap = wrap === unwrap ? `_${wrap}_` : unwrap
|
||||
this.insert(wrap, true)
|
||||
},
|
||||
|
||||
through(elem) {
|
||||
var wrap = this.selection() || placeholder
|
||||
through() {
|
||||
var wrap = this.selection() || PLACEHOLDER
|
||||
var unwrap = trim(wrap, '~~')
|
||||
wrap = wrap === unwrap ? `~~${wrap}~~` : unwrap
|
||||
this.insert(wrap, true)
|
||||
},
|
||||
|
||||
list(elem) {
|
||||
var wrap = this.selection(true) || placeholder
|
||||
list() {
|
||||
var wrap = this.selection(true) || PLACEHOLDER
|
||||
|
||||
wrap = wrap.replace(/^([+\-*] )?/, '+ ')
|
||||
this.insert(wrap, true)
|
||||
},
|
||||
|
||||
order(elem) {
|
||||
var wrap = this.selection(true) || placeholder
|
||||
order() {
|
||||
var wrap = this.selection(true) || PLACEHOLDER
|
||||
|
||||
wrap = wrap.replace(/^(\d+\. )?/, '1. ')
|
||||
this.insert(wrap, true)
|
||||
},
|
||||
|
||||
line(elem) {
|
||||
line() {
|
||||
this.insert('\n\n---\n\n', false)
|
||||
},
|
||||
|
||||
code(elem) {
|
||||
var wrap = this.selection() || placeholder
|
||||
code() {
|
||||
var wrap = this.selection() || PLACEHOLDER
|
||||
var unwrap = trim(wrap, '`')
|
||||
wrap = wrap === unwrap ? `\`${wrap}\`` : unwrap
|
||||
this.insert(wrap, true)
|
||||
},
|
||||
|
||||
codeblock(elem) {
|
||||
codeblock() {
|
||||
this.insert('\n```language\n\n```\n')
|
||||
},
|
||||
|
||||
table(elem) {
|
||||
// showDialog(this.__TABLE_ADDON__, elem)
|
||||
table() {
|
||||
this.$refs.table.classList.add('fadein')
|
||||
},
|
||||
|
||||
link(elem) {
|
||||
showDialog(this.__LINK_ADDON__, elem).then(dialog => {
|
||||
var wrap = this.selection() || placeholder
|
||||
dialog.__txt__.value = wrap
|
||||
})
|
||||
link() {
|
||||
this.$refs.link.classList.add('fadein')
|
||||
},
|
||||
|
||||
image(elem) {
|
||||
var $file = this.__ATTACH_ADDON__.querySelector('input')
|
||||
|
||||
this._attach = 'image'
|
||||
$file.setAttribute('accept', 'image/*')
|
||||
|
||||
showDialog(this.__ATTACH_ADDON__, elem)
|
||||
},
|
||||
|
||||
attach(elem) {
|
||||
var $file = this.__ATTACH_ADDON__.querySelector('input')
|
||||
this._attach = 'file'
|
||||
$file.removeAttribute('accept')
|
||||
showDialog(this.__ATTACH_ADDON__, elem)
|
||||
},
|
||||
|
||||
fullscreen(elem) {
|
||||
fullscreen() {
|
||||
this.classList.toggle('fullscreen')
|
||||
elem.classList.toggle('active')
|
||||
},
|
||||
preview(elem) {
|
||||
preview() {
|
||||
this.previewEnabled = !this.previewEnabled
|
||||
this.$refs.view.classList.toggle('active')
|
||||
elem.classList.toggle('active')
|
||||
if (this.previewEnabled) {
|
||||
this.$refs.view.code = this.value
|
||||
}
|
||||
|
|
|
@ -4,9 +4,6 @@
|
|||
* @date 2020/10/12 18:23:23
|
||||
*/
|
||||
|
||||
import { html } from 'wkit'
|
||||
import ICONS from './svg.js'
|
||||
|
||||
const ELEMS = {
|
||||
a: function (str, attr, inner) {
|
||||
let href = attr.match(attrExp('href'))
|
||||
|
@ -83,31 +80,6 @@ export const DEFAULT_TOOLS = [
|
|||
'preview'
|
||||
]
|
||||
|
||||
export const TOOL_TITLE = {
|
||||
header: '插入标题',
|
||||
h1: '一级标题',
|
||||
h2: '二级标题',
|
||||
h3: '三级标题',
|
||||
h4: '四级标题',
|
||||
h5: '五级标题',
|
||||
h6: '六级标题',
|
||||
quote: '引用文本',
|
||||
bold: '粗体',
|
||||
italic: '斜体',
|
||||
through: '横线',
|
||||
list: '无序列表',
|
||||
order: '有序列表',
|
||||
line: '分割线',
|
||||
code: '行内代码',
|
||||
codeblock: '插入代码块',
|
||||
table: '插入表格',
|
||||
link: '插入连接',
|
||||
image: '上传图片',
|
||||
attach: '上传附件',
|
||||
fullscreen: '全屏编辑',
|
||||
preview: '预览'
|
||||
}
|
||||
|
||||
const LI_EXP = /<(ul|ol)>(?:(?!<ul|<ol)[\s\S])*?<\/\1>/gi
|
||||
|
||||
// html标签的属性正则
|
||||
|
@ -126,21 +98,6 @@ function tagExp(tag, open) {
|
|||
return new RegExp(exp, 'gi')
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染工具栏图标
|
||||
*/
|
||||
export function renderToolbar(list = [], dict = {}, showText = false) {
|
||||
return list.map(it => {
|
||||
let title = showText ? '' : dict[it]
|
||||
let text = showText ? dict[it] || '' : ''
|
||||
|
||||
return html`<span data-act=${it} title=${title}>
|
||||
<svg class="icon" viewBox="0 0 1024 1024"><path d=${ICONS[it]} /></svg>
|
||||
${text}
|
||||
</span>`
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* html转成md
|
||||
*/
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
raw,
|
||||
html,
|
||||
Component,
|
||||
range,
|
||||
bind,
|
||||
unbind,
|
||||
nextTick,
|
||||
|
@ -18,7 +19,7 @@ import {
|
|||
clearOutsideClick
|
||||
} from 'wkit'
|
||||
|
||||
import { renderToolbar, DEFAULT_TOOLS, html2md } from './helper.js'
|
||||
import { DEFAULT_TOOLS, html2md } from './helper.js'
|
||||
import Addon from './addon.js'
|
||||
import markd from '../markd/index.js'
|
||||
|
||||
|
@ -26,6 +27,8 @@ import '../form/input.js'
|
|||
import '../form/button.js'
|
||||
import '../code/index.js'
|
||||
|
||||
import ICONS from './svg.js'
|
||||
|
||||
const COLORS = [
|
||||
'#f3f5fb',
|
||||
'#dae1e9',
|
||||
|
@ -83,6 +86,7 @@ class MEditor extends Component {
|
|||
display: flex;
|
||||
min-width: 200px;
|
||||
max-height: 720px;
|
||||
min-height: 64px;
|
||||
border-radius: 3px;
|
||||
transition: box-shadow 0.15s linear;
|
||||
background: var(--wc-meditor-background, #fff);
|
||||
|
@ -135,6 +139,12 @@ class MEditor extends Component {
|
|||
fill: currentColor;
|
||||
color: #62778d;
|
||||
}
|
||||
input {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
|
@ -155,7 +165,6 @@ class MEditor extends Component {
|
|||
flex: 1;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
border-radius: 3px;
|
||||
|
||||
.editor,
|
||||
|
@ -197,16 +206,12 @@ class MEditor extends Component {
|
|||
`,
|
||||
css`
|
||||
:host([readonly]) {
|
||||
.editor {
|
||||
cursor: default;
|
||||
opacity: 0.8;
|
||||
}
|
||||
cursor: default;
|
||||
opacity: 0.8;
|
||||
}
|
||||
:host([disabled]) {
|
||||
.editor {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
:host([readonly]),
|
||||
:host([disabled]) {
|
||||
|
@ -235,6 +240,7 @@ class MEditor extends Component {
|
|||
|
||||
css`
|
||||
.font-layer,
|
||||
.link-layer,
|
||||
.table-layer {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
|
@ -310,119 +316,16 @@ class MEditor extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
.addon-link {
|
||||
width: 320px;
|
||||
padding: 8px 5px;
|
||||
background: #fff;
|
||||
font-size: 13px;
|
||||
.link-layer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
left: 330px;
|
||||
width: 230px;
|
||||
padding: 8px;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
margin-top: 6px;
|
||||
|
||||
label {
|
||||
width: 60px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
wc-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
wc-button {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.addon-attach {
|
||||
width: 320px;
|
||||
padding: 8px 5px;
|
||||
background: #fff;
|
||||
font-size: 13px;
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--color-plain-2);
|
||||
user-select: none;
|
||||
|
||||
span {
|
||||
height: 28px;
|
||||
padding: 0 8px;
|
||||
line-height: 28px;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
color: var(--color-teal-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remote,
|
||||
.locale {
|
||||
display: none;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.locale {
|
||||
height: 120px;
|
||||
padding: 24px 32px;
|
||||
|
||||
.button {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 12px 16px;
|
||||
line-height: 46px;
|
||||
border: 1px dashed var(--color-plain-3);
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background 0.1s ease-in-out;
|
||||
|
||||
&::after {
|
||||
content: '点击选择文件,或拖拽文件到此处';
|
||||
}
|
||||
&:hover,
|
||||
&.active {
|
||||
background: rgba(255, 228, 196, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
margin-top: 6px;
|
||||
|
||||
label {
|
||||
width: 60px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
wc-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
wc-button {
|
||||
width: 80px;
|
||||
}
|
||||
wc-button {
|
||||
width: 40px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
@ -481,7 +384,7 @@ class MEditor extends Component {
|
|||
#hideLayers() {
|
||||
this.$refs.header.classList.remove('fadein')
|
||||
// this.$refs.color.classList.remove('fadein')
|
||||
// this.$refs.link.classList.remove('fadein')
|
||||
this.$refs.link.classList.remove('fadein')
|
||||
this.$refs.table.classList.remove('fadein')
|
||||
}
|
||||
|
||||
|
@ -490,6 +393,7 @@ class MEditor extends Component {
|
|||
if (ev && ev.type === 'change') {
|
||||
file = ev.target.files[0]
|
||||
ev.target.value = ''
|
||||
t = ev.target.dataset.type === 'image' ? '!' : ''
|
||||
}
|
||||
this.$emit('upload', {
|
||||
detail: {
|
||||
|
@ -564,6 +468,10 @@ class MEditor extends Component {
|
|||
|
||||
this.#hideLayers()
|
||||
|
||||
if (act === 'image' || act === 'attach') {
|
||||
return
|
||||
}
|
||||
|
||||
Addon[act].call(this, elem)
|
||||
}
|
||||
|
||||
|
@ -582,9 +490,7 @@ class MEditor extends Component {
|
|||
if (value) {
|
||||
this.$refs.link.classList.remove('fadein')
|
||||
this.$refs.editor.focus()
|
||||
this.restoreSelection()
|
||||
this.exec(ACTTION.link, value)
|
||||
this.saveSelection()
|
||||
this.insert(`[${value}](${value}) `)
|
||||
this.$refs.linkinput.value = ''
|
||||
}
|
||||
}
|
||||
|
@ -889,14 +795,25 @@ class MEditor extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
mounted() {
|
||||
let pb = ~~(this.clientHeight * 0.6)
|
||||
pb = pb < 64 ? 64 : pb
|
||||
Addon.preview.call(this, this)
|
||||
this.$refs.editor.style.paddingBottom = pb + 'px'
|
||||
#fixedPadding() {
|
||||
if (this.clientHeight > 64) {
|
||||
let pb = ~~(this.clientHeight * 0.6)
|
||||
pb = pb < 64 ? 64 : pb
|
||||
this.$refs.editor.style.paddingBottom = pb + 'px'
|
||||
} else {
|
||||
this.$refs.editor.style.cssText = 'padding-bottom:;'
|
||||
}
|
||||
}
|
||||
|
||||
unmounted() {}
|
||||
mounted() {
|
||||
this.#fixedPadding()
|
||||
this.__observer = new ResizeObserver(this.#fixedPadding.bind(this))
|
||||
this.__observer.observe(this)
|
||||
}
|
||||
|
||||
unmounted() {
|
||||
this.__observer?.disconnect()
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
|
@ -905,7 +822,22 @@ class MEditor extends Component {
|
|||
class=${classMap({ toolbar: true, active: this.#toolbar.length })}
|
||||
@click=${this.#toolbarClick}
|
||||
>
|
||||
${renderToolbar(this.#toolbar)}
|
||||
${this.#toolbar.map(it => {
|
||||
return html`<span data-act=${it}>
|
||||
<svg class="icon" viewBox="0 0 1024 1024">
|
||||
<path d=${ICONS[it]} />
|
||||
</svg>
|
||||
${it === 'image' || it === 'attach'
|
||||
? html`<input
|
||||
type="file"
|
||||
data-type=${it}
|
||||
accept=${it === 'image' ? 'image/*' : '*/*'}
|
||||
:disabled=${this.readOnly || this.disabled}
|
||||
@change=${this.#handleFile}
|
||||
/>`
|
||||
: ''}
|
||||
</span>`
|
||||
})}
|
||||
</header>
|
||||
<div class="editor-outbox">
|
||||
<textarea
|
||||
|
@ -919,6 +851,7 @@ class MEditor extends Component {
|
|||
@input=${this.#updatePreview}
|
||||
@scroll=${this.#syncScrollToPreview}
|
||||
></textarea>
|
||||
|
||||
<wc-markd ref="view" class="preview"></wc-markd>
|
||||
</div>
|
||||
|
||||
|
@ -942,9 +875,12 @@ class MEditor extends Component {
|
|||
@mousemove=${this.#tableSelect}
|
||||
@mouseleave=${this.#tableSelect}
|
||||
>
|
||||
${Array(81)
|
||||
.fill(0)
|
||||
.map((_, n) => html`<span data-idx=${n}></span>`)}
|
||||
${range(81).map((_, n) => html`<span data-idx=${n}></span>`)}
|
||||
</div>
|
||||
|
||||
<div class="link-layer noselect" ref="link">
|
||||
<wc-input ref="linkinput" placeholder="请输入链接地址"></wc-input>
|
||||
<wc-button size="m" @click=${this.#insertLink}>插入</wc-button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
|
Loading…
Reference in New Issue