更新markdown编辑器;
parent
09356d0d92
commit
779aeb65a2
|
@ -605,7 +605,7 @@ class Editor extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
#chnageFontSize(ev) {
|
||||
#changeFontSize(ev) {
|
||||
if (ev.target === ev.currentTarget) {
|
||||
return
|
||||
}
|
||||
|
@ -677,6 +677,8 @@ class Editor extends Component {
|
|||
grids[i].classList.toggle('active', _x <= x && _y <= y)
|
||||
}
|
||||
} else {
|
||||
this.#gridx = -1
|
||||
this.#gridy = -1
|
||||
grids.forEach(it => it.classList.remove('active'))
|
||||
}
|
||||
}
|
||||
|
@ -826,7 +828,7 @@ class Editor extends Component {
|
|||
<div
|
||||
class="font-layer noselect"
|
||||
ref="font"
|
||||
@click=${this.#chnageFontSize}
|
||||
@click=${this.#changeFontSize}
|
||||
>
|
||||
<span data-size="6">6号字体</span>
|
||||
<span data-size="5">5号字体</span>
|
||||
|
|
|
@ -33,7 +33,7 @@ const Helper = {
|
|||
isHr(str) {
|
||||
var s = str[0]
|
||||
if (HR_LIST.includes(s)) {
|
||||
return str.slice(0, 3) === s.repeat(3) ? str.slice(3) : false
|
||||
return str.trim() === s.repeat(3)
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
@ -118,8 +118,8 @@ const Decoder = {
|
|||
.replace(ESCAPE_RE, '$1') // 处理转义字符
|
||||
},
|
||||
// 分割线
|
||||
hr(name = '') {
|
||||
return `\n\n<fieldset class="md-hr"><legend name="${name}"></legend></fieldset>\n\n`
|
||||
hr() {
|
||||
return `\n\n<hr>\n\n`
|
||||
},
|
||||
// 标题
|
||||
head(str) {
|
||||
|
@ -362,10 +362,8 @@ class Tool {
|
|||
}
|
||||
|
||||
// 无属性标签
|
||||
|
||||
let hrName = Helper.isHr(it)
|
||||
if (typeof hrName === 'string') {
|
||||
html += Decoder.hr(hrName)
|
||||
if (Helper.isHr(it)) {
|
||||
html += Decoder.hr()
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -76,19 +76,11 @@ class Markd extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
fieldset.md-hr {
|
||||
hr {
|
||||
height: 1px;
|
||||
margin: 30px 0;
|
||||
border: 0;
|
||||
border-top: 1px dashed var(--color-plain-3);
|
||||
|
||||
legend {
|
||||
color: var(--color-grey-1);
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
&::before {
|
||||
content: attr(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
ol {
|
||||
margin-left: 1em;
|
||||
|
|
|
@ -30,7 +30,7 @@ function showDialog(dialog, elem) {
|
|||
|
||||
export default {
|
||||
header(elem) {
|
||||
showDialog(this.__HEADER_ADDON__, elem)
|
||||
this.$refs.header.classList.add('fadein')
|
||||
},
|
||||
|
||||
h(level) {
|
||||
|
@ -97,7 +97,8 @@ export default {
|
|||
},
|
||||
|
||||
table(elem) {
|
||||
showDialog(this.__TABLE_ADDON__, elem)
|
||||
// showDialog(this.__TABLE_ADDON__, elem)
|
||||
this.$refs.table.classList.add('fadein')
|
||||
},
|
||||
|
||||
link(elem) {
|
||||
|
|
|
@ -234,20 +234,71 @@ class MEditor extends Component {
|
|||
`,
|
||||
|
||||
css`
|
||||
.addon-table {
|
||||
.font-layer,
|
||||
.table-layer {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 99;
|
||||
width: 80px;
|
||||
padding: 5px 0;
|
||||
line-height: 25px;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
|
||||
font-size: 13px;
|
||||
opacity: 0;
|
||||
transition: all ease-in-out 0.2s;
|
||||
|
||||
&.fadein {
|
||||
visibility: visible;
|
||||
top: 34px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.font-layer {
|
||||
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
padding: 0 8px;
|
||||
|
||||
&:hover {
|
||||
background: #f7f8fb;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
font-size: 24px;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
font-size: 22px;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
font-size: 20px;
|
||||
}
|
||||
&:nth-child(4) {
|
||||
font-size: 18px;
|
||||
}
|
||||
&:nth-child(5) {
|
||||
font-size: 16px;
|
||||
}
|
||||
&:nth-child(6) {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-layer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
width: 222px;
|
||||
height: 222px;
|
||||
left: 240px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
padding: 2px;
|
||||
background: #fff;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 20px;
|
||||
|
||||
span {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
@ -258,36 +309,6 @@ class MEditor extends Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.addon-header {
|
||||
width: 108px;
|
||||
height: 190px;
|
||||
padding: 5px 0;
|
||||
line-height: 30px;
|
||||
user-select: none;
|
||||
background: #fff;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
padding: 0 12px;
|
||||
transition: background 0.1s ease-in-out;
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-plain-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.addon-link {
|
||||
width: 320px;
|
||||
|
@ -404,6 +425,25 @@ class MEditor extends Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
css`
|
||||
.editor,
|
||||
wc-markd {
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
visibility: hidden;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
visibility: visible;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
`
|
||||
]
|
||||
|
||||
|
@ -411,7 +451,6 @@ class MEditor extends Component {
|
|||
try {
|
||||
return this.$refs.editor.value
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
@ -431,7 +470,7 @@ class MEditor extends Component {
|
|||
|
||||
#toolbar = [...DEFAULT_TOOLS]
|
||||
#value = ''
|
||||
#cache = { bar: 0, y: 0 }
|
||||
|
||||
#gridx = 0
|
||||
#gridy = 0
|
||||
|
||||
|
@ -439,49 +478,10 @@ class MEditor extends Component {
|
|||
|
||||
previewEnabled = false
|
||||
|
||||
__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'
|
||||
}
|
||||
|
||||
#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')
|
||||
this.$refs.link.classList.remove('fadein')
|
||||
this.$refs.header.classList.remove('fadein')
|
||||
// this.$refs.color.classList.remove('fadein')
|
||||
// this.$refs.link.classList.remove('fadein')
|
||||
this.$refs.table.classList.remove('fadein')
|
||||
}
|
||||
|
||||
|
@ -548,8 +548,6 @@ class MEditor extends Component {
|
|||
var elem = ev.target
|
||||
var act
|
||||
|
||||
this.restoreSelection()
|
||||
|
||||
if (elem === ev.currentTarget) {
|
||||
return
|
||||
}
|
||||
|
@ -564,20 +562,19 @@ class MEditor extends Component {
|
|||
|
||||
act = elem.dataset.act
|
||||
|
||||
// this.#hideLayers()
|
||||
this.#hideLayers()
|
||||
|
||||
Addon[act].call(this, elem)
|
||||
}
|
||||
|
||||
#chnageFontSize(ev) {
|
||||
#setHeaderLevel(ev) {
|
||||
if (ev.target === ev.currentTarget) {
|
||||
return
|
||||
}
|
||||
this.$refs.font.classList.remove('fadein')
|
||||
this.$refs.header.classList.remove('fadein')
|
||||
this.$refs.editor.focus()
|
||||
this.restoreSelection()
|
||||
this.exec(ACTTION.font, ev.target.dataset.size)
|
||||
this.saveSelection()
|
||||
let level = +ev.target.dataset.value
|
||||
Addon.h.call(this, level)
|
||||
}
|
||||
|
||||
#chnageColor(ev) {
|
||||
|
@ -604,17 +601,16 @@ class MEditor extends Component {
|
|||
}
|
||||
|
||||
#insertTable(ev) {
|
||||
let th = `<th> </th>`.repeat(this.#gridx + 1)
|
||||
let td = `<td> </td>`.repeat(this.#gridx + 1)
|
||||
this.exec(
|
||||
'insertHtml',
|
||||
`<br>
|
||||
<table>
|
||||
<thead><tr>${th}</tr></thead>
|
||||
<tbody>${`<tr>${td}</tr>`.repeat(this.#gridy + 1)}</tbody>
|
||||
</table><br>`
|
||||
)
|
||||
this.$refs.table.classList.remove('fadein')
|
||||
if (this.#gridx < 0 || this.#gridy < 0) {
|
||||
return
|
||||
}
|
||||
let thead = `\n\n${'| 表头 '.repeat(this.#gridx)}|\n`
|
||||
let pipe = `${'| -- '.repeat(this.#gridx)}|\n`
|
||||
let tbody = ('| '.repeat(this.#gridx) + '|\n').repeat(this.#gridy)
|
||||
this.#gridx = -1
|
||||
this.#gridy = -1
|
||||
this.insert(thead + pipe + tbody, false)
|
||||
}
|
||||
|
||||
#tableSelect(ev) {
|
||||
|
@ -641,29 +637,12 @@ class MEditor extends Component {
|
|||
grids[i].classList.toggle('active', _x <= x && _y <= y)
|
||||
}
|
||||
} else {
|
||||
this.#gridx = -1
|
||||
this.#gridy = -1
|
||||
grids.forEach(it => it.classList.remove('active'))
|
||||
}
|
||||
}
|
||||
|
||||
// 保存选中
|
||||
saveSelection() {
|
||||
var gs = this.root.getSelection()
|
||||
if (gs.getRangeAt && gs.rangeCount) {
|
||||
this.#select = gs.getRangeAt(0)
|
||||
}
|
||||
}
|
||||
|
||||
// 清除选中并重置选中
|
||||
restoreSelection() {
|
||||
var gs = this.root.getSelection()
|
||||
if (this.#select) {
|
||||
try {
|
||||
gs.removeAllRanges()
|
||||
} catch (err) {}
|
||||
gs.addRange(this.#select)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 往文本框中插入内容
|
||||
* @param {String} val [要插入的文本]
|
||||
|
@ -907,6 +886,18 @@ class MEditor extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
#syncScrollToPreview(ev) {
|
||||
if (this.previewEnabled) {
|
||||
let { editor, view } = this.$refs
|
||||
let st = editor.scrollTop
|
||||
let eh = editor.clientHeight
|
||||
let sh = editor.scrollHeight
|
||||
let veh = view.clientHeight
|
||||
let vsh = view.scrollHeight
|
||||
this.$refs.view.scrollTop = (st / (sh - eh)) * (vsh - veh)
|
||||
}
|
||||
}
|
||||
|
||||
mounted() {
|
||||
Addon.preview.call(this, this)
|
||||
}
|
||||
|
@ -934,9 +925,35 @@ class MEditor extends Component {
|
|||
@keydown=${this.#handleKeydown}
|
||||
@paste.prevent=${this.#handlePaste}
|
||||
@input=${this.#updatePreview}
|
||||
@scroll=${this.#syncScrollToPreview}
|
||||
></textarea>
|
||||
<wc-markd ref="view" class="preview"></wc-markd>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="font-layer noselect"
|
||||
ref="header"
|
||||
@click=${this.#setHeaderLevel}
|
||||
>
|
||||
<span data-value="1">H1</span>
|
||||
<span data-value="2">H2</span>
|
||||
<span data-value="3">H3</span>
|
||||
<span data-value="4">H4</span>
|
||||
<span data-value="5">H5</span>
|
||||
<span data-value="6">H6</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="table-layer noselect"
|
||||
ref="table"
|
||||
@click=${this.#insertTable}
|
||||
@mousemove=${this.#tableSelect}
|
||||
@mouseleave=${this.#tableSelect}
|
||||
>
|
||||
${Array(81)
|
||||
.fill(0)
|
||||
.map((_, n) => html`<span data-idx=${n}></span>`)}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue