appstore/dist/lib/ui/meditor/index.js

76 lines
12 KiB
JavaScript
Raw Permalink Normal View History

2023-12-19 15:30:29 +08:00
import{css as h,html as n,Component as x,range as v,nextTick as g,classMap as y,outsideClick as m,clearOutsideClick as $}from"wkit";import"../views/markd/index.js";import"../form/input.js";import"../base/button.js";import{DEFAULT_TOOLS as c,html2md as w,Addon as f}from"./helper.js";import k from"./svg.js";function p(d){return d/9>>0}function u(d){return d%9}function T(d,e){return d+e*9}class z extends x{static watches=["value"];static props={toolbar:{type:String,default:null,attribute:!1,observer(e){e===null?this.#t=[...c]:this.#t=e.split(",").filter(t=>t).map(t=>t.trim())}},placeholder:"",readonly:!1,disabled:!1,autofocus:!1};static styles=[h`:host{display:flex;min-width:200px;max-height:720px;min-height:64px;border-radius:3px;transition:box-shadow .15s linear;background:var(--wc-meditor-background, #fff)}.noselect{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}`,h`.meditor{position:relative;flex:1;display:flex;flex-direction:column;width:100%;border:1px solid var(--wc-editor-border-color, var(--color-grey-2));border-radius:inherit;font-size:14px;background:#fff}.toolbar{display:none;width:100%;height:34px;padding:5px;line-height:24px;border-bottom:1px solid var(--color-grey-1)}.toolbar span{position:relative;overflow:hidden;display:flex;justify-content:center;align-items:center;width:24px;height:24px;margin:0 3px;border-radius:3px;color:var(--color-grey-3)}.toolbar span .icon{overflow:hidden;width:70%;height:70%;fill:currentColor;color:#62778d}.toolbar span input{position:absolute;width:100%;height:100%;opacity:0}.toolbar span:hover,.toolbar span.active{background:var(--color-plain-1)}.toolbar span.active{color:var(--color-teal-1)}.toolbar.active{display:flex}`,h`.editor-outbox{overflow:hidden;flex:1;display:flex;width:100%;border-radius:3px}.editor-outbox .editor,.editor-outbox .preview{flex:1;flex-shrink:0}.editor-outbox .editor{height:100%;padding:5px 8px;line-height:1.5;border:0;font-size:var(--wc-meditor-font-size, 14px);font-family:Menlo,Monaco,Consolas,"Courier New",monospace;color:var(--color-dark-1);background:none;outline:none;resize:none;cursor:inherit}.editor-outbox .editor::placeholder{color:var(--color-grey-1)}.editor-outbox .preview{overflow:hidden;overflow-y:auto;display:none;padding:6px 12px;border-left:1px solid var(--color-plain-2)}.editor-outbox .preview.active{display:block}`,h`:host([readonly]){cursor:default;opacity:.8}:host([disabled]){cursor:not-allowed;opacity:.6}:host([readonly]) .toolbar span:hover,:host([disabled]) .toolbar span:hover{background:none}:host(:focus-within){box-shadow:0 0 0 2px var(--color-plain-a)}:host(.fullscreen){position:fixed;top:0;left:0;z-index:9;width:100vw;height:100vh;max-height:100vh;border-radius:0}`,h`.font-layer,.link-layer,.table-layer{visibility:hidden;position:absolute;left:0;top:0;z-index:99;height:0;background:#fff;opacity:0;box-shadow:0 0 8px rgba(0,0,0,.2);transition:all ease-in-out .2s}.font-layer.fadein,.link-layer.fadein,.table-layer.fadein{visibility:visible;top:34px;height:auto;opacity:1}.font-layer{width:120px;padding:5px 0;font-family:Menlo,Monaco,Consolas,"Courier New",monospace}.font-layer span{display:block;padding:0 8px}.font-layer span:hover{background:#f7f8fb}.font-layer span:first-child{font-size:24px}.font-layer span:nth-child(2){font-size:22px}.font-layer span:nth-child(3){font-size:20px}.font-layer span:nth-child(4){font-size:18px}.font-layer span:nth-child(5){font-size:16px}.font-layer span:nth-child(6){font-size:14px}.table-layer{overflow:hidden;display:flex;flex-wrap:wrap;justify-content:space-between;left:240px;width:200px;padding:2px;background:#fff}.table-layer span{width:20px;height:20px;background:var(--color-plain-1)}.table-layer span.active{background:rgba(77,182,172,.3)}.table-layer.fadein{height:200px}.link-layer{display:flex;flex-direction:column;left:330px;width:230px;padding:8px}.link-layer wc-button{width:40px;margin-top:8px}`,h`.editor::-webkit-scrollbar,wc-markd::-webkit-scrollbar{width:6px}.editor::-webkit-scrollbar-thumb,wc-markd::-webkit-scrollbar-thumb{visibility:hidden;bo
${"| \u8868\u5934 ".repeat(this.#e+1)}|
`,s=`${"| -- ".repeat(this.#e+1)}|
`,i=("| ".repeat(this.#e+1)+`|
`).repeat(this.#s+1);this.#e=-1,this.#s=-1,this.insert(t+s+i,!1)}#n(e){let t=Array.from(this.$refs.table.children);if(e.type==="mousemove"){if(e.target===e.currentTarget)return;let s=+e.target.dataset.idx,i=u(s),r=p(s),a=Math.max(T(this.#e,this.#s),s)+1;if(i===this.#e&&r===this.#s)return;this.#e=i,this.#s=r;for(let l=0;l<a;l++){let o=u(l),b=p(l);t[l].classList.toggle("active",o<=i&&b<=r)}}else this.#e=-1,this.#s=-1,t.forEach(s=>s.classList.remove("active"))}focus(){this.$refs.editor?.focus()}insert(e="",t=!1,s=0){let i=this.$refs.editor,r=i.selectionStart,a=i.selectionEnd,l=i.scrollTop;r||r===0?(i.value=i.value.slice(0,r)+e+i.value.slice(a),this.select((t?r:r+e.length)+s,r+e.length-s),i.scrollTop=l,i.focus()):(i.value+=e,i.focus()),this.#a(),this.$emit("input")}selection(e=!1){let t=this.$refs.editor,s=t.selectionStart,i=t.selectionEnd;if(i){if(e){s=t.value.slice(0,s).lastIndexOf(`
`);let r=t.value.slice(i).indexOf(`
`);r=r<0?t.value.slice(i).length:r,s+=1,i+=r,t.selectionStart=s,t.selectionEnd=i}}else e&&(i=t.value.indexOf(`
`),i=i<0?t.value.length:i,t.selectionEnd=i);return t.focus(),t.value.slice(s,i)}select(e=0,t=0){let s=this.$refs.editor;s.selectionStart=e,s.selectionEnd=t}cursor(e){this.select(e,e)}#r(e){let t=this.$refs.editor,s=(e<0?t.selectionStart:t.selectionEnd)||0;s+=e,e!==0&&(s<0&&(s=0),this.cursor(s))}#i(e){let t=this.$refs.editor,s=t.selectionStart,i=t.selectionEnd;return e<0&&(i=s-1),this.value[i]||""}#b(e){let t=this.$refs.editor,s=this.selection()||"",i=s.length>0,r="";if(!(this.readOnly||this.disabled))switch(e.keyCode){case 9:if(e.preventDefault(),e.shiftKey&&!i){let a=t.selectionStart,l=this.selection(!0);if(r=l.replace(/^\s{2}/,""),l===r){this.cursor(a);break}a-=2,this.insert(r,i),this.cursor(a)}else{if(r=s.split(`
`).map(function(a){return e.shiftKey?a.replace(/^\s{2}/,""):" "+a}).join(`
`),r===s)break;this.insert(r,i)}break;case 8:if(!i){let a=t.selectionStart,l=this.#i(-1),o=this.#i(1);(l===o&&(l==='"'||l==="'")||l===o&&l==="`"||l==="["&&o==="]"||l==="{"&&o==="}"||l==="("&&o===")")&&this.select(a-1,a+1)}break;case 57:e.shiftKey&&(e.preventDefault(),this.insert("("+s+")",i,i&&1||0),this.#r(i?0:-1));break;case 48:if(e.shiftKey){let a=this.#i(-1),l=this.#i(1);a==="("&&l===")"&&(e.preventDefault(),this.#r(1))}break;case 219:e.preventDefault(),this.insert(e.shiftKey?`{${s}}`:`[${s}]`,i,i^0),this.#r(i?0:-1);break;case 221:{let a=this.#i(-1),l=this.#i(1);e.shiftKey?a==="{"&&l==="}"&&(e.preventDefault(),this.#r(1)):a==="["&&l==="]"&&(e.preventDefault(),this.#r(1));break}case 192:case 222:if(e.shiftKey&&e.keyCode===192)break;{let a=e.keyCode===192?"`":e.shiftKey?'"':"'",l=this.#i(-1),o=this.#i(1);if(l===""&&o===a)break;e.preventDefault(),i?this.insert(a+s+a,!0,1):l===o&&l===a?this.#r(1):(this.insert(a+s+a),this.#r(-1));break}}}#x(e){if(this.previewEnabled){let{editor:t,view:s}=this.$refs,i=t.scrollTop,r=t.clientHeight,a=t.scrollHeight,l=s.clientHeight,o=s.scrollHeight;this.$refs.view.scrollTop=i/(a-r)*(o-l)}}#d(){if(this.clientHeight>64){let e=~~(this.clientHeight*.6)-(this.#t.length?34:0);e=e<64?64:e,this.$refs.editor.style.paddingBottom=e+"px"}else this.$refs.editor.style.cssText="padding-bottom:;"}mounted(){this.#d(),this._clickoutsideFn=m(this,e=>this.#l()),this.__observer=new ResizeObserver(this.#d.bind(this)),this.__observer.observe(this)}unmounted(){this.__observer?.disconnect(),$(this._clickoutsideFn)}render(){let e=this.#t.includes("table"),t=this.#t.includes("header"),s=this.#t.includes("link");return n`
<div class="meditor">
<header
class=${y({toolbar:!0,active:this.#t.length})}
@click=${this.#c}
>
${this.#t.map(i=>n`<span data-act=${i}>
<svg class="icon" viewBox="0 0 1024 1024">
<path d=${k[i]} />
</svg>
${i==="image"||i==="attach"?n`<input
type="file"
data-type=${i}
accept=${i==="image"?"image/*":"*/*"}
:disabled=${this.readOnly||this.disabled}
@change=${this.#o}
/>`:""}
</span>`)}
</header>
<div class="editor-outbox">
<textarea
ref="editor"
class="editor"
spellcheck="false"
placeholder=${this.placeholder}
autofocus=${this.autofocus}
:readOnly=${this.readOnly}
:disabled=${this.disabled}
@click=${this.#l}
@keydown=${this.#b}
@paste.prevent=${this.#h}
@input=${this.#a}
@scroll=${this.#x}
></textarea>
<wc-markd ref="view" class="preview"></wc-markd>
</div>
${t?n`<div
class="font-layer noselect"
ref="header"
@click=${this.#f}
>
<span data-value="1">一级标题</span>
<span data-value="2">二级标题</span>
<span data-value="3">三级标题</span>
<span data-value="4">四级标题</span>
<span data-value="5">五级标题</span>
<span data-value="6">六级标题</span>
</div>`:""}
${e?n`<div
class="table-layer noselect"
ref="table"
@click=${this.#u}
@mousemove=${this.#n}
@mouseleave=${this.#n}
>
${v(81).map((i,r)=>n`<span data-idx=${r}></span>`)}
</div>`:""}
${s?n`<div class="link-layer noselect" ref="link">
<wc-input ref="linkinput" placeholder="请输入链接地址"></wc-input>
<wc-button size="m" @click=${this.#p}>插入</wc-button>
</div>`:""}
</div>
`}}z.reg("meditor");