更新markdown编辑器;

master
yutent 2023-10-11 18:47:24 +08:00
parent 09356d0d92
commit 779aeb65a2
5 changed files with 152 additions and 142 deletions

View File

@ -605,7 +605,7 @@ class Editor extends Component {
} }
} }
#chnageFontSize(ev) { #changeFontSize(ev) {
if (ev.target === ev.currentTarget) { if (ev.target === ev.currentTarget) {
return return
} }
@ -677,6 +677,8 @@ class Editor extends Component {
grids[i].classList.toggle('active', _x <= x && _y <= y) grids[i].classList.toggle('active', _x <= x && _y <= y)
} }
} else { } else {
this.#gridx = -1
this.#gridy = -1
grids.forEach(it => it.classList.remove('active')) grids.forEach(it => it.classList.remove('active'))
} }
} }
@ -826,7 +828,7 @@ class Editor extends Component {
<div <div
class="font-layer noselect" class="font-layer noselect"
ref="font" ref="font"
@click=${this.#chnageFontSize} @click=${this.#changeFontSize}
> >
<span data-size="6">6号字体</span> <span data-size="6">6号字体</span>
<span data-size="5">5号字体</span> <span data-size="5">5号字体</span>

View File

@ -33,7 +33,7 @@ const Helper = {
isHr(str) { isHr(str) {
var s = str[0] var s = str[0]
if (HR_LIST.includes(s)) { 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 return false
}, },
@ -118,8 +118,8 @@ const Decoder = {
.replace(ESCAPE_RE, '$1') // 处理转义字符 .replace(ESCAPE_RE, '$1') // 处理转义字符
}, },
// 分割线 // 分割线
hr(name = '') { hr() {
return `\n\n<fieldset class="md-hr"><legend name="${name}"></legend></fieldset>\n\n` return `\n\n<hr>\n\n`
}, },
// 标题 // 标题
head(str) { head(str) {
@ -362,10 +362,8 @@ class Tool {
} }
// 无属性标签 // 无属性标签
if (Helper.isHr(it)) {
let hrName = Helper.isHr(it) html += Decoder.hr()
if (typeof hrName === 'string') {
html += Decoder.hr(hrName)
continue continue
} }

View File

@ -76,19 +76,11 @@ class Markd extends Component {
} }
} }
fieldset.md-hr { hr {
height: 1px;
margin: 30px 0; margin: 30px 0;
border: 0; border: 0;
border-top: 1px dashed var(--color-plain-3); 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 { ol {
margin-left: 1em; margin-left: 1em;

View File

@ -30,7 +30,7 @@ function showDialog(dialog, elem) {
export default { export default {
header(elem) { header(elem) {
showDialog(this.__HEADER_ADDON__, elem) this.$refs.header.classList.add('fadein')
}, },
h(level) { h(level) {
@ -97,7 +97,8 @@ export default {
}, },
table(elem) { table(elem) {
showDialog(this.__TABLE_ADDON__, elem) // showDialog(this.__TABLE_ADDON__, elem)
this.$refs.table.classList.add('fadein')
}, },
link(elem) { link(elem) {

View File

@ -234,57 +234,78 @@ class MEditor extends Component {
`, `,
css` css`
.addon-table { .font-layer,
display: flex; .table-layer {
flex-direction: column; visibility: hidden;
justify-content: space-between; position: absolute;
width: 222px; left: 0;
height: 222px; top: 0;
padding: 2px; z-index: 99;
width: 80px;
padding: 5px 0;
line-height: 25px;
background: #fff; 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;
li { &.fadein {
display: flex; visibility: visible;
justify-content: space-between; top: 34px;
height: 20px; opacity: 1;
}
}
span { .font-layer {
width: 20px; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
height: 20px;
background: var(--color-plain-1);
&.active { span {
background: rgba(77, 182, 172, 0.3); 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;
} }
} }
} }
.addon-header { .table-layer {
width: 108px; display: flex;
height: 190px; flex-wrap: wrap;
padding: 5px 0; justify-content: space-between;
line-height: 30px; left: 240px;
user-select: none; width: 200px;
height: 200px;
padding: 2px;
background: #fff; background: #fff;
li { span {
display: flex; width: 20px;
align-items: center; height: 20px;
width: 100%; background: var(--color-plain-1);
height: 30px;
padding: 0 12px;
transition: background 0.1s ease-in-out;
cursor: pointer;
.icon { &.active {
width: 14px; background: rgba(77, 182, 172, 0.3);
height: 14px;
margin-right: 8px;
}
&:hover {
background: var(--color-plain-1);
} }
} }
} }
@ -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 { try {
return this.$refs.editor.value return this.$refs.editor.value
} catch (err) { } catch (err) {
console.log(err)
return '' return ''
} }
} }
@ -431,7 +470,7 @@ class MEditor extends Component {
#toolbar = [...DEFAULT_TOOLS] #toolbar = [...DEFAULT_TOOLS]
#value = '' #value = ''
#cache = { bar: 0, y: 0 }
#gridx = 0 #gridx = 0
#gridy = 0 #gridy = 0
@ -439,49 +478,10 @@ class MEditor extends Component {
previewEnabled = false 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() { #hideLayers() {
this.$refs.font.classList.remove('fadein') this.$refs.header.classList.remove('fadein')
this.$refs.color.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') this.$refs.table.classList.remove('fadein')
} }
@ -548,8 +548,6 @@ class MEditor extends Component {
var elem = ev.target var elem = ev.target
var act var act
this.restoreSelection()
if (elem === ev.currentTarget) { if (elem === ev.currentTarget) {
return return
} }
@ -564,20 +562,19 @@ class MEditor extends Component {
act = elem.dataset.act act = elem.dataset.act
// this.#hideLayers() this.#hideLayers()
Addon[act].call(this, elem) Addon[act].call(this, elem)
} }
#chnageFontSize(ev) { #setHeaderLevel(ev) {
if (ev.target === ev.currentTarget) { if (ev.target === ev.currentTarget) {
return return
} }
this.$refs.font.classList.remove('fadein') this.$refs.header.classList.remove('fadein')
this.$refs.editor.focus() this.$refs.editor.focus()
this.restoreSelection() let level = +ev.target.dataset.value
this.exec(ACTTION.font, ev.target.dataset.size) Addon.h.call(this, level)
this.saveSelection()
} }
#chnageColor(ev) { #chnageColor(ev) {
@ -604,17 +601,16 @@ class MEditor extends Component {
} }
#insertTable(ev) { #insertTable(ev) {
let th = `<th>&nbsp;</th>`.repeat(this.#gridx + 1)
let td = `<td>&nbsp;</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') 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) { #tableSelect(ev) {
@ -641,29 +637,12 @@ class MEditor extends Component {
grids[i].classList.toggle('active', _x <= x && _y <= y) grids[i].classList.toggle('active', _x <= x && _y <= y)
} }
} else { } else {
this.#gridx = -1
this.#gridy = -1
grids.forEach(it => it.classList.remove('active')) 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 [要插入的文本] * @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() { mounted() {
Addon.preview.call(this, this) Addon.preview.call(this, this)
} }
@ -934,9 +925,35 @@ class MEditor extends Component {
@keydown=${this.#handleKeydown} @keydown=${this.#handleKeydown}
@paste.prevent=${this.#handlePaste} @paste.prevent=${this.#handlePaste}
@input=${this.#updatePreview} @input=${this.#updatePreview}
@scroll=${this.#syncScrollToPreview}
></textarea> ></textarea>
<wc-markd ref="view" class="preview"></wc-markd> <wc-markd ref="view" class="preview"></wc-markd>
</div> </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> </div>
` `
} }