This repository has been archived on 2023-08-30. You can view files and clone it, but cannot push or open issues/pull-requests.
bytedo
/
wcui
Archived
1
0
Fork 0

new version

old
宇天 2020-12-14 14:44:05 +08:00
parent 0234144dfb
commit 0cb17b6292
65 changed files with 4039 additions and 12740 deletions

View File

@ -15,20 +15,6 @@ const VERSION = require('./package.json').version
const BUILD_DATE = new Date().format() const BUILD_DATE = new Date().format()
const BASE_SCSS = ` const BASE_SCSS = `
$ct: #4db6ac #26a69a #009688;
$cg: #81c784 #66bb6a #4caf50;
$cpp: #9575cd #9575cd #673ab7;
$cb: #64b5f6 #42a5f5 #2196f3;
$cr: #ff5061 #eb3b48 #ce3742;
$co: #ffb618 #f39c12 #e67e22;
$cp: #f2f5fc #e8ebf4 #dae1e9;
$cgr: #bdbdbd #9e9e9e #757575;
$cd: #62778d #526273 #425064;
@mixin ts($c: all, $t: .1s, $m: ease-in-out){
transition:$c $t $m;
}
@mixin focus1(){ @mixin focus1(){
box-shadow: 0 0 2px #88f7df; box-shadow: 0 0 2px #88f7df;
} }
@ -43,6 +29,36 @@ $cd: #62778d #526273 #425064;
} }
::before, ::before,
::after{box-sizing:border-box;} ::after{box-sizing:border-box;}
:host {
--color-teal-1: #4db6ac;
--color-teal-2: #26a69a;
--color-teal-3: #009688;
--color-green-1: #81c784;
--color-green-2: #66bb6a;
--color-green-3: #4caf50;
--color-purple-1: #9575cd;
--color-purple-2: #9575cd;
--color-purple-3: #673ab7;
--color-blue-1: #64b5f6;
--color-blue-2: #42a5f5;
--color-blue-3: #2196f3;
--color-red-1: #ff5061;
--color-red-2: #eb3b48;
--color-red-3: #ce3742;
--color-orange-1: #ffb618;
--color-orange-2: #f39c12;
--color-orange-3: #e67e22;
--color-plain-1: #f2f5fc;
--color-plain-2: #e8ebf4;
--color-plain-3: #dae1e9;
--color-grey-1: #bdbdbd;
--color-grey-2: #9e9e9e;
--color-grey-3: #757575;
--color-dark-1: #62778d;
--color-dark-2: #526273;
--color-dark-3: #425064;
}
` `
function parseName(str) { function parseName(str) {
@ -79,47 +95,42 @@ function mkWCFile({ style, html, js }) {
style = compileScss(style) style = compileScss(style)
let name = '' let name = ''
let props = ''
js = js.replace(/props = (\{\}|\{[\w\W]*?\n\s{2}?\})/, function(s, m) { js = js.replace(/props = (\{\}|\{[\w\W]*?\n\s{2}?\})/, function(str) {
props = m var attr = str
var attr = new Function( .split(/\n+/)
`try { .slice(1, -1)
var props = ${m}, attr = [] .map(it => {
for(var i in props){attr.push(i)} var tmp = it.split(':')
return attr return tmp[0].trim()
} catch(err) {console.error(err);return []} })
` return `
)()
return `static get observedAttributes() { static get observedAttributes() {
return ${JSON.stringify(attr)} return ${JSON.stringify(attr)}
} }
`
${str}
`
}) })
js = fixImport(js) js = fixImport(js)
.replace(/class ([a-zA-Z0-9]+)/, function(s, m) { .replace(/export default class ([a-zA-Z0-9]+)/, function(s, m) {
name = m name = m
return `${s} extends HTMLElement ` return `${s} extends HTMLElement `
}) })
.replace(/__init__\(\)\s+\{/, 'constructor() {\n super();') .replace(/__init__\(\)\s+\{/, 'constructor() {\n super();')
.replace( .replace(
'/* render */', '/* render */',
` `
Object.defineProperty(this, 'root', { Object.defineProperty(this, 'root', {
value: this.attachShadow({ mode: 'open' }), value: this.attachShadow({ mode: 'open' }),
writable: true, writable: true,
enumerable: false, enumerable: false,
configurable: true configurable: true
}) })
Object.defineProperty(this, 'props', {
value: ${props},
writable: true,
enumerable: false,
configurable: true
})
this.root.innerHTML = \`<style>${style}</style>${html}\` this.root.innerHTML = \`<style>${style}</style>${html}\`
` `
) )
.replace('mounted()', 'connectedCallback()') .replace('mounted()', 'connectedCallback()')
@ -138,10 +149,6 @@ function mkWCFile({ style, html, js }) {
* *
*/ */
'use strict'
const log = console.log
${js} ${js}
if(!customElements.get('wc-${parseName(name)}')){ if(!customElements.get('wc-${parseName(name)}')){

View File

@ -6,7 +6,7 @@ const fs = require('iofs')
const path = require('path') const path = require('path')
const scss = require('node-sass') const scss = require('node-sass')
const chalk = require('chalk') const chalk = require('chalk')
const uglify = require('uglify-es') const { minify } = require('terser')
const sourceDir = path.resolve(__dirname, 'src') const sourceDir = path.resolve(__dirname, 'src')
const buildDir = path.resolve(__dirname, 'dist') const buildDir = path.resolve(__dirname, 'dist')
@ -15,20 +15,6 @@ const VERSION = require('./package.json').version
const BUILD_DATE = new Date().format() const BUILD_DATE = new Date().format()
const BASE_SCSS = ` const BASE_SCSS = `
$ct: #4db6ac #26a69a #009688;
$cg: #81c784 #66bb6a #4caf50;
$cpp: #9575cd #9575cd #673ab7;
$cb: #64b5f6 #42a5f5 #2196f3;
$cr: #ff5061 #eb3b48 #ce3742;
$co: #ffb618 #f39c12 #e67e22;
$cp: #f2f5fc #e8ebf4 #dae1e9;
$cgr: #bdbdbd #9e9e9e #757575;
$cd: #62778d #526273 #425064;
@mixin ts($c: all, $t: .1s, $m: ease-in-out){
transition:$c $t $m;
}
@mixin focus1(){ @mixin focus1(){
box-shadow: 0 0 2px #88f7df; box-shadow: 0 0 2px #88f7df;
} }
@ -43,6 +29,36 @@ $cd: #62778d #526273 #425064;
} }
::before, ::before,
::after{box-sizing:border-box;} ::after{box-sizing:border-box;}
:host {
--color-teal-1: #4db6ac;
--color-teal-2: #26a69a;
--color-teal-3: #009688;
--color-green-1: #81c784;
--color-green-2: #66bb6a;
--color-green-3: #4caf50;
--color-purple-1: #9575cd;
--color-purple-2: #9575cd;
--color-purple-3: #673ab7;
--color-blue-1: #64b5f6;
--color-blue-2: #42a5f5;
--color-blue-3: #2196f3;
--color-red-1: #ff5061;
--color-red-2: #eb3b48;
--color-red-3: #ce3742;
--color-orange-1: #ffb618;
--color-orange-2: #f39c12;
--color-orange-3: #e67e22;
--color-plain-1: #f2f5fc;
--color-plain-2: #e8ebf4;
--color-plain-3: #dae1e9;
--color-grey-1: #bdbdbd;
--color-grey-2: #9e9e9e;
--color-grey-3: #757575;
--color-dark-1: #62778d;
--color-dark-2: #526273;
--color-dark-3: #425064;
}
` `
function parseName(str) { function parseName(str) {
@ -62,14 +78,14 @@ const compileJs = (entry, output) => {
let t1 = Date.now() let t1 = Date.now()
let buf = fs.cat(entry).toString() let buf = fs.cat(entry).toString()
buf = fixImport(buf) buf = fixImport(buf)
let { code } = uglify.minify(buf) minify(buf, { sourceMap: false }).then(res => {
log(
log( '编译JS: %s, 耗时 %s ms',
'编译JS: %s, 耗时 %s ms', chalk.green(entry),
chalk.green(entry), chalk.yellow(Date.now() - t1)
chalk.yellow(Date.now() - t1) )
) fs.echo(res.code, output)
fs.echo(code, output) })
} }
// 编译样式 // 编译样式
@ -93,25 +109,31 @@ function mkWCFile({ style, html, js }) {
html = html.replace(/\s+/g, ' ') html = html.replace(/\s+/g, ' ')
let name = '' let name = ''
let props = ''
js = js.replace(/props = (\{\}|\{[\w\W]*?\n\s{2}?\})/, function(s, m) { js = js.replace(/props = (\{\}|\{[\w\W]*?\n\s{2}?\})/, function(str) {
props = m var attr = str
var attr = new Function( .split(/\n+/)
`var props = ${m}, attr = []; for(var i in props){attr.push(i)}; return attr` .slice(1, -1)
)() .map(it => {
return `static get observedAttributes() { var tmp = it.split(':')
return ${JSON.stringify(attr)} return tmp[0].trim()
} })
` return `
static get observedAttributes() {
return ${JSON.stringify(attr)}
}
${str}
`
}) })
js = fixImport(js) js = fixImport(js)
.replace(/class ([a-zA-Z0-9]+)/, function(s, m) { .replace(/export default class ([a-zA-Z0-9]+)/, function(s, m) {
name = m name = m
return `${s} extends HTMLElement ` return `${s} extends HTMLElement `
}) })
.replace(/__init__\(\)\s+\{/, 'constructor() {\n super();') .replace(/__init__\(\)\s+\{/, 'constructor() {\n super();')
.replace( .replace(
'/* render */', '/* render */',
` `
@ -121,12 +143,7 @@ function mkWCFile({ style, html, js }) {
enumerable: false, enumerable: false,
configurable: true configurable: true
}) })
Object.defineProperty(this, 'props', {
value: ${props},
writable: true,
enumerable: false,
configurable: true
})
this.root.innerHTML = \`<style>${style}</style>${html}\` this.root.innerHTML = \`<style>${style}</style>${html}\`
` `
@ -139,17 +156,14 @@ function mkWCFile({ style, html, js }) {
) )
.replace('adopted()', 'adoptedCallback()') .replace('adopted()', 'adoptedCallback()')
let res = uglify.minify(js) return minify(js, { sourceMap: false }).then(res => {
return `/**
return `/** *
* * @authors yutent (yutent.io@gmail.com)
* @authors yutent (yutent.io@gmail.com) * @date ${BUILD_DATE}
* @date ${BUILD_DATE} * @version v${VERSION}
* @version v${VERSION} *
* */
*/
'use strict'
${res.code} ${res.code}
@ -157,6 +171,7 @@ if(!customElements.get('wc-${parseName(name)}')){
customElements.define('wc-${parseName(name)}', ${name}) customElements.define('wc-${parseName(name)}', ${name})
} }
` `
})
} }
const compileWC = (entry, output) => { const compileWC = (entry, output) => {
@ -170,8 +185,9 @@ const compileWC = (entry, output) => {
html = html ? html[1] : '' html = html ? html[1] : ''
js = js ? js[1] : '' js = js ? js[1] : ''
let result = mkWCFile({ style, html, js }) mkWCFile({ style, html, js }).then(txt => {
fs.echo(result, output) fs.echo(txt, output)
})
} }
/*=======================================================*/ /*=======================================================*/

View File

@ -1,6 +1,6 @@
{ {
"name": "@bd/wcui", "name": "@bytedo/wcui",
"version": "2.0.0", "version": "1.0.0",
"description": "基于wc开发的一套UI库, 面向未来, 面向electron", "description": "基于wc开发的一套UI库, 面向未来, 面向electron",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@ -9,7 +9,7 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/yutent/doui.git" "url": "git+https://github.com/bytedo/wcui.git"
}, },
"keywords": ["web-components", "wc", "components", "yutent"], "keywords": ["web-components", "wc", "components", "yutent"],
"author": "yutent", "author": "yutent",
@ -21,6 +21,6 @@
"es.shim": "^1.1.2", "es.shim": "^1.1.2",
"iofs": "^1.3.2", "iofs": "^1.3.2",
"node-sass": "^4.12.0", "node-sass": "^4.12.0",
"uglify-es": "^3.3.9" "terser": "^5.0.0"
} }
} }

View File

@ -20,7 +20,7 @@
font-size: 12px; font-size: 12px;
border: 1px solid #fff; border: 1px solid #fff;
border-radius: 16px; border-radius: 16px;
background: nth($cr, 1); background: var(--color-red-1);
color: #fff; color: #fff;
text-align: center; text-align: center;
transform: translateX(100%) translateY(-50%); transform: translateX(100%) translateY(-50%);
@ -34,22 +34,22 @@
} }
} }
:host([color='dark']) .dot { :host([color='dark']) .dot {
background: nth($cd, 1); background: var(--color-dark-1);
} }
:host([color='green']) .dot { :host([color='green']) .dot {
background: nth($cg, 1); background: var(--color-green-1);
} }
:host([color='blue']) .dot { :host([color='blue']) .dot {
background: nth($cb, 1); background: var(--color-blue-1);
} }
:host([color='orange']) .dot { :host([color='orange']) .dot {
background: nth($co, 1); background: var(--color-orange-1);
} }
:host([color='purple']) .dot { :host([color='purple']) .dot {
background: nth($cpp, 1); background: var(--color-purple-1);
} }
:host([color='teal']) .dot { :host([color='teal']) .dot {
background: nth($ct, 1); background: var(--color-teal-1);
} }
</style> </style>

227
src/chart/line.wc Normal file
View File

@ -0,0 +1,227 @@
<template>
<div class="container">
<canvas></canvas>
<section>
<wc-button color="blue" data-key="1" size="mini">1月</wc-button>
<wc-button data-key="3" size="mini">3月</wc-button>
<wc-button data-key="6" size="mini">半年</wc-button>
<wc-button data-key="12" size="mini">1年</wc-button>
<wc-button data-key="36" size="mini">3年</wc-button>
<wc-button data-key="999" size="mini">所有</wc-button>
</section>
</div>
</template>
<style lang="scss">
:host {
display: flex;
width: 680px;
}
.container {
position: relative;
padding: 24px 0 0;
}
canvas {
width: 680px;
height: 230px;
}
section {
position: absolute;
right: 0;
top: 0;
}
</style>
<script>
import $ from '../utils'
import '../form/button'
const DARK = '#62778d'
const BLUE = '#64b5f6'
const PLAIN = '#f2f5fc'
export default class Line {
props = {
list: []
}
state = {
key: 1,
list: []
}
__init__() {
/* render */
var elem = this.root.children[1]
this.__SCENE__ = elem.firstElementChild
this.__FILTER__ = elem.lastElementChild
this.__CTX__ = this.__SCENE__.getContext('2d')
this.__SCENE__.width = 680
this.__SCENE__.height = 230
}
_getTime(n) {
var now = new Date()
var time = { getTime: _ => 0 }
var Y = now.getFullYear()
var m = now.getMonth()
var d = now.getDate()
switch (n) {
case 1:
time = new Date(Y, m - 1, d, 0, 0, 0)
break
case 3:
time = new Date(Y, m - 3, d, 0, 0, 0)
break
case 6:
time = new Date(Y, m - 6, d, 0, 0, 0)
break
case 12:
time = new Date(Y - 1, m, d, 0, 0, 0)
break
case 36:
time = new Date(Y - 3, m, d, 0, 0, 0)
break
}
return time.getTime()
}
_filter(n) {
if (n < 999) {
var time = this._getTime(n)
this.state.list = this.props.list.filter(it => it.x >= time)
} else {
this.state.list = this.props.list.concat()
}
}
draw() {
var { list, key } = this.state
var ctx = this.__CTX__
var x = 36
var max = 0
var min = Number.MAX_SAFE_INTEGER
var step = 0 // 纵坐标间隔
var dis = +(640 / list.length).toFixed(2) || 1 // 横坐标间隔
var point
var p1, p2, p3, p4
var format = key > 12 ? 'Y/m' : 'm/d'
for (let it of list) {
if (max < it.y) {
max = it.y
}
if (min > it.y) {
min = it.y
}
}
min = ~~(min / 100)
max = Math.ceil(max / 100)
step = ~~((max - min) / 3)
p1 = Math.floor(list.length / 4)
p2 = Math.floor(list.length / 2)
p3 = Math.floor((list.length * 3) / 4)
p4 = list.length - 1
ctx.clearRect(0, 0, 680, 230)
// 纵坐标数值
ctx.font = '12px Arial'
ctx.textAlign = 'right'
ctx.fillStyle = DARK
ctx.fillText(min / 100, 32, 205)
ctx.fillText((min + step) / 100, 32, 155)
ctx.fillText((min + step + step) / 100, 32, 105)
ctx.fillText((min + step + step + step) / 100, 32, 55)
ctx.font = '10px Arial'
ctx.textAlign = 'left'
ctx.fillText(new Date(list[0].x).format(format), x - 12, 225)
ctx.fillText(new Date(list[p1].x).format(format), x + dis * p1 - 12, 225)
ctx.fillText(new Date(list[p2].x).format(format), x + dis * p2 - 12, 225)
ctx.fillText(new Date(list[p3].x).format(format), x + dis * p3 - 12, 225)
ctx.fillText(
new Date(list[p4].x).format(format),
x + dis * p4 - 12 - (key > 12 ? 24 : 4),
225
)
// x轴参考线
ctx.fillStyle = PLAIN
ctx.fillRect(x, 50, 648, 1)
ctx.fillRect(x, 100, 648, 1)
ctx.fillRect(x, 150, 648, 1)
ctx.fillRect(x, 200, 648, 1)
// y轴参考 线
ctx.fillRect(x, 0, 1, 210)
ctx.fillRect(x + dis * p1, 0, 1, 210)
ctx.fillRect(x + dis * p2, 0, 1, 210)
ctx.fillRect(x + dis * p3, 0, 1, 210)
ctx.fillRect(x + dis * p4, 0, 1, 210)
point = list.shift()
// 曲线
ctx.beginPath()
ctx.strokeStyle = BLUE
ctx.lineWidth = 1
ctx.moveTo(x, 200 - (((point.y / 100 - min) / step) * 50).toFixed(0))
while (list.length) {
let y
point = list.shift()
y = 200 - (((point.y / 100 - min) / step) * 50).toFixed(0)
x += dis
ctx.lineTo(x, y)
}
ctx.stroke()
}
mounted() {
$.bind(this.__FILTER__, 'click', ev => {
var el = ev.target
if (this.props.list.length < 1) {
return
}
if (el.tagName === 'WC-BUTTON') {
var k = +el.dataset.key
$.each(this.__FILTER__.children, function(it) {
it.removeAttribute('color')
})
el.setAttribute('color', 'blue')
this.state.key = k
this._filter(k)
this.draw()
}
})
}
watch() {
switch (name) {
case 'list':
try {
var list = JSON.parse(val)
list.forEach(it => (it.x = it.x * 1000))
this.props.list = list
this._filter(this.state.key)
this.removeAttribute('list')
this.draw()
} catch (e) {}
break
}
}
}
</script>

112
src/chart/rank.wc Normal file
View File

@ -0,0 +1,112 @@
<template>
<canvas></canvas>
</template>
<style lang="scss">
:host {
display: flex;
}
canvas {
width: 680px;
height: 100px;
}
</style>
<script>
const RED = '#ff5061'
const GREEN = '#4caf50'
const BLUE = '#64b5f6'
const GREY = '#bdbdbd'
const PLAIN = '#f2f5fc'
const DARK = '#62778d'
export default class Rank {
props = {
stat: {}
}
__init__() {
/* render */
this.__SCENE__ = this.root.children[1]
this.__CTX__ = this.__SCENE__.getContext('2d')
this.__SCENE__.width = 680
this.__SCENE__.height = 100
}
draw() {
var { rank, e1, e3, e6, e12, cm, cp } = this.props.stat
var ctx = this.__CTX__
var x = 32
while (rank.length < 60) {
rank.unshift(0)
}
ctx.clearRect(0, 0, 680, 101)
ctx.font = '10px Arial'
ctx.textAlign = 'right'
ctx.fillStyle = RED
ctx.fillText('10%', 28, 10)
ctx.fillText('5%', 28, 30)
ctx.fillStyle = GREEN
ctx.fillText('-5%', 28, 80)
ctx.fillText('-10%', 28, 100)
ctx.font = '10px menlo,Hiragino Sans GB'
ctx.textAlign = 'left'
ctx.fillStyle = DARK
ctx.fillText('60天红绿榜', 160, 10)
ctx.font = '12px menlo,Hiragino Sans GB'
ctx.fillText(`最近1个月收益: ${e1}%`, 360, 25)
ctx.fillText(`最近3个月收益: ${e3}%`, 360, 45)
ctx.fillText(`最近半年收益: ${e6}%`, 528, 25)
ctx.fillText(`最近一年收益: ${e12}%`, 528, 45)
ctx.fillStyle = cp > 0 ? RED : cp === 0 ? GREY : GREEN
ctx.fillRect(360, 65, 140, 20)
ctx.fillRect(526, 65, 140, 20)
ctx.fillStyle = '#fff'
ctx.font = 'bold 14px menlo,Hiragino Sans GB'
ctx.fillText(`实时净值: ¥${cm}`, 364, 80)
ctx.fillText(`实时涨跌: ${cp}%`, 532, 80)
ctx.fillStyle = PLAIN
ctx.fillRect(28, 25, 320, 1)
ctx.fillRect(28, 75, 320, 1)
ctx.fillStyle = GREY
ctx.fillRect(28, 0, 1, 140)
ctx.fillRect(0, 50, 348, 1)
while (rank.length) {
var n = rank.shift()
var y = Math.ceil(50 - (n / 10) * 50)
ctx.fillStyle = n > 0 ? RED : GREEN
if (y > 50) {
ctx.fillRect(x, 50, 3, y - 50)
} else {
ctx.fillRect(x, y, 3, 50 - y)
}
x += 5
}
}
watch() {
switch (name) {
case 'stat':
try {
var stat = JSON.parse(val)
this.props.stat = stat
this.removeAttribute('stat')
this.draw()
} catch (e) {}
break
}
}
}
</script>

View File

@ -24,7 +24,7 @@
width: 100%; width: 100%;
max-height: 610px; max-height: 610px;
margin: 10px 0; margin: 10px 0;
border: 1px solid nth($cp, 2); border: 1px solid var(--color-plain-2);
border-radius: 2px; border-radius: 2px;
.title { .title {
@ -36,7 +36,7 @@
padding: 0 12px; padding: 0 12px;
line-height: 1; line-height: 1;
font-size: 12px; font-size: 12px;
background: nth($cp, 2); background: var(--color-plain-2);
user-select: none; user-select: none;
i { i {
@ -45,23 +45,23 @@
height: 10px; height: 10px;
margin-right: 6px; margin-right: 6px;
border-radius: 50%; border-radius: 50%;
background: nth($cr, 1); background: var(--color-red-1);
} }
i:nth-child(2) { i:nth-child(2) {
background: nth($co, 1); background: var(--color-orange-1);
} }
i:nth-child(3) { i:nth-child(3) {
background: nth($cg, 1); background: var(--color-green-1);
} }
.act { .act {
--size: 16px; --size: 16px;
margin: 0 2px; margin: 0 2px;
color: nth($cgr, 2); color: var(--color-grey-2);
cursor: pointer; cursor: pointer;
&:hover { &:hover {
color: nth($cgr, 3); color: var(--color-grey-3);
} }
&.run { &.run {
display: none; display: none;
@ -75,8 +75,8 @@
line-height: 18px; line-height: 18px;
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
font-size: 13px; font-size: 13px;
background: linear-gradient(to right, nth($cp, 1) 40px, #fff 40px); background: linear-gradient(to right, var(--color-plain-1) 40px, #fff 40px);
color: nth($cd, 1); color: var(--color-dark-1);
cursor: text; cursor: text;
counter-reset: code; counter-reset: code;
@ -95,7 +95,7 @@
height: 100%; height: 100%;
padding-right: 5px; padding-right: 5px;
text-align: right; text-align: right;
color: nth($cgr, 1); color: var(--color-grey-1);
content: counter(code); content: counter(code);
counter-increment: code; counter-increment: code;
} }
@ -113,18 +113,22 @@
:host([dark]) { :host([dark]) {
.code-box { .code-box {
border-color: nth($cd, 2); border-color: var(--color-dark-2);
.title { .title {
background: nth($cd, 2); background: var(--color-dark-2);
} }
.code { .code {
background: linear-gradient(to right, #596b7f 40px, nth($cd, 1) 40px); background: linear-gradient(
color: nth($cp, 3); to right,
#596b7f 40px,
var(--color-dark-1) 40px
);
color: var(--color-plain-3);
p::before { p::before {
color: nth($cgr, 3); color: var(--color-grey-3);
} }
} }
} }
@ -162,6 +166,10 @@ export default class Code {
set value(txt) { set value(txt) {
this.props.content = txt this.props.content = txt
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
txt = txt txt = txt
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
.replace(/>/g, '&gt;') .replace(/>/g, '&gt;')

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

117
src/color/helper.js Normal file
View File

@ -0,0 +1,117 @@
/**
*
* @author yutent<yutent.io@gmail.com>
* @date 2020/12/03 18:24:47
*/
// H: 色相, S: 饱和度, B/V: 亮度
export function hsb2rgb(hsb) {
var h = hsb.h
var s = Math.round((hsb.s * 255) / 100)
var v = Math.round((hsb.b * 255) / 100)
var r = 0
var g = 0
var b = 0
if (s === 0) {
r = g = b = v
} else {
var t1 = v
var t2 = ((255 - s) * v) / 255
var t3 = ((t1 - t2) * (h % 60)) / 60
//
if (h === 360) {
h = 0
}
if (h < 60) {
r = t1
g = t2 + t3
b = t2
} else if (h < 120) {
r = t1 - t3
g = t1
b = t2
} else if (h < 180) {
r = t2
g = t1
b = t2 + t3
} else if (h < 240) {
r = t2
g = t1 - t3
b = t1
} else if (h < 300) {
r = t2 + t3
g = t2
b = t1
} else if (h < 360) {
r = t1
g = t2
b = t1 - t3
}
}
r = Math.round(r)
g = Math.round(g)
b = Math.round(b)
return { r, g, b }
}
export function rgb2hex({ r, g, b }) {
return [r, g, b].map(it => it.toString(16).padStart(2, '0')).join('')
}
export function hex2rgb(hex) {
var r, g, b
hex = hex.replace(/^#/, '').split('')
if (hex.length === 3) {
r = parseInt(hex[0] + hex[0], 16)
g = parseInt(hex[1] + hex[1], 16)
b = parseInt(hex[2] + hex[2], 16)
} else {
r = parseInt(hex[0] + hex[1], 16)
g = parseInt(hex[2] + hex[3], 16)
b = parseInt(hex[4] + hex[5], 16)
}
return { r, g, b }
}
export function rgb2hsb({ r, g, b }) {
var hsb = { h: 0, s: 0, b: 0 }
var max = Math.max(r, g, b)
var min = Math.min(r, g, b)
var delta = max - min
hsb.b = max
hsb.s = max === 0 ? 0 : (delta * 255) / max
if (hsb.s === 0) {
hsb.h = -1
} else {
if (r === max) {
hsb.h = (g - b) / delta
} else if (g === max) {
hsb.h = 2 + (b - r) / delta
} else {
hsb.h = 4 + (r - g) / delta
}
}
hsb.h *= 60
if (hsb.h < 0) {
hsb.h += 360
}
hsb.s *= 100 / 255
hsb.b *= 100 / 255
return hsb
}
export function hex2hsb(hex) {
return rgb2hsb(hex2rgb(hex))
}

475
src/color/index.wc Normal file
View File

@ -0,0 +1,475 @@
<template>
<div class="color-picker">
<section class="preview alpha-bg">
<span></span>
</section>
<div class="color-panel">
<section class="dashboard">
<div class="scene"><span class="thumb"></span></div>
<div class="pool">
<span class="thumb hue-thumb"></span>
<input class="hue" max="360" min="0" step="1" type="range" />
</div>
</section>
<section class="alpha-box alpha-bg">
<div class="bar"></div>
<span class="thumb alpha-thumb"></span>
<input class="alpha" max="100" min="0" step="1" type="range" />
</section>
<section class="input-box">
<input class="input" maxlength="28" />
<a class="clear">清除</a>
<a class="submit">确定</a>
</section>
</div>
</div>
</template>
<style lang="scss">
.alpha-bg {
background: linear-gradient(
45deg,
var(--color-grey-1) 25%,
transparent 25%,
transparent 75%,
var(--color-grey-1) 75%,
var(--color-grey-1)
),
linear-gradient(
45deg,
var(--color-grey-1) 25%,
transparent 25%,
transparent 75%,
var(--color-grey-1) 75%,
var(--color-grey-1)
);
background-size: 12px 12px;
background-position: 0 0, 6px 6px;
}
.color-picker {
position: relative;
width: 32px;
height: 32px;
.preview {
display: flex;
width: 100%;
height: 100%;
border: 1px solid var(--color-grey-1);
cursor: pointer;
span {
width: 100%;
height: 100%;
border: 3px solid #fff;
}
}
.color-panel {
position: absolute;
left: 0;
top: 32px;
width: 310px;
padding: 5px;
background: #fff;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
.dashboard {
display: flex;
justify-content: space-between;
.scene {
overflow: hidden;
position: relative;
width: 280px;
height: 180px;
background: #f00;
&::before,
&::after {
position: absolute;
left: 0;
top: 0;
z-index: 0;
width: 100%;
height: 100%;
content: '';
}
&::before {
background: linear-gradient(to right, #fff, transparent);
}
&::after {
background: linear-gradient(to bottom, transparent, #000);
}
.thumb {
position: absolute;
z-index: 99;
width: 0;
height: 0;
&::after {
display: block;
width: 10px;
height: 10px;
border-radius: 50%;
background: rgba(32, 32, 32, 0.3);
box-shadow: 0 0 0 1px #fff;
transform: translate(-5px, -5px);
content: '';
}
}
}
.pool {
overflow: hidden;
position: relative;
width: 12px;
height: 180px;
background: linear-gradient(
to bottom,
#f00 0,
#ff0 17%,
#0f0 33%,
#0ff 50%,
#00f 67%,
#f0f 83%,
#f00
);
.thumb {
position: absolute;
left: 0;
top: 0;
z-index: 0;
width: 12px;
height: 0;
&::after {
display: block;
width: 12px;
height: 12px;
border-radius: 50%;
background: #fff;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
transform: translateY(-6px);
content: '';
}
}
.hue {
position: relative;
z-index: 1;
display: block;
width: 12px;
height: 180px;
appearance: slider-vertical;
opacity: 0;
}
}
}
.alpha-box {
overflow: hidden;
position: relative;
width: 100%;
height: 12px;
margin: 12px 0;
.bar {
position: absolute;
left: 0;
top: 0;
z-index: 0;
width: 100%;
height: 12px;
background: linear-gradient(to right, transparent, #f00);
}
.thumb {
position: absolute;
left: 100%;
top: 0;
z-index: 0;
width: 0;
height: 12px;
&::after {
display: block;
width: 12px;
height: 12px;
border-radius: 50%;
background: #fff;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
transform: translateX(-6px);
content: '';
}
}
.alpha {
position: relative;
z-index: 9;
display: block;
width: 100%;
height: 12px;
opacity: 0;
}
}
.input-box {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
.input {
width: 200px;
height: 24px;
padding: 0 6px;
line-height: 22px;
border: 1px solid var(--color-plain-3);
border-radius: 2px;
outline: none;
color: var(--color-dark-1);
}
.clear,
.submit {
font-size: 12px;
cursor: pointer;
user-select: none;
}
.clear {
color: var(--color-teal-1);
}
.submit {
padding: 2px 6px;
border-radius: 2px;
color: var(--color-plain-1);
background: var(--color-teal-1);
}
}
}
}
</style>
<script>
import $ from '../utils'
import { hsb2rgb, rgb2hex, hex2rgb, rgb2hsb } from './helper'
export default class Color {
props = {
value: ''
}
state = {
hsb: { h: 0, s: 100, b: 100 },
rgba: { r: 255, g: 0, b: 0, a: 100 }
}
__init__() {
/* render */
var elem = this.root.children[1]
this.__PREVIEW__ = elem.children[0].children[0]
this.__SCENE__ = elem.querySelector('.scene')
this.__SCENE_THUMB__ = this.__SCENE__.querySelector('.thumb')
this.__HUE__ = elem.querySelector('.hue')
this.__HUE_THUMB__ = elem.querySelector('.hue-thumb')
this.__ALPHA__ = elem.querySelector('.alpha')
this.__ALPHA_BAR__ = elem.querySelector('.alpha-box .bar')
this.__ALPHA_THUMB__ = elem.querySelector('.alpha-thumb')
this.__INPUT__ = elem.querySelector('.input')
}
_updateView() {
var { hsb, rgba } = this.state
var scene, color, alphaColor
var x, y
x = Math.ceil((hsb.s * 280) / 100)
y = 180 - Math.ceil((hsb.b * 180) / 100)
scene = '#' + rgb2hex(hsb2rgb({ h: hsb.h, s: 100, b: 100 }))
alphaColor = '#' + rgb2hex(rgba)
if (rgba.a < 100) {
color = `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a / 100})`
} else {
color = alphaColor
}
this._moveSceneThumb(x, y)
this.__HUE_THUMB__.style.top = hsb.h / 2 + 'px'
this.__SCENE__.style.backgroundColor = scene
this.__PREVIEW__.style.backgroundColor = color
this.__ALPHA_BAR__.style.background = `linear-gradient(to right, transparent, ${alphaColor})`
this.__INPUT__.value = color
}
_moveSceneThumb(x, y) {
this.__SCENE_THUMB__.style.cssText = `left: ${x}px; top:${y}px`
}
_changeColor(x, y) {
var { rgba, hsb } = this.state
var alphaColor
hsb.s = ~~((100 * x) / 280)
hsb.b = ~~((100 * (180 - y)) / 180)
Object.assign(rgba, hsb2rgb(hsb))
var { r, g, b, a } = rgba
if (rgba.a < 100) {
this.__INPUT__.value = `rgba(${r}, ${g}, ${b}, ${a / 100})`
} else {
this.__INPUT__.value = `#${rgb2hex({ r, g, b })}`
}
alphaColor = `linear-gradient(to right, transparent, rgb(${r}, ${g}, ${b}))`
this.__PREVIEW__.style.backgroundColor = this.__INPUT__.value
this.__ALPHA_BAR__.style.background = alphaColor
this._moveSceneThumb(x, y)
this.dispatchEvent(new CustomEvent('input'))
}
get value() {
return this.__INPUT__.value
}
set value(val) {
var isHex
var rgb
val = val.toLowerCase()
if (!val) {
return
}
isHex = /^#[0-9a-f]{3,6}$/.test(val)
if (isHex) {
Object.assign(this.state.rgba, hex2rgb(val), { a: 100 })
} else {
var res = val.match(/rgba?\((\d+),\s*?(\d+),\s*?(\d+)[,\s]*?([\d\.]+)?\)/)
if (res) {
this.state.rgba = { r: +res[1], g: +res[2], b: +res[3], a: 100 }
if (res[4] !== undefined) {
this.state.rgba.a = ~~(res[4] * 100)
}
} else {
return
}
}
this.state.hsb = rgb2hsb(this.state.rgba)
this._updateView()
}
mounted() {
var handleMove
this.handleChange = $.bind(this.__INPUT__, 'change', ev => {
this.value = this.__INPUT__.value
})
this.handleDown = $.bind(this.__SCENE__, 'mousedown', ev => {
ev.preventDefault()
var { pageX, pageY } = ev
var { left, top } = $.offset(this.__SCENE__)
var w = 280
var h = 180
var x = pageX - left
var y = pageY - top
this._changeColor(x, y)
handleMove = $.bind(document, 'mousemove', ev => {
ev.preventDefault()
var { pageX, pageY } = ev
var x = pageX - left
var y = pageY - top
var rgb
x = x < 0 ? 0 : x > 280 ? 280 : x
y = y < 0 ? 0 : y > 180 ? 180 : y
this._changeColor(x, y)
})
})
this.handleUp = $.bind(document, 'mouseup', ev => {
ev.preventDefault()
$.unbind(document, 'mousemove', handleMove)
})
// 颜色池
this.handleInput1 = $.bind(this.__HUE__, 'input', ev => {
var h = 360 - this.__HUE__.value
var { s, b } = this.state.hsb
var rgba = this.state.rgba
var hsb = { h, s, b }
var scene, color, alphaColor
Object.assign(rgba, hsb2rgb(hsb))
this.state.hsb = hsb
this.state.rgba = rgba
scene = '#' + rgb2hex(hsb2rgb({ h, s: 100, b: 100 }))
alphaColor = '#' + rgb2hex(rgba)
if (rgba.a < 100) {
color = `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a / 100})`
} else {
color = alphaColor
}
this.__HUE_THUMB__.style.top = h / 2 + 'px'
this.__SCENE__.style.backgroundColor = scene
this.__PREVIEW__.style.backgroundColor = color
this.__ALPHA_BAR__.style.background = `linear-gradient(to right, transparent, ${alphaColor})`
this.__INPUT__.value = color
})
// 透明度
this.handleInput2 = $.bind(this.__ALPHA__, 'input', ev => {
var a = this.__ALPHA__.value
var { r, g, b } = this.state.rgba
var color = `rgba(${r}, ${g}, ${b}, ${a / 100})`
this.state.rgba.a = a
this.__ALPHA_THUMB__.style.left = a + '%'
this.__PREVIEW__.style.backgroundColor = color
this.__INPUT__.value = color
})
}
unmount() {
$.unbind(this.__SCENE__, 'mousedown', this.handleDown)
$.unbind(document, 'mousedown', this.handleUp)
$.unbind(this.__INPUT__, 'change', this.handleChange)
$.unbind(this.__HUE__, 'input', this.handleInput1)
$.unbind(this.__ALPHA__, 'input', this.handleInput2)
delete this.handleDown
delete this.handleUp
delete this.handleChange
delete this.handleInput1
delete this.handleInput2
}
}
</script>

View File

@ -1,47 +0,0 @@
# crypto & md5 加密组件
## md5
> 这里使用的是第三方的 SparkMD5 实现
```javascript
import { md5, md5Sum } from 'crypto/md5'
// 直接计算字段串的md5值
console.log(md5('123456'))
// 计算该文件的md5签名
var file = /*...*/ //文件表单获取
var fs = new FileReader()
fs.onload = function(){
console.log(md5Sum(this.result))
}
fs.readAsBinaryString(file)
```
## crypto
> 这里使用的是浏览器内置的实现, 需要在https下或本地才能用。
>> 返回值都是Promise对象
```javascript
import hash, { sha1, sha256, sha512, hmac, base64encode, base64decode } from 'crypto/index'
hash('sha-1', '123456')
// 等价于
sha1('123456')
// hmac签名
hmac('sha-1', '123456', 'a key', 'hex')
```

View File

@ -1,77 +0,0 @@
const encoder = new TextEncoder()
const subtle = window.crypto.subtle
/**
* String Uint8Array
*/
function str2uint(txt) {
return encoder.encode(txt)
}
/**
* ArrayBuffer hex
*/
function ab2hex(buf) {
var uint8 = new Uint8Array(buf)
return [...uint8].map(n => n.toString(16).padStart(2, '0')).join('')
}
/**
* ArrayBuffer Binary
*/
function ab2bin(buf) {
var bin = ''
var uint8 = new Uint8Array(buf)
for (var i = 0; i < uint8.length; i++) {
bin += String.fromCharCode(uint8[i])
}
return bin
}
/* ------------------------------------- */
export default function hash(type, str) {
return subtle.digest(type, str2uint(str)).then(buf => ab2hex(buf))
}
export function sha1(str) {
return hash('sha-1', str)
}
export function sha256(str) {
return hash('sha-256', str)
}
export function sha512(str) {
return hash('sha-512', str)
}
export function hmac(mode, str, key, output) {
key = key === '' ? new Uint8Array(16) : str2uint(key)
return subtle
.importKey('raw', key, { name: 'HMAC', hash: { name: mode } }, true, [
'sign',
'verify'
])
.then(cKey => {
return subtle.sign('HMAC', cKey, str2uint(str)).then(buf => {
if (output === 'binary') {
return ab2bin(buf)
} else if (output === 'hex') {
return ab2hex(buf)
} else if (output === 'base64') {
return window.btoa(ab2bin(buf))
}
return new Uint8Array(buf)
})
})
}
// 支持对中文的base64编码
export function base64encode(str) {
return window.btoa(unescape(encodeURIComponent(str)))
}
export function base64decode(str) {
return decodeURIComponent(escape(window.atob(str)))
}

View File

@ -1,764 +0,0 @@
/*
* Fastest md5 implementation around (JKM md5).
* Credits: Joseph Myers
*
* @see http://www.myersdaily.org/joseph/javascript/md5-text.html
* @see http://jsperf.com/md5-shootout/7
*/
/* this function is much faster,
so if possible we use it. Some IEs
are the only ones I know of that
need the idiotic second function,
generated by an if clause. */
var add32 = function(a, b) {
return (a + b) & 0xffffffff
},
hex_chr = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'a',
'b',
'c',
'd',
'e',
'f'
]
function cmn(q, a, b, x, s, t) {
a = add32(add32(a, q), add32(x, t))
return add32((a << s) | (a >>> (32 - s)), b)
}
function md5cycle(x, k) {
var a = x[0],
b = x[1],
c = x[2],
d = x[3]
a += (((b & c) | (~b & d)) + k[0] - 680876936) | 0
a = (((a << 7) | (a >>> 25)) + b) | 0
d += (((a & b) | (~a & c)) + k[1] - 389564586) | 0
d = (((d << 12) | (d >>> 20)) + a) | 0
c += (((d & a) | (~d & b)) + k[2] + 606105819) | 0
c = (((c << 17) | (c >>> 15)) + d) | 0
b += (((c & d) | (~c & a)) + k[3] - 1044525330) | 0
b = (((b << 22) | (b >>> 10)) + c) | 0
a += (((b & c) | (~b & d)) + k[4] - 176418897) | 0
a = (((a << 7) | (a >>> 25)) + b) | 0
d += (((a & b) | (~a & c)) + k[5] + 1200080426) | 0
d = (((d << 12) | (d >>> 20)) + a) | 0
c += (((d & a) | (~d & b)) + k[6] - 1473231341) | 0
c = (((c << 17) | (c >>> 15)) + d) | 0
b += (((c & d) | (~c & a)) + k[7] - 45705983) | 0
b = (((b << 22) | (b >>> 10)) + c) | 0
a += (((b & c) | (~b & d)) + k[8] + 1770035416) | 0
a = (((a << 7) | (a >>> 25)) + b) | 0
d += (((a & b) | (~a & c)) + k[9] - 1958414417) | 0
d = (((d << 12) | (d >>> 20)) + a) | 0
c += (((d & a) | (~d & b)) + k[10] - 42063) | 0
c = (((c << 17) | (c >>> 15)) + d) | 0
b += (((c & d) | (~c & a)) + k[11] - 1990404162) | 0
b = (((b << 22) | (b >>> 10)) + c) | 0
a += (((b & c) | (~b & d)) + k[12] + 1804603682) | 0
a = (((a << 7) | (a >>> 25)) + b) | 0
d += (((a & b) | (~a & c)) + k[13] - 40341101) | 0
d = (((d << 12) | (d >>> 20)) + a) | 0
c += (((d & a) | (~d & b)) + k[14] - 1502002290) | 0
c = (((c << 17) | (c >>> 15)) + d) | 0
b += (((c & d) | (~c & a)) + k[15] + 1236535329) | 0
b = (((b << 22) | (b >>> 10)) + c) | 0
a += (((b & d) | (c & ~d)) + k[1] - 165796510) | 0
a = (((a << 5) | (a >>> 27)) + b) | 0
d += (((a & c) | (b & ~c)) + k[6] - 1069501632) | 0
d = (((d << 9) | (d >>> 23)) + a) | 0
c += (((d & b) | (a & ~b)) + k[11] + 643717713) | 0
c = (((c << 14) | (c >>> 18)) + d) | 0
b += (((c & a) | (d & ~a)) + k[0] - 373897302) | 0
b = (((b << 20) | (b >>> 12)) + c) | 0
a += (((b & d) | (c & ~d)) + k[5] - 701558691) | 0
a = (((a << 5) | (a >>> 27)) + b) | 0
d += (((a & c) | (b & ~c)) + k[10] + 38016083) | 0
d = (((d << 9) | (d >>> 23)) + a) | 0
c += (((d & b) | (a & ~b)) + k[15] - 660478335) | 0
c = (((c << 14) | (c >>> 18)) + d) | 0
b += (((c & a) | (d & ~a)) + k[4] - 405537848) | 0
b = (((b << 20) | (b >>> 12)) + c) | 0
a += (((b & d) | (c & ~d)) + k[9] + 568446438) | 0
a = (((a << 5) | (a >>> 27)) + b) | 0
d += (((a & c) | (b & ~c)) + k[14] - 1019803690) | 0
d = (((d << 9) | (d >>> 23)) + a) | 0
c += (((d & b) | (a & ~b)) + k[3] - 187363961) | 0
c = (((c << 14) | (c >>> 18)) + d) | 0
b += (((c & a) | (d & ~a)) + k[8] + 1163531501) | 0
b = (((b << 20) | (b >>> 12)) + c) | 0
a += (((b & d) | (c & ~d)) + k[13] - 1444681467) | 0
a = (((a << 5) | (a >>> 27)) + b) | 0
d += (((a & c) | (b & ~c)) + k[2] - 51403784) | 0
d = (((d << 9) | (d >>> 23)) + a) | 0
c += (((d & b) | (a & ~b)) + k[7] + 1735328473) | 0
c = (((c << 14) | (c >>> 18)) + d) | 0
b += (((c & a) | (d & ~a)) + k[12] - 1926607734) | 0
b = (((b << 20) | (b >>> 12)) + c) | 0
a += ((b ^ c ^ d) + k[5] - 378558) | 0
a = (((a << 4) | (a >>> 28)) + b) | 0
d += ((a ^ b ^ c) + k[8] - 2022574463) | 0
d = (((d << 11) | (d >>> 21)) + a) | 0
c += ((d ^ a ^ b) + k[11] + 1839030562) | 0
c = (((c << 16) | (c >>> 16)) + d) | 0
b += ((c ^ d ^ a) + k[14] - 35309556) | 0
b = (((b << 23) | (b >>> 9)) + c) | 0
a += ((b ^ c ^ d) + k[1] - 1530992060) | 0
a = (((a << 4) | (a >>> 28)) + b) | 0
d += ((a ^ b ^ c) + k[4] + 1272893353) | 0
d = (((d << 11) | (d >>> 21)) + a) | 0
c += ((d ^ a ^ b) + k[7] - 155497632) | 0
c = (((c << 16) | (c >>> 16)) + d) | 0
b += ((c ^ d ^ a) + k[10] - 1094730640) | 0
b = (((b << 23) | (b >>> 9)) + c) | 0
a += ((b ^ c ^ d) + k[13] + 681279174) | 0
a = (((a << 4) | (a >>> 28)) + b) | 0
d += ((a ^ b ^ c) + k[0] - 358537222) | 0
d = (((d << 11) | (d >>> 21)) + a) | 0
c += ((d ^ a ^ b) + k[3] - 722521979) | 0
c = (((c << 16) | (c >>> 16)) + d) | 0
b += ((c ^ d ^ a) + k[6] + 76029189) | 0
b = (((b << 23) | (b >>> 9)) + c) | 0
a += ((b ^ c ^ d) + k[9] - 640364487) | 0
a = (((a << 4) | (a >>> 28)) + b) | 0
d += ((a ^ b ^ c) + k[12] - 421815835) | 0
d = (((d << 11) | (d >>> 21)) + a) | 0
c += ((d ^ a ^ b) + k[15] + 530742520) | 0
c = (((c << 16) | (c >>> 16)) + d) | 0
b += ((c ^ d ^ a) + k[2] - 995338651) | 0
b = (((b << 23) | (b >>> 9)) + c) | 0
a += ((c ^ (b | ~d)) + k[0] - 198630844) | 0
a = (((a << 6) | (a >>> 26)) + b) | 0
d += ((b ^ (a | ~c)) + k[7] + 1126891415) | 0
d = (((d << 10) | (d >>> 22)) + a) | 0
c += ((a ^ (d | ~b)) + k[14] - 1416354905) | 0
c = (((c << 15) | (c >>> 17)) + d) | 0
b += ((d ^ (c | ~a)) + k[5] - 57434055) | 0
b = (((b << 21) | (b >>> 11)) + c) | 0
a += ((c ^ (b | ~d)) + k[12] + 1700485571) | 0
a = (((a << 6) | (a >>> 26)) + b) | 0
d += ((b ^ (a | ~c)) + k[3] - 1894986606) | 0
d = (((d << 10) | (d >>> 22)) + a) | 0
c += ((a ^ (d | ~b)) + k[10] - 1051523) | 0
c = (((c << 15) | (c >>> 17)) + d) | 0
b += ((d ^ (c | ~a)) + k[1] - 2054922799) | 0
b = (((b << 21) | (b >>> 11)) + c) | 0
a += ((c ^ (b | ~d)) + k[8] + 1873313359) | 0
a = (((a << 6) | (a >>> 26)) + b) | 0
d += ((b ^ (a | ~c)) + k[15] - 30611744) | 0
d = (((d << 10) | (d >>> 22)) + a) | 0
c += ((a ^ (d | ~b)) + k[6] - 1560198380) | 0
c = (((c << 15) | (c >>> 17)) + d) | 0
b += ((d ^ (c | ~a)) + k[13] + 1309151649) | 0
b = (((b << 21) | (b >>> 11)) + c) | 0
a += ((c ^ (b | ~d)) + k[4] - 145523070) | 0
a = (((a << 6) | (a >>> 26)) + b) | 0
d += ((b ^ (a | ~c)) + k[11] - 1120210379) | 0
d = (((d << 10) | (d >>> 22)) + a) | 0
c += ((a ^ (d | ~b)) + k[2] + 718787259) | 0
c = (((c << 15) | (c >>> 17)) + d) | 0
b += ((d ^ (c | ~a)) + k[9] - 343485551) | 0
b = (((b << 21) | (b >>> 11)) + c) | 0
x[0] = (a + x[0]) | 0
x[1] = (b + x[1]) | 0
x[2] = (c + x[2]) | 0
x[3] = (d + x[3]) | 0
}
function md5blk(s) {
var md5blks = [],
i /* Andy King said do it this way. */
for (i = 0; i < 64; i += 4) {
md5blks[i >> 2] =
s.charCodeAt(i) +
(s.charCodeAt(i + 1) << 8) +
(s.charCodeAt(i + 2) << 16) +
(s.charCodeAt(i + 3) << 24)
}
return md5blks
}
function md5blk_array(a) {
var md5blks = [],
i /* Andy King said do it this way. */
for (i = 0; i < 64; i += 4) {
md5blks[i >> 2] =
a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24)
}
return md5blks
}
function md51(s) {
var n = s.length,
state = [1732584193, -271733879, -1732584194, 271733878],
i,
length,
tail,
tmp,
lo,
hi
for (i = 64; i <= n; i += 64) {
md5cycle(state, md5blk(s.substring(i - 64, i)))
}
s = s.substring(i - 64)
length = s.length
tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
for (i = 0; i < length; i += 1) {
tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3)
}
tail[i >> 2] |= 0x80 << (i % 4 << 3)
if (i > 55) {
md5cycle(state, tail)
for (i = 0; i < 16; i += 1) {
tail[i] = 0
}
}
// Beware that the final length might not fit in 32 bits so we take care of that
tmp = n * 8
tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/)
lo = parseInt(tmp[2], 16)
hi = parseInt(tmp[1], 16) || 0
tail[14] = lo
tail[15] = hi
md5cycle(state, tail)
return state
}
function md51_array(a) {
var n = a.length,
state = [1732584193, -271733879, -1732584194, 271733878],
i,
length,
tail,
tmp,
lo,
hi
for (i = 64; i <= n; i += 64) {
md5cycle(state, md5blk_array(a.subarray(i - 64, i)))
}
// Not sure if it is a bug, however IE10 will always produce a sub array of length 1
// containing the last element of the parent array if the sub array specified starts
// beyond the length of the parent array - weird.
// https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue
a = i - 64 < n ? a.subarray(i - 64) : new Uint8Array(0)
length = a.length
tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
for (i = 0; i < length; i += 1) {
tail[i >> 2] |= a[i] << (i % 4 << 3)
}
tail[i >> 2] |= 0x80 << (i % 4 << 3)
if (i > 55) {
md5cycle(state, tail)
for (i = 0; i < 16; i += 1) {
tail[i] = 0
}
}
// Beware that the final length might not fit in 32 bits so we take care of that
tmp = n * 8
tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/)
lo = parseInt(tmp[2], 16)
hi = parseInt(tmp[1], 16) || 0
tail[14] = lo
tail[15] = hi
md5cycle(state, tail)
return state
}
function rhex(n) {
var s = '',
j
for (j = 0; j < 4; j += 1) {
s += hex_chr[(n >> (j * 8 + 4)) & 0x0f] + hex_chr[(n >> (j * 8)) & 0x0f]
}
return s
}
function hex(x) {
var i
for (i = 0; i < x.length; i += 1) {
x[i] = rhex(x[i])
}
return x.join('')
}
// In some cases the fast add32 function cannot be used..
if (hex(md51('hello')) !== '5d41402abc4b2a76b9719d911017c592') {
add32 = function(x, y) {
var lsw = (x & 0xffff) + (y & 0xffff),
msw = (x >> 16) + (y >> 16) + (lsw >> 16)
return (msw << 16) | (lsw & 0xffff)
}
}
// ---------------------------------------------------
/**
* ArrayBuffer slice polyfill.
*
* @see https://github.com/ttaubert/node-arraybuffer-slice
*/
if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) {
;(function() {
function clamp(val, length) {
val = val | 0 || 0
if (val < 0) {
return Math.max(val + length, 0)
}
return Math.min(val, length)
}
ArrayBuffer.prototype.slice = function(from, to) {
var length = this.byteLength,
begin = clamp(from, length),
end = length,
num,
target,
targetArray,
sourceArray
if (to !== undefined) {
end = clamp(to, length)
}
if (begin > end) {
return new ArrayBuffer(0)
}
num = end - begin
target = new ArrayBuffer(num)
targetArray = new Uint8Array(target)
sourceArray = new Uint8Array(this, begin, num)
targetArray.set(sourceArray)
return target
}
})()
}
// ---------------------------------------------------
/**
* Helpers.
*/
function toUtf8(str) {
str += ''
if (/[\u0080-\uFFFF]/.test(str)) {
str = unescape(encodeURIComponent(str))
}
return str
}
function utf8Str2ArrayBuffer(str, returnUInt8Array) {
var length = str.length,
buff = new ArrayBuffer(length),
arr = new Uint8Array(buff),
i
for (i = 0; i < length; i += 1) {
arr[i] = str.charCodeAt(i)
}
return returnUInt8Array ? arr : buff
}
function arrayBuffer2Utf8Str(buff) {
return String.fromCharCode.apply(null, new Uint8Array(buff))
}
function concatenateArrayBuffers(first, second, returnUInt8Array) {
var result = new Uint8Array(first.byteLength + second.byteLength)
result.set(new Uint8Array(first))
result.set(new Uint8Array(second), first.byteLength)
return returnUInt8Array ? result : result.buffer
}
function hexToBinaryString(hex) {
var bytes = [],
length = hex.length,
x
for (x = 0; x < length - 1; x += 2) {
bytes.push(parseInt(hex.substr(x, 2), 16))
}
return String.fromCharCode.apply(String, bytes)
}
// ---------------------------------------------------
/**
* SparkMD5 OOP implementation.
*
* Use this class to perform an incremental md5, otherwise use the
* static methods instead.
*/
function SparkMD5() {
// call reset to init the instance
this.reset()
}
/**
* Appends a string.
* A conversion will be applied if an utf8 string is detected.
*
* @param {String} str The string to be appended
*
* @return {SparkMD5} The instance itself
*/
SparkMD5.prototype.append = function(str) {
// Converts the string to utf8 bytes if necessary
// Then append as binary
this.appendBinary(toUtf8(str))
return this
}
/**
* Appends a binary string.
*
* @param {String} contents The binary string to be appended
*
* @return {SparkMD5} The instance itself
*/
SparkMD5.prototype.appendBinary = function(contents) {
this._buff += contents
this._length += contents.length
var length = this._buff.length,
i
for (i = 64; i <= length; i += 64) {
md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i)))
}
this._buff = this._buff.substring(i - 64)
return this
}
/**
* Finishes the incremental computation, reseting the internal state and
* returning the result.
*
* @param {Boolean} raw True to get the raw string, false to get the hex string
*
* @return {String} The result
*/
SparkMD5.prototype.end = function(raw) {
var buff = this._buff,
length = buff.length,
i,
tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
ret
for (i = 0; i < length; i += 1) {
tail[i >> 2] |= buff.charCodeAt(i) << (i % 4 << 3)
}
this._finish(tail, length)
ret = hex(this._hash)
if (raw) {
ret = hexToBinaryString(ret)
}
this.reset()
return ret
}
/**
* Resets the internal state of the computation.
*
* @return {SparkMD5} The instance itself
*/
SparkMD5.prototype.reset = function() {
this._buff = ''
this._length = 0
this._hash = [1732584193, -271733879, -1732584194, 271733878]
return this
}
/**
* Gets the internal state of the computation.
*
* @return {Object} The state
*/
SparkMD5.prototype.getState = function() {
return {
buff: this._buff,
length: this._length,
hash: this._hash
}
}
/**
* Gets the internal state of the computation.
*
* @param {Object} state The state
*
* @return {SparkMD5} The instance itself
*/
SparkMD5.prototype.setState = function(state) {
this._buff = state.buff
this._length = state.length
this._hash = state.hash
return this
}
/**
* Releases memory used by the incremental buffer and other additional
* resources. If you plan to use the instance again, use reset instead.
*/
SparkMD5.prototype.destroy = function() {
delete this._hash
delete this._buff
delete this._length
}
/**
* Finish the final calculation based on the tail.
*
* @param {Array} tail The tail (will be modified)
* @param {Number} length The length of the remaining buffer
*/
SparkMD5.prototype._finish = function(tail, length) {
var i = length,
tmp,
lo,
hi
tail[i >> 2] |= 0x80 << (i % 4 << 3)
if (i > 55) {
md5cycle(this._hash, tail)
for (i = 0; i < 16; i += 1) {
tail[i] = 0
}
}
// Do the final computation based on the tail and length
// Beware that the final length may not fit in 32 bits so we take care of that
tmp = this._length * 8
tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/)
lo = parseInt(tmp[2], 16)
hi = parseInt(tmp[1], 16) || 0
tail[14] = lo
tail[15] = hi
md5cycle(this._hash, tail)
}
/**
* Performs the md5 hash on a string.
* A conversion will be applied if utf8 string is detected.
*
* @param {String} str The string
* @param {Boolean} [raw] True to get the raw string, false to get the hex string
*
* @return {String} The result
*/
SparkMD5.hash = function(str, raw) {
// Converts the string to utf8 bytes if necessary
// Then compute it using the binary function
return SparkMD5.hashBinary(toUtf8(str), raw)
}
/**
* Performs the md5 hash on a binary string.
*
* @param {String} content The binary string
* @param {Boolean} [raw] True to get the raw string, false to get the hex string
*
* @return {String} The result
*/
SparkMD5.hashBinary = function(content, raw) {
var hash = md51(content),
ret = hex(hash)
return raw ? hexToBinaryString(ret) : ret
}
// ---------------------------------------------------
/**
* SparkMD5 OOP implementation for array buffers.
*
* Use this class to perform an incremental md5 ONLY for array buffers.
*/
SparkMD5.ArrayBuffer = function() {
// call reset to init the instance
this.reset()
}
/**
* Appends an array buffer.
*
* @param {ArrayBuffer} arr The array to be appended
*
* @return {SparkMD5.ArrayBuffer} The instance itself
*/
SparkMD5.ArrayBuffer.prototype.append = function(arr) {
var buff = concatenateArrayBuffers(this._buff.buffer, arr, true),
length = buff.length,
i
this._length += arr.byteLength
for (i = 64; i <= length; i += 64) {
md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i)))
}
this._buff =
i - 64 < length
? new Uint8Array(buff.buffer.slice(i - 64))
: new Uint8Array(0)
return this
}
/**
* Finishes the incremental computation, reseting the internal state and
* returning the result.
*
* @param {Boolean} raw True to get the raw string, false to get the hex string
*
* @return {String} The result
*/
SparkMD5.ArrayBuffer.prototype.end = function(raw) {
var buff = this._buff,
length = buff.length,
tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
i,
ret
for (i = 0; i < length; i += 1) {
tail[i >> 2] |= buff[i] << (i % 4 << 3)
}
this._finish(tail, length)
ret = hex(this._hash)
if (raw) {
ret = hexToBinaryString(ret)
}
this.reset()
return ret
}
/**
* Resets the internal state of the computation.
*
* @return {SparkMD5.ArrayBuffer} The instance itself
*/
SparkMD5.ArrayBuffer.prototype.reset = function() {
this._buff = new Uint8Array(0)
this._length = 0
this._hash = [1732584193, -271733879, -1732584194, 271733878]
return this
}
/**
* Gets the internal state of the computation.
*
* @return {Object} The state
*/
SparkMD5.ArrayBuffer.prototype.getState = function() {
var state = SparkMD5.prototype.getState.call(this)
// Convert buffer to a string
state.buff = arrayBuffer2Utf8Str(state.buff)
return state
}
/**
* Gets the internal state of the computation.
*
* @param {Object} state The state
*
* @return {SparkMD5.ArrayBuffer} The instance itself
*/
SparkMD5.ArrayBuffer.prototype.setState = function(state) {
// Convert string to buffer
state.buff = utf8Str2ArrayBuffer(state.buff, true)
return SparkMD5.prototype.setState.call(this, state)
}
SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy
SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish
/**
* Performs the md5 hash on an array buffer.
*
* @param {ArrayBuffer} arr The array buffer
* @param {Boolean} [raw] True to get the raw string, false to get the hex one
*
* @return {String} The result
*/
SparkMD5.ArrayBuffer.hash = function(arr, raw) {
var hash = md51_array(new Uint8Array(arr)),
ret = hex(hash)
return raw ? hexToBinaryString(ret) : ret
}
var _sparkIns = new SparkMD5()
export function md5(str) {
_sparkIns.append(str)
return _sparkIns.end()
}
export function md5Sum(binStr) {
_sparkIns.appendBinary(binStr)
return _sparkIns.end()
}
export default _sparkIns

View File

@ -1,219 +0,0 @@
.CodeMirror {
height: 100%;
line-height: 1.5;
font-family: monospace;
position: relative;
overflow: hidden;
background: #272822;
color: #f8f8f2;
}
.CodeMirror-scroll {
overflow: auto;
height: 100%;
width: 100%;
position: relative;
outline: 0;
}
.CodeMirror-scrollbar {
position: absolute;
right: 0;
top: 0;
overflow-x: hidden;
overflow-y: scroll;
z-index: 5;
}
.CodeMirror-scrollbar-inner {
width: 1px;
}
.CodeMirror-scrollbar.cm-sb-overlap {
position: absolute;
z-index: 1;
float: none;
right: 0;
min-width: 12px;
}
.CodeMirror-scrollbar.cm-sb-nonoverlap {
min-width: 12px;
}
.CodeMirror-scrollbar.cm-sb-ie7 {
min-width: 18px;
}
.CodeMirror-gutter {
position: absolute;
left: 0;
top: 0;
z-index: 10;
background-color: transparent;
border-right: 1px solid #454545;
min-width: 2em;
height: 100%;
}
.CodeMirror-gutter-text {
color: #aaa;
text-align: right;
padding: 0.4em 0.2em 0.4em 0.4em;
white-space: pre !important;
cursor: default;
}
.CodeMirror-lines {
padding: 0.4em;
white-space: pre;
cursor: text;
}
.CodeMirror pre {
-moz-border-radius: 0;
-webkit-border-radius: 0;
-o-border-radius: 0;
border-radius: 0;
border-width: 0;
background: 0 0;
font-family: inherit;
font-size: inherit;
padding: 0;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
overflow: visible;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: hidden;
}
.CodeMirror textarea {
outline: 0 !important;
}
.CodeMirror pre.CodeMirror-cursor {
z-index: 10;
position: absolute;
visibility: hidden;
border-left: 1px solid #9effff;
border-right: none;
width: 0;
}
.cm-keymap-fat-cursor pre.CodeMirror-cursor {
width: auto;
border: 0;
background: 0 0;
background: rgba(0, 200, 0, 0.4);
filter: progid:DXImageTransform.Microsoft.gradient(
startColorstr=#6600c800,
endColorstr=#4c00c800
);
}
.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) {
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
}
.CodeMirror-focused pre.CodeMirror-cursor {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-selected,
div.CodeMirror-selected {
background: #49483e;
}
.CodeMirror-searching {
background: #ffa;
background: rgba(255, 255, 0, 0.4);
}
.cm-s-default span.cm-keyword {
color: #f92672;
}
.cm-s-default span.cm-atom {
color: #ae81ff;
}
.cm-s-default span.cm-number {
color: #f30;
}
.cm-s-default span.cm-def {
color: #fd971f;
}
.cm-s-default span.cm-variable {
color: #f8f8f2;
}
.cm-s-default span.cm-variable-2 {
color: #9effff;
}
.cm-s-default span.cm-property,
.cm-s-default span.cm-variable-3 {
color: #66d9ef;
}
.cm-s-default span.cm-operator {
color: #9effff;
}
.cm-s-default span.cm-comment {
color: #75715e;
}
.cm-s-default span.cm-string {
color: #e6db74;
}
.cm-s-default span.cm-string-2 {
color: #f50;
}
.cm-s-default span.cm-meta {
color: #555;
}
.cm-s-default span.cm-error {
background: #f92672;
color: #f8f8f0;
}
.cm-s-default span.cm-qualifier {
color: #75d908;
}
.cm-s-default span.cm-builtin {
color: #66d9ef;
}
.cm-s-default span.cm-bracket {
color: #f8f8f2;
}
.cm-s-default span.cm-tag {
color: #f92672;
}
.cm-s-default span.cm-attribute {
color: #a6e22e;
}
.cm-s-default span.cm-header {
color: #ae81ff;
}
.cm-s-default span.cm-quote {
color: #090;
}
.cm-s-default span.cm-hr {
color: #999;
}
.cm-s-default span.cm-link {
color: #ae81ff;
}
span.cm-header,
span.cm-strong {
font-weight: 700;
}
span.cm-em {
font-style: italic;
}
span.cm-emstrong {
font-style: italic;
font-weight: 700;
}
span.cm-link {
text-decoration: underline;
}
span.cm-invalidchar {
color: red;
}
div.CodeMirror span.CodeMirror-matchingbracket {
text-decoration: underline;
color: #fff !important;
}
div.CodeMirror span.CodeMirror-nonmatchingbracket {
color: #f22;
}
@media print {
.CodeMirror pre.CodeMirror-cursor {
visibility: hidden;
}
}

View File

@ -1,226 +0,0 @@
.CodeMirror {
height: 100%;
line-height: 1.5;
font-family: monospace;
position: relative;
overflow: hidden;
background: #fff;
color: #666;
}
.CodeMirror-scroll {
overflow: auto;
height: 100%;
width: 100%;
position: relative;
outline: 0;
}
.CodeMirror-scrollbar {
position: absolute;
right: 0;
top: 0;
overflow-x: hidden;
overflow-y: scroll;
z-index: 5;
}
.CodeMirror-scrollbar-inner {
width: 1px;
}
.CodeMirror-scrollbar.cm-sb-overlap {
position: absolute;
z-index: 1;
float: none;
right: 0;
min-width: 12px;
}
.CodeMirror-scrollbar.cm-sb-nonoverlap {
min-width: 12px;
}
.CodeMirror-scrollbar.cm-sb-ie7 {
min-width: 18px;
}
.CodeMirror-gutter {
position: absolute;
left: 0;
top: 0;
z-index: 10;
background-color: transparent;
border-right: 1px solid #454545;
min-width: 2em;
height: 100%;
}
.CodeMirror-gutter-text {
color: #aaa;
text-align: right;
padding: 0.4em 0.2em 0.4em 0.4em;
white-space: pre !important;
cursor: default;
}
.CodeMirror-lines {
padding: 0.4em;
white-space: pre;
cursor: text;
}
.CodeMirror pre {
-moz-border-radius: 0;
-webkit-border-radius: 0;
-o-border-radius: 0;
border-radius: 0;
border-width: 0;
background: 0 0;
font-family: inherit;
font-size: inherit;
padding: 0;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
overflow: visible;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: hidden;
}
.CodeMirror textarea {
outline: 0 !important;
}
.CodeMirror pre.CodeMirror-cursor {
z-index: 10;
position: absolute;
visibility: hidden;
border-left: 1px solid #f00;
border-right: none;
width: 0;
}
.cm-keymap-fat-cursor pre.CodeMirror-cursor {
width: auto;
border: 0;
background: 0 0;
background: rgba(0, 200, 0, 0.4);
filter: progid:DXImageTransform.Microsoft.gradient(
startColorstr=#6600c800,
endColorstr=#4c00c800
);
}
.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) {
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
}
.CodeMirror-focused pre.CodeMirror-cursor {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-selected,
div.CodeMirror-selected {
background: #49483e;
}
.CodeMirror-searching {
background: #ffa;
background: rgba(255, 255, 0, 0.4);
}
.cm-s-default span.cm-keyword {
color: #f92672;
}
.cm-s-default span.cm-atom {
color: #ae81ff;
}
.cm-s-default span.cm-number {
color: #f30;
}
.cm-s-default span.cm-def {
color: #fd971f;
}
.cm-s-default span.cm-variable {
color: #f8f8f2;
}
.cm-s-default span.cm-variable-2 {
color: #05a;
}
.cm-s-default span.cm-property,
.cm-s-default span.cm-variable-3 {
color: #3298dc;
}
.cm-s-default span.cm-operator {
color: #9effff;
}
.cm-s-default span.cm-comment {
border: 1px solid #eee;
color: #75715e;
}
.cm-s-default span.cm-string {
color: #a11;
}
.cm-s-default span.cm-string-2 {
color: #f50;
}
.cm-s-default span.cm-meta {
color: #555;
}
.cm-s-default span.cm-error {
background: #f92672;
color: #f8f8f0;
}
.cm-s-default span.cm-qualifier {
color: #75d908;
}
.cm-s-default span.cm-builtin {
color: #66d9ef;
}
.cm-s-default span.cm-bracket {
color: #f8f8f2;
}
.cm-s-default span.cm-tag {
color: #13b65a;
}
.cm-s-default span.cm-attribute {
color: #a6e22e;
}
.cm-s-default span.cm-header {
color: #f92672;
}
.cm-s-default span.cm-quote {
color: #666;
background: #f2f2f2;
}
.cm-s-default span.cm-hr {
color: #999;
}
.cm-s-default span.cm-link {
color: #ae81ff;
}
.cm-s-default span.cm-strikethrough {
text-decoration: line-through;
}
span.cm-header,
span.cm-strong {
font-weight: bold;
color: #333;
}
span.cm-em {
font-style: italic;
color: #f90;
}
span.cm-emstrong {
font-style: italic;
font-weight: bold;
}
span.cm-link {
text-decoration: underline;
}
span.cm-invalidchar {
color: red;
}
div.CodeMirror span.CodeMirror-matchingbracket {
text-decoration: underline;
color: #fff !important;
}
div.CodeMirror span.CodeMirror-nonmatchingbracket {
color: #f22;
}
@media print {
.CodeMirror pre.CodeMirror-cursor {
visibility: hidden;
}
}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.do-meditor-attach{width:630px;height:300px;cursor:default;color:#526273}.do-meditor-attach ::-webkit-scrollbar{width:5px;height:5px;background:#f2f5fc}.do-meditor-attach ::-webkit-scrollbar:hover{background:#e8ebf4}.do-meditor-attach ::-webkit-scrollbar-button{display:none}.do-meditor-attach ::-webkit-scrollbar-thumb{background:#bdbdbd}.do-meditor-attach ::-webkit-scrollbar-thumb:hover{background:#9e9e9e}.do-meditor-attach .tab-box{float:left;width:130px;height:300px;padding:10px 5px;text-align:center;background:#f2f5fc;border-radius:5px}.do-meditor-attach .tab-box .item{display:block;width:100%;height:40px;line-height:40px;border-radius:3px;cursor:pointer}.do-meditor-attach .tab-box .item.active{background:#fff}.do-meditor-attach .cont-box{position:relative;float:right;width:480px;height:auto;min-height:200px}.do-meditor-attach .cont-box .remote,.do-meditor-attach .cont-box .local{position:relative;width:92%;height:auto;margin:0 auto}.do-meditor-attach .cont-box .remote{padding:30px 0}.do-meditor-attach .cont-box .hide{display:none}.do-meditor-attach .cont-box .section{display:block;width:100%;height:auto;margin:15px 0;line-height:35px}.do-meditor-attach .cont-box .section .submit{float:right;width:30%;height:45px;line-height:45px}.do-meditor-attach .cont-box .select-file{width:100%;height:35px;line-height:35px}.do-meditor-attach .cont-box .select-file .file{float:left;width:100px;height:35px;border-radius:3px;background:#e8ebf4;text-align:center;cursor:pointer}.do-meditor-attach .cont-box .select-file .file:hover{background:#f2f5fc}.do-meditor-attach .cont-box .select-file .file:active{background:#dae1e9}.do-meditor-attach .cont-box .select-file .tips{display:inline-block;padding:0 10px}.do-meditor-attach .cont-box .upload-box{width:100%;height:auto;min-height:255px;padding-top:10px}.do-meditor-attach .cont-box .upload-box .thead{width:100%;height:35px;line-height:35px;background:#f2f5fc}.do-meditor-attach .cont-box .upload-box .col{overflow:hidden;float:left;height:30px;padding:0 5px;text-align:center}.do-meditor-attach .cont-box .upload-box .col:nth-child(1){width:50%}.do-meditor-attach .cont-box .upload-box .col:nth-child(2){width:35%}.do-meditor-attach .cont-box .upload-box .col:nth-child(3){width:15%}.do-meditor-attach .cont-box .upload-box .tbody{overflow:hidden;overflow-y:auto;width:100%;height:220px}.do-meditor-attach .cont-box .upload-box .tbody p{display:block;width:100%;height:30px;line-height:30px}.do-meditor-attach .cont-box .upload-box .insert{display:inline-block;padding:3px 5px;line-height:13px;background:#f2f5fc;color:#526273;cursor:pointer}.do-meditor-attach .cont-box .upload-box .red{color:#f30}.do-meditor-attach .cont-box .manager{overflow:hidden;overflow-y:auto;width:100%;height:300px}.do-meditor-attach .cont-box .manager .list-box{width:100%;-webkit-column-count:4;column-count:4;-webkit-column-gap:5;column-gap:5}.do-meditor-attach .cont-box .manager .item{margin:0 0 10px;padding:5px;text-align:center;background:#f2f5fc;-webkit-column-break-inside:avoid;break-inside:avoid;transition:background .2s ease-in-out}.do-meditor-attach .cont-box .manager .item:hover{background:#dae1e9}.do-meditor-attach .cont-box .manager .item .thumb{display:block;width:100%}.do-meditor-attach .cont-box .manager .item .name{overflow:hidden;height:30px;line-height:30px;text-align:center}.do-meditor-attach .cont-box .manager .item img{width:100%;height:auto}.do-meditor-attach .cont-box .manager .item em{line-height:1;font-size:50px}

View File

@ -1,84 +0,0 @@
@charset "UTF-8";
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2017-04-20 19:13:24
*
*/
@import 'var.scss';
.do-meditor-attach {width:630px;height:300px;cursor:default;color:nth($cd, 2);
::-webkit-scrollbar {width:5px;height:5px;background:nth($cp, 1);}
::-webkit-scrollbar:hover {background:nth($cp, 2);}
::-webkit-scrollbar-button {display:none;}
::-webkit-scrollbar-thumb {background:nth($cgr, 1);}
::-webkit-scrollbar-thumb:hover {background:nth($cgr, 2);}
.tab-box {float:left;width:130px;height:300px;padding:10px 5px;text-align:center;background:nth($cp, 1);border-radius:5px;
.item {display:block;width:100%;height:40px;line-height:40px;border-radius:3px;cursor:pointer;
&.active {background:#fff;}
}
}
.cont-box {position:relative;float:right;width:480px;height:auto;min-height:200px;
.remote,
.local {position:relative;width:92%;height:auto;margin:0 auto;}
.remote {padding:30px 0;}
.hide {display:none;}
.section {display:block;width:100%;height:auto;margin:15px 0;line-height:35px;
.submit {float:right;width:30%;height:45px;line-height:45px;}
}
.select-file {width:100%;height:35px;line-height:35px;
.file {float:left;width:100px;height:35px;border-radius:3px;background:nth($cp, 2);text-align:center;cursor:pointer;
&:hover {background:nth($cp, 1);}
&:active {background:nth($cp, 3);}
}
.tips {display:inline-block;padding:0 10px;}
}
.upload-box {width:100%;height:auto;min-height:255px;padding-top:10px;
.thead {width:100%;height:35px;line-height:35px;background:nth($cp, 1);}
.col {overflow:hidden;float:left;height:30px;padding:0 5px;text-align:center;}
.col:nth-child(1) {width:50%}
.col:nth-child(2) {width:35%}
.col:nth-child(3) {width:15%}
.tbody {overflow:hidden;overflow-y:auto;width:100%;height:220px;
p {display:block;width:100%;height:30px;line-height:30px;}
}
.insert {display:inline-block;padding:3px 5px;line-height:13px;background:nth($cp, 1);color:nth($cd, 2);cursor:pointer;}
.red {color:#f30;}
}
.manager {overflow:hidden;overflow-y:auto;width:100%;height:300px;
.list-box {width:100%;column-count:4;column-gap: 5;}
.item {margin:0 0 10px;padding:5px;text-align:center;background:nth($cp, 1); break-inside: avoid;@include ts(background);
&:hover {background:nth($cp, 3)}
.thumb {display:block;width:100%;}
.name {overflow:hidden;height:30px;line-height:30px; text-align:center;}
img {width:100%;height:auto;}
em {line-height:1;font-size:50px;}
}
}
}
}

View File

@ -1,109 +0,0 @@
@charset "UTF-8";
/**
*
* @authors Lincoln (875482941@qq.com)
* @date 2017-09-07 20:32:11
* @version $Id$
*/
@import "var.scss";
.do-sliders {position: relative;height: 100%;width: 100%;
.skin{height: 100%}
&.fullscreen{position: absolute;width: 100%;height: 100%;left: 0;top: 0;}
.slider-content{position: relative;
.container {position: relative;height: 100%;overflow: hidden;
.box {position: relative;height: 100%;width: 100%;transition: .4s;
.page {position:relative;display: inline-block;height: 100%;vertical-align: middle;
img {width: 100%;height: 100%;object-fit: cover;}
.title-class {position: absolute;width: 30%;min-height: 30px;line-height: 30px;left: 0;bottom: 15%;right: 0;margin: auto;text-align: center;font-size: 25px;}
.elm{height: 100%;width: 100%;background: #333;}
.default-elm{width: 100%;height: 100%;}
.default-btn{width: 100%;height: 100%;}
}
.fadeType{position: absolute;left: 0;top: 0;width: 100%;transition: .4s;}
}
}
.slider-btn {position: absolute;top: 50%;min-width: 50px;min-height: 50px;margin-top: -25px;text-decoration: none;font-size: 30px;line-height: 50px;text-align: center;border-radius: 100%;color: rgba(255,255,255,.6);font-weight: bold;z-index: 1;transition: .4s;}
// .slider-btn:hover {color: #fff;}
.slider-btn:nth-of-type(1){margin-left: 20px;}
// .slider-btn:nth-of-type(1):hover{animation: left-to-right .3s;}
.slider-btn:nth-of-type(2){right: 0;margin-right: 20px;}
// .slider-btn:nth-of-type(2):hover{animation: right-to-left .3s;}
}
.slider-preview-btn {position: absolute;bottom: 2%;height: 12%;width: 100%;margin: 0 auto;text-align: center;overflow: hidden;
span {display: inline-block;width: 10px;height: 10px;margin: 0 5px;border-radius: 100%;background: rgba(255,255,255,0.8);cursor: pointer;transition: .5s;}
span:hover{background:nth($cg, 1);}
.no-preview-act{background:nth($cg, 3);}
.btn-group{height: 100%;}
.btn-img{display: inline-block;position: relative;width: 80px;height: 100%;margin: 0 5px;transition: .4s;cursor: pointer;
img {position: relative;width: 100%;height: 100%;object-fit: cover;transition: .4s;z-index: 2;}
.preview-act{transform: scale(.95)}
}
.btn-img:after {content: '';position: absolute;left: 0;top: 0;width: 100%;height: 100%;opacity: 0;transition: .8s;z-index: 1;}
.btn-img.act:after {opacity: 1;}
}
.slider-preview-btn-vertical{right: 10px;bottom: 50%;width: 20px;height: auto;text-align: center;transform: translate(0, 50%);}
.skin-0 .title-class{color: #e0e0e0}
.skin-1 .title-class{color: nth($cr, 3)}
.skin-2 .title-class{color: nth($cg, 3)}
.skin-3 .title-class{color: nth($cb, 3)}
.skin-0 .slider-btn{color: rgba(255,255,255,.6)}
.skin-1 .slider-btn{color: nth($cr, 3)}
.skin-2 .slider-btn{color: nth($cg, 3)}
.skin-3 .slider-btn{color: nth($cb, 3)}
.skin-0 .slider-btn:hover{color: #e0e0e0}
.skin-1 .slider-btn:hover{color: nth($cr, 1)}
.skin-2 .slider-btn:hover{color: nth($cg, 1)}
.skin-3 .slider-btn:hover{color: nth($cb, 1)}
.skin-0 .btn-img:after{background: rgba(0,0,0,0.5)}
.skin-1 .btn-img:after{background: nth($cr, 1)}
.skin-2 .btn-img:after{background: nth($cg, 1)}
.skin-3 .btn-img:after{background: nth($cb, 1)}
.skin-0 .default-elm{background: #e0e0e0}
.skin-1 .default-elm{background: nth($cr, 1)}
.skin-2 .default-elm{background: nth($cg, 1)}
.skin-3 .default-elm{background: nth($cb, 1)}
.skin-0 .default-btn{background: #e0e0e0}
.skin-1 .default-btn{background: nth($cr, 1)}
.skin-2 .default-btn{background: nth($cg, 1)}
.skin-3 .default-btn{background: nth($cb, 1)}
.skin-0 .no-preview-act{background: rgba(0,0,0,.6)}
.skin-1 .no-preview-act{background: nth($cr, 3)}
.skin-2 .no-preview-act{background: nth($cg, 3)}
.skin-3 .no-preview-act{background: nth($cb, 3)}
.skin-0 .slider-preview-btn span:hover{background: rgba(0,0,0,.6)}
.skin-1 .slider-preview-btn span:hover{background: nth($cr, 3)}
.skin-2 .slider-preview-btn span:hover{background: nth($cg, 3)}
.skin-3 .slider-preview-btn span:hover{background: nth($cb, 3)}
.h-83{height: 83%;}
.h-100{height: 100%;}
}
@keyframes right-to-left{
49% {-webkit-transform: translate(100%)}
50% {-webkit-transform: translate(-100%);opacity: 0;}
100% {opacity: 1;}
}
@keyframes left-to-right{
49% {-webkit-transform: translate(-100%)}
50% {-webkit-transform: translate(100%);opacity: 0;}
100% {opacity: 1;}
}

View File

@ -1 +0,0 @@
.do-tree{overflow:hidden;overflow-y:auto;position:relative;display:block;width:100%;height:100%;line-height:30px;font-size:15px;color:#526273}.do-tree__item{overflow:hidden;min-height:30px}.do-tree__item .sub-tree{display:none;width:100%;padding-left:20px}.do-tree__item em,.do-tree__item span{display:block;cursor:pointer}.do-tree__item em{float:left;padding:0 5px;font-size:20px;color:#62778d}.do-tree__item span:hover{color:#62778d}.do-tree__item span.active{color:#425064;font-weight:bold}.do-tree__item span.checkbox{float:left;position:relative;width:18px;height:18px;margin:6px 5px 6px 0;line-height:16px;border:1px solid #526273;border-radius:3px;font-size:16px;text-align:center}.do-tree__item span.label{white-space:nowrap;text-overflow:ellipsis}.do-tree__item.open>.sub-tree{display:block}

View File

@ -1,31 +0,0 @@
@charset "UTF-8";
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2017-04-14 21:18:53
*
*/
@import 'var.scss';
.do-tree {overflow:hidden;overflow-y:auto;position:relative;display:block;width:100%;height:100%;line-height:30px;font-size:15px;color:nth($cd, 2);
&__item {overflow:hidden; min-height:30px;
.sub-tree {display:none;width:100%;padding-left:20px;}
em,span {display:block;cursor:pointer;}
em {float:left;padding:0 5px;font-size:20px;color:nth($cd, 1);}
span {
&:hover {color:nth($cd, 1);}
&.active {color:nth($cd, 3);font-weight:bold;}
&.checkbox {float:left;position:relative;width:18px;height:18px;margin:6px 5px 6px 0;line-height:16px;border:1px solid nth($cd, 2);border-radius:3px; font-size:16px;text-align:center;}
&.label { white-space:nowrap; text-overflow:ellipsis;}
}
}
&__item.open>.sub-tree {display:block;}
}

View File

@ -1,13 +0,0 @@
$ct: #4db6ac #26a69a #009688;
$cg: #81c784 #66bb6a #4caf50;
$cpp: #ba68c8 #ba68c8 #9c27b0;
$cb: #64b5f6 #42a5f5 #2196f3;
$cr: #ff5061 #eb3b48 #ce3742;
$co: #ffb618 #f39c12 #e67e22;
$cp: #f2f5fc #e8ebf4 #dae1e9;
$cgr: #bdbdbd #9e9e9e #757575;
$cd: #62778d #526273 #425064;
@mixin ts($c: all, $t: .2s, $m: ease-in-out){
transition:$c $t $m;
}

View File

@ -4,8 +4,6 @@
* @date 2019/08/23 19:41:21 * @date 2019/08/23 19:41:21
*/ */
'use strict'
import $ from '../utils' import $ from '../utils'
const DEF_OPT = { const DEF_OPT = {

View File

@ -5,8 +5,6 @@
* *
*/ */
'use strict'
import Drag from './core' import Drag from './core'
Anot.directive('drag', { Anot.directive('drag', {

View File

@ -14,7 +14,7 @@
border-radius: 2px; border-radius: 2px;
user-select: none; user-select: none;
-moz-user-select: none; -moz-user-select: none;
color: nth($cd, 2); color: var(--color-dark-2);
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
@ -27,7 +27,7 @@
padding: 0 10px; padding: 0 10px;
margin: auto; margin: auto;
line-height: 0; line-height: 0;
border: 1px solid nth($cp, 3); border: 1px solid var(--color-plain-3);
border-radius: inherit; border-radius: inherit;
white-space: nowrap; white-space: nowrap;
background: #fff; background: #fff;
@ -38,11 +38,11 @@
cursor: inherit; cursor: inherit;
&:hover { &:hover {
background: nth($cp, 1); background: var(--color-plain-1);
} }
&:active { &:active {
border-color: nth($cgr, 1); border-color: var(--color-grey-1);
} }
&::-moz-focus-inner { &::-moz-focus-inner {
@ -133,15 +133,15 @@
:host([loading]), :host([loading]),
:host([disabled]) { :host([disabled]) {
cursor: not-allowed; cursor: not-allowed;
color: nth($cgr, 1); color: var(--color-grey-1);
opacity: 0.6; opacity: 0.6;
.icon { .icon {
color: nth($cgr, 1); color: var(--color-grey-1);
} }
button { button {
background: #fff; background: #fff;
border-color: nth($cp, 3); border-color: var(--color-plain-3);
} }
} }
@ -156,198 +156,198 @@
} }
:host([color='red']) button { :host([color='red']) button {
background: nth($cr, 2); background: var(--color-red-2);
&:hover { &:hover {
background: nth($cr, 1); background: var(--color-red-1);
} }
&:active { &:active {
background: nth($cr, 3); background: var(--color-red-3);
} }
} }
:host([color='red'][text]) button { :host([color='red'][text]) button {
background: transparent; background: transparent;
color: nth($cr, 2); color: var(--color-red-2);
&:hover { &:hover {
color: nth($cr, 1); color: var(--color-red-1);
} }
&:active { &:active {
color: nth($cr, 3); color: var(--color-red-3);
} }
} }
:host([color='red'][loading]) button, :host([color='red'][loading]) button,
:host([color='red'][disabled]) button { :host([color='red'][disabled]) button {
background: nth($cr, 1); background: var(--color-red-1);
} }
:host([color='blue']) button { :host([color='blue']) button {
background: nth($cb, 2); background: var(--color-blue-2);
&:hover { &:hover {
background: nth($cb, 1); background: var(--color-blue-1);
} }
&:active { &:active {
background: nth($cb, 3); background: var(--color-blue-3);
} }
} }
:host([color='blue'][text]) button { :host([color='blue'][text]) button {
background: transparent; background: transparent;
color: nth($cb, 2); color: var(--color-blue-2);
&:hover { &:hover {
color: nth($cb, 1); color: var(--color-blue-1);
} }
&:active { &:active {
color: nth($cb, 3); color: var(--color-blue-3);
} }
} }
:host([color='blue'][loading]) button, :host([color='blue'][loading]) button,
:host([color='blue'][disabled]) button { :host([color='blue'][disabled]) button {
background: nth($cb, 1); background: var(--color-blue-1);
} }
:host([color='green']) button { :host([color='green']) button {
background: nth($cg, 2); background: var(--color-green-2);
&:hover { &:hover {
background: nth($cg, 1); background: var(--color-green-1);
} }
&:active { &:active {
background: nth($cg, 3); background: var(--color-green-3);
} }
} }
:host([color='green'][text]) button { :host([color='green'][text]) button {
background: transparent; background: transparent;
color: nth($cg, 2); color: var(--color-green-2);
&:hover { &:hover {
color: nth($cg, 1); color: var(--color-green-1);
} }
&:active { &:active {
color: nth($cg, 3); color: var(--color-green-3);
} }
} }
:host([color='green'][loading]) button, :host([color='green'][loading]) button,
:host([color='green'][disabled]) button { :host([color='green'][disabled]) button {
background: nth($cg, 1); background: var(--color-green-1);
} }
:host([color='teal']) button { :host([color='teal']) button {
background: nth($ct, 2); background: var(--color-teal-2);
&:hover { &:hover {
background: nth($ct, 1); background: var(--color-teal-1);
} }
&:active { &:active {
background: nth($ct, 3); background: var(--color-teal-3);
} }
} }
:host([color='teal'][text]) button { :host([color='teal'][text]) button {
background: transparent; background: transparent;
color: nth($ct, 2); color: var(--color-teal-2);
&:hover { &:hover {
color: nth($ct, 1); color: var(--color-teal-1);
} }
&:active { &:active {
color: nth($ct, 3); color: var(--color-teal-3);
} }
} }
:host([color='teal'][loading]) button, :host([color='teal'][loading]) button,
:host([color='teal'][disabled]) button { :host([color='teal'][disabled]) button {
background: nth($ct, 1); background: var(--color-teal-1);
} }
:host([color='orange']) button { :host([color='orange']) button {
background: nth($co, 2); background: var(--color-orange-2);
&:hover { &:hover {
background: nth($co, 1); background: var(--color-orange-1);
} }
&:active { &:active {
background: nth($co, 3); background: var(--color-orange-3);
} }
} }
:host([color='orange'][text]) button { :host([color='orange'][text]) button {
background: transparent; background: transparent;
color: nth($co, 2); color: var(--color-orange-2);
&:hover { &:hover {
color: nth($co, 1); color: var(--color-orange-1);
} }
&:active { &:active {
color: nth($co, 3); color: var(--color-orange-3);
} }
} }
:host([color='orange'][loading]) button, :host([color='orange'][loading]) button,
:host([color='orange'][disabled]) button { :host([color='orange'][disabled]) button {
background: nth($co, 1); background: var(--color-orange-1);
} }
:host([color='dark']) button { :host([color='dark']) button {
background: nth($cd, 2); background: var(--color-dark-2);
&:hover { &:hover {
background: nth($cd, 1); background: var(--color-dark-1);
} }
&:active { &:active {
background: nth($cd, 3); background: var(--color-dark-3);
} }
} }
:host([color='dark'][text]) button { :host([color='dark'][text]) button {
background: transparent; background: transparent;
color: nth($cd, 2); color: var(--color-dark-2);
&:hover { &:hover {
color: nth($cd, 1); color: var(--color-dark-1);
} }
&:active { &:active {
color: nth($cd, 3); color: var(--color-dark-3);
} }
} }
:host([color='dark'][loading]) button, :host([color='dark'][loading]) button,
:host([color='dark'][disabled]) button { :host([color='dark'][disabled]) button {
background: nth($cd, 1); background: var(--color-dark-1);
} }
:host([color='purple']) button { :host([color='purple']) button {
background: nth($cpp, 2); background: var(--color-purple-2);
&:hover { &:hover {
background: nth($cpp, 1); background: var(--color-purple-1);
} }
&:active { &:active {
background: nth($cpp, 3); background: var(--color-purple-3);
} }
} }
:host([color='purple'][text]) button { :host([color='purple'][text]) button {
background: transparent; background: transparent;
color: nth($cpp, 2); color: var(--color-purple-2);
&:hover { &:hover {
color: nth($cpp, 1); color: var(--color-purple-1);
} }
&:active { &:active {
color: nth($cpp, 3); color: var(--color-purple-3);
} }
} }
:host([color='purple'][loading]) button, :host([color='purple'][loading]) button,
:host([color='purple'][disabled]) button { :host([color='purple'][disabled]) button {
background: nth($cpp, 1); background: var(--color-purple-1);
} }
:host([color='grey']) button { :host([color='grey']) button {
background: nth($cgr, 2); background: var(--color-grey-2);
&:hover { &:hover {
background: nth($cgr, 1); background: var(--color-grey-1);
} }
&:active { &:active {
background: nth($cgr, 3); background: var(--color-grey-3);
} }
} }
:host([color='grey'][text]) button { :host([color='grey'][text]) button {
background: transparent; background: transparent;
color: nth($cgr, 2); color: var(--color-grey-2);
&:hover { &:hover {
color: nth($cgr, 1); color: var(--color-grey-1);
} }
&:active { &:active {
color: nth($cgr, 3); color: var(--color-grey-3);
} }
} }
:host([color='grey'][loading]) button, :host([color='grey'][loading]) button,
:host([color='grey'][disabled]) button { :host([color='grey'][disabled]) button {
background: nth($cgr, 1); background: var(--color-grey-1);
} }
:host([no-border]) { :host([no-border]) {

283
src/form/checkbox-item.wc Normal file
View File

@ -0,0 +1,283 @@
<template>
<label>
<wc-icon class="dot" is="checkbox-off"></wc-icon>
<slot />
</label>
</template>
<style lang="scss">
:host {
display: inline-flex;
line-height: 1;
font-size: 14px;
label {
display: flex;
justify-content: center;
align-items: center;
min-width: 32px;
height: 32px;
padding: 0 5px;
line-height: 0;
-moz-user-select: none;
user-select: none;
white-space: nowrap;
cursor: inherit;
color: var(--color-grey-3);
}
.dot {
--size: 18px;
padding: 2px;
margin-right: 3px;
}
}
:host([readonly]) {
opacity: 0.8;
}
:host([disabled]) {
cursor: not-allowed;
opacity: 0.6;
}
:host([size='large']) {
font-size: 16px;
label {
height: 42px;
}
.dot {
--size: 22px;
}
}
:host([size='medium']) {
label {
height: 38px;
}
.dot {
--size: 20px;
}
}
:host([size='mini']) {
font-size: 12px;
label {
height: 20px;
}
.dot {
--size: 14px;
}
}
:host([color='red']) label.checked {
color: var(--color-red-1);
.dot {
border-color: var(--color-red-1);
}
.dot::after {
background: var(--color-red-1);
}
}
:host([color='blue']) label.checked {
color: var(--color-blue-1);
.dot {
border-color: var(--color-blue-1);
}
.dot::after {
background: var(--color-blue-1);
}
}
:host([color='green']) label.checked {
color: var(--color-green-1);
.dot {
border-color: var(--color-green-1);
}
.dot::after {
background: var(--color-green-1);
}
}
:host([color='teal']) label.checked {
color: var(--color-teal-1);
.dot {
border-color: var(--color-teal-1);
}
.dot::after {
background: var(--color-teal-1);
}
}
:host([color='orange']) label.checked {
color: var(--color-orange-1);
.dot {
border-color: var(--color-orange-1);
}
.dot::after {
background: var(--color-orange-1);
}
}
:host([color='dark']) label.checked {
color: var(--color-dark-1);
.dot {
border-color: var(--color-dark-1);
}
.dot::after {
background: var(--color-dark-1);
}
}
:host([color='purple']) label.checked {
color: var(--color-purple-1);
.dot {
border-color: var(--color-purple-1);
}
.dot::after {
background: var(--color-purple-1);
}
}
</style>
<script>
import '../icon/index'
import $ from '../utils'
export default class CheckboxItem {
props = {
color: '',
value: '',
checked: false,
readonly: false,
disabled: false
}
__init__() {
/* render */
this.__SWITCH__ = this.root.lastElementChild
this.__ICO__ = this.__SWITCH__.children[0]
this._isInGroup = false
}
_checkGroup() {
this._isInGroup = this.parentNode.tagName === 'WC-CHECKBOX'
if (this._isInGroup && this.parentNode.root) {
if (this.parentNode.value.includes(this.value)) {
this.checked = true
}
}
}
get value() {
return this.props.value
}
set value(val) {
this.props.value = val
}
get checked() {
return this.props.checked
}
set checked(val) {
this.props.checked = !!val
var { checked, color } = this.props
this.__SWITCH__.classList.toggle('checked', checked)
this.__ICO__.setAttribute('is', 'checkbox-' + (checked ? 'on' : 'off'))
if (checked) {
this.__ICO__.setAttribute('color', color)
} else {
this.__ICO__.removeAttribute('color')
}
}
get readOnly() {
return this.props.readonly
}
set readOnly(val) {
var type = typeof val
if (val === this.props.readonly) {
return
}
if ((type === 'boolean' && val) || type !== 'boolean') {
this.props.readonly = true
this.setAttribute('readonly', '')
} else {
this.props.readonly = false
this.removeAttribute('readonly')
}
}
get disabled() {
return this.props.disabled
}
set disabled(val) {
var type = typeof val
if (val === this.props.disabled) {
return
}
if ((type === 'boolean' && val) || type !== 'boolean') {
this.props.disabled = true
this.setAttribute('disabled', '')
} else {
this.props.disabled = false
this.removeAttribute('disabled')
}
}
mounted() {
this._checkGroup()
this._handlClick = $.bind(this, 'click', ev => {
ev.preventDefault()
if (this.disabled || this.readOnly) {
return
}
this.checked = !this.checked
if (this._isInGroup) {
this.parentNode.dispatchEvent(
new CustomEvent('child-picked', {
detail: { value: this.value, checked: this.checked }
})
)
} else {
this.dispatchEvent(new CustomEvent('input'))
}
})
}
unmount() {
$.unbind(this, 'click', this._handlClick)
}
watch() {
switch (name) {
case 'value':
case 'color':
this.props[name] = val
break
case 'checked':
case 'readonly':
case 'disabled':
var k = name
if (k === 'readonly') {
k = 'readOnly'
}
this[k] = true
break
}
}
}
</script>

View File

@ -1,163 +1,36 @@
<template> <template>
<label> <slot />
<wc-icon class="dot" is="checkbox-off"></wc-icon>
<slot></slot>
</label>
</template> </template>
<style lang="scss"> <style lang="scss">
:host { :host {
display: inline-block; display: inline-flex;
line-height: 1;
font-size: 14px;
label {
display: flex;
justify-content: center;
align-items: center;
min-width: 32px;
height: 32px;
padding: 0 5px;
line-height: 0;
-moz-user-select: none;
user-select: none;
white-space: nowrap;
cursor: inherit;
color: nth($cgr, 3);
}
.dot {
--size: 18px;
padding: 2px;
margin-right: 3px;
}
}
:host([readonly]) {
opacity: 0.8;
}
:host([disabled]) {
cursor: not-allowed;
opacity: 0.6;
}
:host([size='large']) {
font-size: 16px;
label {
height: 42px;
}
.dot {
--size: 22px;
}
}
:host([size='medium']) {
label {
height: 38px;
}
.dot {
--size: 20px;
}
}
:host([size='mini']) {
font-size: 12px;
label {
height: 20px;
}
.dot {
--size: 14px;
}
}
:host([color='red']) label.checked {
color: nth($cr, 1);
.dot {
border-color: nth($cr, 1);
}
.dot::after {
background: nth($cr, 1);
}
}
:host([color='blue']) label.checked {
color: nth($cb, 1);
.dot {
border-color: nth($cb, 1);
}
.dot::after {
background: nth($cb, 1);
}
}
:host([color='green']) label.checked {
color: nth($cg, 1);
.dot {
border-color: nth($cg, 1);
}
.dot::after {
background: nth($cg, 1);
}
}
:host([color='teal']) label.checked {
color: nth($ct, 1);
.dot {
border-color: nth($ct, 1);
}
.dot::after {
background: nth($ct, 1);
}
}
:host([color='orange']) label.checked {
color: nth($co, 1);
.dot {
border-color: nth($co, 1);
}
.dot::after {
background: nth($co, 1);
}
}
:host([color='dark']) label.checked {
color: nth($cd, 1);
.dot {
border-color: nth($cd, 1);
}
.dot::after {
background: nth($cd, 1);
}
}
:host([color='purple']) label.checked {
color: nth($cpp, 1);
.dot {
border-color: nth($cpp, 1);
}
.dot::after {
background: nth($cpp, 1);
}
} }
</style> </style>
<script> <script>
import '../icon/index'
import $ from '../utils' import $ from '../utils'
import './checkbox-item'
export default class Checkbox { export default class Checkbox {
props = { props = {
label: '', value: []
color: '',
value: [],
checked: false,
readonly: false,
disabled: false
} }
__init__() { __init__() {
/* render */ /* render */
}
this.__SWITCH__ = this.root.lastElementChild _updateChildrenStat() {
this.__ICO__ = this.__SWITCH__.children[0] Array.from(this.children).forEach(it => {
if (it.tagName === 'WC-CHECKBOX-ITEM' && it.root) {
if (this.value.includes(it.value)) {
it.checked = true
} else {
it.checked = false
}
}
})
} }
get value() { get value() {
@ -165,106 +38,41 @@ export default class Checkbox {
} }
set value(val) { set value(val) {
if (Array.isArray(val)) { if (val === this.props.value) {
this.props.value = val
this.checked = this.props.value.includes(this.props.label)
} else {
console.error('checkbox组件的value必须是数组, 当前为: ' + typeof val)
}
}
get checked() {
return this.props.checked
}
set checked(val) {
this.props.checked = !!val
var { value, checked, label, color } = this.props
this.__SWITCH__.classList.toggle('checked', checked)
this.__ICO__.setAttribute('is', 'checkbox-' + (checked ? 'on' : 'off'))
var idx = value.indexOf(label)
if (checked) {
this.__ICO__.setAttribute('color', color)
if (idx < 0) {
value.push(label)
}
} else {
this.__ICO__.removeAttribute('color')
if (~idx) {
value.splice(idx, 1)
}
}
}
get readOnly() {
return this.props.readonly
}
set readOnly(val) {
var type = typeof val
if (val === this.props.readonly) {
return return
} }
if ((type === 'boolean' && val) || type !== 'boolean') { this.props.value = val
this.props.readonly = true this._updateChildrenStat()
this.setAttribute('readonly', '')
} else {
this.props.readonly = false
this.removeAttribute('readonly')
}
}
get disabled() {
return this.props.disabled
}
set disabled(val) {
var type = typeof val
if (val === this.props.disabled) {
return
}
if ((type === 'boolean' && val) || type !== 'boolean') {
this.props.disabled = true
this.setAttribute('disabled', '')
} else {
this.props.disabled = false
this.removeAttribute('disabled')
}
} }
mounted() { mounted() {
this._handlClick = $.bind(this, 'click', ev => { this._pickedFn = $.bind(this, 'child-picked', ev => {
ev.preventDefault() var tmp = [...this.props.value]
var idx = tmp.indexOf(ev.detail.value)
if (!this.disabled && !this.readOnly) { if (ev.detail.checked) {
this.checked = !this.checked if (idx < 0) {
this.dispatchEvent(new CustomEvent('input')) tmp.push(ev.detail.value)
}
} else {
if (~idx) {
tmp.splice(idx, 1)
}
} }
this.props.value = tmp
this.dispatchEvent(new CustomEvent('input'))
}) })
} }
unmount() { unmount() {
$.unbind(this, 'click', this._handlClick) $.unbind(this, 'child-picked', this._pickedFn)
} }
watch() { watch() {
switch (name) { switch (name) {
case 'label': case 'value':
case 'color': if (val) {
this.props[name] = val this.value = val.split(/,\s*?/)
break
case 'checked':
case 'readonly':
case 'disabled':
var k = name
if (k === 'readonly') {
k = 'readOnly'
} }
this[k] = true
break break
} }
} }

View File

@ -23,7 +23,7 @@ li {
display: inline-block; display: inline-block;
user-select: none; user-select: none;
-moz-user-select: none; -moz-user-select: none;
color: nth($cd, 2); color: var(--color-dark-1);
border-radius: 2px; border-radius: 2px;
cursor: text; cursor: text;
} }
@ -36,9 +36,9 @@ li {
width: 100%; width: 100%;
height: 32px; height: 32px;
font-size: 14px; font-size: 14px;
border: 1px solid nth($cp, 3); border: 1px solid var(--color-plain-3);
border-radius: inherit; border-radius: inherit;
background: #fff; background: var(--bg-color, #fff);
color: inherit; color: inherit;
cursor: inherit; cursor: inherit;
@ -59,7 +59,7 @@ li {
cursor: inherit; cursor: inherit;
&::placeholder { &::placeholder {
color: nth($cgr, 1); color: var(--color-grey-1);
} }
} }
textarea { textarea {
@ -75,15 +75,15 @@ li {
height: 30px; height: 30px;
padding: 0 10px; padding: 0 10px;
line-height: 0; line-height: 0;
background: nth($cp, 1); background: var(--bg-color, --color-plain-1);
white-space: nowrap; white-space: nowrap;
} }
.prepend { .prepend {
border-right: 1px solid nth($cp, 3); border-right: 1px solid var(--color-plain-3);
border-radius: 2px 0 0 2px; border-radius: 2px 0 0 2px;
} }
.append { .append {
border-left: 1px solid nth($cp, 3); border-left: 1px solid var(--color-plain-3);
border-radius: 0 2px 2px 0; border-radius: 0 2px 2px 0;
} }
&[prepend] .prepend, &[prepend] .prepend,
@ -99,7 +99,7 @@ li {
--size: 20px; --size: 20px;
padding: 0 5px; padding: 0 5px;
margin: 0 5px; margin: 0 5px;
color: nth($cgr, 2); color: var(--color-grey-2);
} }
} }
@ -149,7 +149,7 @@ li {
&:hover, &:hover,
&[focus] { &[focus] {
background: nth($cp, 1); background: var(--color-plain-1);
} }
} }
} }
@ -162,7 +162,7 @@ li {
cursor: not-allowed; cursor: not-allowed;
.label { .label {
background: nth($cp, 1); background: var(--color-plain-1);
opacity: 0.6; opacity: 0.6;
} }
} }
@ -172,7 +172,7 @@ li {
:host(:focus-within) { :host(:focus-within) {
@include focus1; @include focus1;
.label { .label {
border-color: nth($cp, 3); border-color: var(--color-plain-3);
} }
} }
:host(:focus-within[readonly]) { :host(:focus-within[readonly]) {
@ -277,20 +277,26 @@ export default class Input {
value: '', value: '',
icon: '', icon: '',
type: 'text', type: 'text',
label: '',
placeholder: '', placeholder: '',
mvidx: null, //下拉列表光标的索引ID maxlength: null,
minlength: null,
autofocus: false, autofocus: false,
readonly: false, readonly: false,
disabled: false disabled: false
} }
state = {
mvidx: null //下拉列表光标的索引ID
}
__init__() { __init__() {
var type = this.getAttribute('type') var type = this.getAttribute('type')
var input = '' var input = ''
if (type !== 'textarea') { if (type !== 'textarea') {
type = 'text' type = 'text'
} }
input = INPUTS[type] input = INPUTS[type]
/* render */ /* render */
@ -375,18 +381,18 @@ export default class Input {
var items = Array.from( var items = Array.from(
this.__LIST__.firstElementChild.firstElementChild.children this.__LIST__.firstElementChild.firstElementChild.children
) )
if (this.props.mvidx === null) { if (this.state.mvidx === null) {
this.props.mvidx = 0 this.state.mvidx = 0
} else { } else {
this.props.mvidx += step this.state.mvidx += step
} }
if (this.props.mvidx < 0) { if (this.state.mvidx < 0) {
this.props.mvidx = 0 this.state.mvidx = 0
} else if (this.props.mvidx > items.length - 1) { } else if (this.state.mvidx > items.length - 1) {
this.props.mvidx = items.length - 1 this.state.mvidx = items.length - 1
} }
items.forEach((it, i) => { items.forEach((it, i) => {
if (i === this.props.mvidx) { if (i === this.state.mvidx) {
this.__LIST__.firstElementChild.scrollTop = it.offsetTop - 150 this.__LIST__.firstElementChild.scrollTop = it.offsetTop - 150
it.setAttribute('focus', '') it.setAttribute('focus', '')
} else { } else {
@ -407,12 +413,28 @@ export default class Input {
) )
this._handleChange(ev) this._handleChange(ev)
this.__LIST__.classList.remove('show') this.__LIST__.classList.remove('show')
this.props.mvidx = null this.state.mvidx = null
}
_updateAttr() {
var { maxlength, minlength } = this.props
if (maxlength && maxlength > 0) {
this.__INPUT__.setAttribute('maxlength', maxlength)
} else {
this.__INPUT__.removeAttribute('maxlength')
}
if (minlength && minlength > 0) {
this.__INPUT__.setAttribute('minlength', minlength)
} else {
this.__INPUT__.removeAttribute('minlength')
}
} }
mounted() { mounted() {
var prepend = this.__PREPEND__.assignedNodes() var prepend = this.__PREPEND__.assignedNodes()
var append = this.__APPEND__.assignedNodes() var append = this.__APPEND__.assignedNodes()
var { type } = this.props
// 相同插槽, 只允许1个 // 相同插槽, 只允许1个
while (prepend.length > 1) { while (prepend.length > 1) {
@ -422,14 +444,14 @@ export default class Input {
this.removeChild(append.pop()) this.removeChild(append.pop())
} }
if (prepend.length && this.props.type !== 'textarea') { if (prepend.length && type !== 'textarea') {
this.__OUTER__.setAttribute('prepend', '') this.__OUTER__.setAttribute('prepend', '')
} }
if (append.length && this.props.type !== 'textarea') { if (append.length && type !== 'textarea') {
this.__OUTER__.setAttribute('append', '') this.__OUTER__.setAttribute('append', '')
} }
var { type } = this.props this._updateAttr()
// 键盘事件 // 键盘事件
this._handleSubmit = $.catch(this.__INPUT__, 'keydown', ev => { this._handleSubmit = $.catch(this.__INPUT__, 'keydown', ev => {
@ -439,7 +461,7 @@ export default class Input {
// up: 38, down: 40 // up: 38, down: 40
if (ev.keyCode === 38 || ev.keyCode === 40) { if (ev.keyCode === 38 || ev.keyCode === 40) {
// 仅普通文本表单, 密码和多行文本框不做响应 // 仅普通文本表单, 密码和多行文本框不做响应
if (this.type === 'text') { if (type === 'text') {
return this._moveSelect(ev) return this._moveSelect(ev)
} }
} }
@ -447,8 +469,8 @@ export default class Input {
// textarea 要按Ctrl Or Cmd键, 才会触发 // textarea 要按Ctrl Or Cmd键, 才会触发
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
// 如果是输入建议存在,则第1次回车的时候, 不触发提交 // 如果是输入建议存在,则第1次回车的时候, 不触发提交
if (this.type === 'text' && this.props.mvidx !== null) { if (type === 'text' && this.state.mvidx !== null) {
return this._fetchSelect(this.props.mvidx, ev) return this._fetchSelect(this.state.mvidx, ev)
} }
if ( if (
@ -544,8 +566,6 @@ export default class Input {
break break
// label和placeholder 功能相同
case 'label':
case 'placeholder': case 'placeholder':
this.__INPUT__.setAttribute('placeholder', val) this.__INPUT__.setAttribute('placeholder', val)
break break
@ -562,6 +582,12 @@ export default class Input {
this.value = val this.value = val
break break
case 'maxlength':
case 'minlength':
this.props[name] = val
this._updateAttr()
break
case 'readonly': case 'readonly':
case 'disabled': case 'disabled':
var k = name var k = name

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="label"> <div class="label">
<span data-act="-">-</span> <span data-act="-">-</span>
<!-- <wc-icon class="icon" icon="minus"></wc-icon> --> <!-- <wc-icon class="icon" is="minus"></wc-icon> -->
<input value="0" maxlength="9" /> <input value="0" maxlength="9" />
<span data-act="+">+</span> <span data-act="+">+</span>
<!-- <wc-icon class="icon" icon="plus"></wc-icon> --> <!-- <wc-icon class="icon" is="plus"></wc-icon> -->
</div> </div>
</template> </template>
@ -16,7 +16,7 @@
height: 32px; height: 32px;
user-select: none; user-select: none;
-moz-user-select: none; -moz-user-select: none;
color: nth($cd, 2); color: var(--color-dark-2);
border-radius: 2px; border-radius: 2px;
} }
@ -27,9 +27,9 @@
margin: 0 auto; margin: 0 auto;
line-height: 0; line-height: 0;
font-size: 14px; font-size: 14px;
border: 1px solid nth($cp, 3); border: 1px solid var(--color-plain-3);
border-radius: inherit; border-radius: inherit;
background: #fff; background: var(--bg-color, #fff);
color: inherit; color: inherit;
cursor: text; cursor: text;
@ -39,17 +39,17 @@
align-items: center; align-items: center;
width: 32px; width: 32px;
height: 100%; height: 100%;
background: nth($cp, 1); background: var(--bg-color, --color-plain-1);
font-size: 18px; font-size: 18px;
cursor: pointer; cursor: pointer;
&:first-child { &:first-child {
border-radius: 2px 0 0 2px; border-radius: 2px 0 0 2px;
border-right: 1px solid nth($cp, 3); border-right: 1px solid var(--color-plain-3);
} }
&:last-child { &:last-child {
border-radius: 0 2px 2px 0; border-radius: 0 2px 2px 0;
border-left: 1px solid nth($cp, 3); border-left: 1px solid var(--color-plain-3);
} }
&.disabled { &.disabled {
@ -76,7 +76,7 @@
cursor: inherit; cursor: inherit;
&::placeholder { &::placeholder {
color: nth($cgr, 1); color: var(--color-grey-1);
} }
} }
/* ----- */ /* ----- */
@ -96,7 +96,7 @@
} }
} }
:host([disabled]) .label { :host([disabled]) .label {
background: nth($cp, 1); background: var(--color-plain-1);
cursor: not-allowed; cursor: not-allowed;
opacity: 0.6; opacity: 0.6;

View File

@ -11,14 +11,14 @@
flex: 1; flex: 1;
height: var(--size, 10px); height: var(--size, 10px);
border-radius: 9px; border-radius: 9px;
background: nth($cp, 2); background: var(--color-plain-2);
span { span {
display: block; display: block;
width: 0; width: 0;
height: 100%; height: 100%;
border-radius: 9px; border-radius: 9px;
background: nth($ct, 1); background: var(--color-teal-1);
} }
} }
} }
@ -36,27 +36,27 @@
} }
:host([color='red']) label span { :host([color='red']) label span {
background: nth($cr, 1); background: var(--color-red-1);
} }
:host([color='blue']) label span { :host([color='blue']) label span {
background: nth($cb, 1); background: var(--color-blue-1);
} }
:host([color='green']) label span { :host([color='green']) label span {
background: nth($cg, 1); background: var(--color-green-1);
} }
:host([color='orange']) label span { :host([color='orange']) label span {
background: nth($co, 1); background: var(--color-orange-1);
} }
:host([color='dark']) label span { :host([color='dark']) label span {
background: nth($cd, 1); background: var(--color-dark-1);
} }
:host([color='purple']) label span { :host([color='purple']) label span {
background: nth($cpp, 1); background: var(--color-purple-1);
} }
</style> </style>

290
src/form/radio-item.wc Normal file
View File

@ -0,0 +1,290 @@
<template>
<label>
<span class="dot"></span>
<slot />
</label>
</template>
<style lang="scss">
:host {
display: inline-flex;
line-height: 1;
font-size: 14px;
label {
display: flex;
justify-content: center;
align-items: center;
min-width: 32px;
height: 32px;
padding: 0 5px;
line-height: 1;
-moz-user-select: none;
user-select: none;
white-space: nowrap;
cursor: inherit;
color: var(--color-grey-3);
&.checked .dot::after {
visibility: visible;
}
}
.dot {
display: flex;
justify-content: center;
align-items: center;
width: 18px;
height: 18px;
margin-right: 3px;
border: 1px solid var(--color-grey-1);
border-radius: 50%;
background: #fff;
&::after {
display: block;
visibility: hidden;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--color-grey-1);
content: '';
}
}
}
:host([readonly]) {
opacity: 0.8;
}
:host([disabled]) {
cursor: not-allowed;
opacity: 0.6;
}
:host([size='large']) {
label {
min-width: 58px;
height: 32px;
}
.dot {
width: 26px;
height: 26px;
&::after {
width: 18px;
height: 18px;
}
}
}
:host([size='medium']) {
label {
min-width: 50px;
height: 28px;
}
.dot {
width: 22px;
height: 22px;
&::after {
width: 14px;
height: 14px;
}
}
}
:host([size='mini']) {
label {
height: 14px;
}
.dot {
width: 14px;
height: 14px;
&::after {
width: 8px;
height: 8px;
}
}
}
:host([color='red']) label.checked {
color: var(--color-red-1);
.dot {
border-color: var(--color-red-1);
}
.dot::after {
background: var(--color-red-1);
}
}
:host([color='blue']) label.checked {
color: var(--color-blue-1);
.dot {
border-color: var(--color-blue-1);
}
.dot::after {
background: var(--color-blue-1);
}
}
:host([color='green']) label.checked {
color: var(--color-green-1);
.dot {
border-color: var(--color-green-1);
}
.dot::after {
background: var(--color-green-1);
}
}
:host([color='teal']) label.checked {
color: var(--color-teal-1);
.dot {
border-color: var(--color-teal-1);
}
.dot::after {
background: var(--color-teal-1);
}
}
:host([color='orange']) label.checked {
color: var(--color-orange-1);
.dot {
border-color: var(--color-orange-1);
}
.dot::after {
background: var(--color-orange-1);
}
}
:host([color='dark']) label.checked {
color: var(--color-dark-1);
.dot {
border-color: var(--color-dark-1);
}
.dot::after {
background: var(--color-dark-1);
}
}
:host([color='purple']) label.checked {
color: var(--color-purple-1);
.dot {
border-color: var(--color-purple-1);
}
.dot::after {
background: var(--color-purple-1);
}
}
</style>
<script>
import $ from '../utils'
export default class RadioItem {
props = {
value: '',
checked: false,
readonly: false,
disabled: false
}
__init__() {
/* render */
this.__SWITCH__ = this.root.lastElementChild
}
get value() {
return this.props.value
}
set value(val) {
this.props.value = val
}
get checked() {
return this.props.checked
}
set checked(val) {
this.props.checked = !!val
this.__SWITCH__.classList.toggle('checked', this.props.checked)
}
get readOnly() {
return this.props.readonly
}
set readOnly(val) {
var type = typeof val
if (val === this.props.readonly) {
return
}
if ((type === 'boolean' && val) || type !== 'boolean') {
this.props.readonly = true
this.setAttribute('readonly', '')
} else {
this.props.readonly = false
this.removeAttribute('readonly')
}
}
get disabled() {
return this.props.disabled
}
set disabled(val) {
var type = typeof val
if (val === this.props.disabled) {
return
}
if ((type === 'boolean' && val) || type !== 'boolean') {
this.props.disabled = true
this.setAttribute('disabled', '')
} else {
this.props.disabled = false
this.removeAttribute('disabled')
}
}
mounted() {
if (this.value === this.parentNode.value) {
this.checked = true
}
this._handleClick = $.catch(this, 'click', ev => {
if (this.disabled || this.readOnly || this.checked) {
return
}
this.parentNode.dispatchEvent(
new CustomEvent('child-picked', { detail: this.value })
)
})
}
unmount() {
$.unbind(this, 'click', this._handleClick)
}
watch() {
switch (name) {
case 'value':
this.value = val
break
case 'checked':
case 'readonly':
case 'disabled':
var k = name
if (k === 'readonly') {
k = 'readOnly'
}
this[k] = true
break
}
}
}
</script>

View File

@ -1,282 +1,66 @@
<template> <template>
<label> <slot />
<span class="dot"></span>
<slot></slot>
</label>
</template> </template>
<style lang="scss"> <style lang="scss">
:host { :host {
display: inline-block; display: inline-flex;
line-height: 1;
font-size: 14px;
label {
display: flex;
justify-content: center;
align-items: center;
min-width: 32px;
height: 32px;
padding: 0 5px;
line-height: 0;
-moz-user-select: none;
user-select: none;
white-space: nowrap;
cursor: inherit;
color: nth($cgr, 3);
&.checked .dot::after {
content: '';
}
}
.dot {
display: flex;
justify-content: center;
align-items: center;
width: 18px;
height: 18px;
padding: 2px;
margin-right: 3px;
border: 1px solid nth($cgr, 1);
border-radius: 50%;
background: #fff;
&::after {
display: block;
width: 12px;
height: 12px;
border-radius: 50%;
background: nth($cgr, 1);
}
}
}
:host([readonly]) {
opacity: 0.8;
}
:host([disabled]) {
cursor: not-allowed;
opacity: 0.6;
}
:host([size='large']) {
label {
min-width: 58px;
height: 32px;
}
.dot {
width: 26px;
height: 26px;
&::after {
width: 18px;
height: 18px;
}
}
}
:host([size='medium']) {
label {
min-width: 50px;
height: 28px;
}
.dot {
width: 22px;
height: 22px;
&::after {
width: 14px;
height: 14px;
}
}
}
:host([size='mini']) {
label {
height: 14px;
}
.dot {
width: 14px;
height: 14px;
&::after {
width: 8px;
height: 8px;
}
}
}
:host([color='red']) label.checked {
color: nth($cr, 1);
.dot {
border-color: nth($cr, 1);
}
.dot::after {
background: nth($cr, 1);
}
}
:host([color='blue']) label.checked {
color: nth($cb, 1);
.dot {
border-color: nth($cb, 1);
}
.dot::after {
background: nth($cb, 1);
}
}
:host([color='green']) label.checked {
color: nth($cg, 1);
.dot {
border-color: nth($cg, 1);
}
.dot::after {
background: nth($cg, 1);
}
}
:host([color='teal']) label.checked {
color: nth($ct, 1);
.dot {
border-color: nth($ct, 1);
}
.dot::after {
background: nth($ct, 1);
}
}
:host([color='orange']) label.checked {
color: nth($co, 1);
.dot {
border-color: nth($co, 1);
}
.dot::after {
background: nth($co, 1);
}
}
:host([color='dark']) label.checked {
color: nth($cd, 1);
.dot {
border-color: nth($cd, 1);
}
.dot::after {
background: nth($cd, 1);
}
}
:host([color='purple']) label.checked {
color: nth($cpp, 1);
.dot {
border-color: nth($cpp, 1);
}
.dot::after {
background: nth($cpp, 1);
}
} }
</style> </style>
<script> <script>
import $ from '../utils' import $ from '../utils'
import './radio-item'
export default class Radio { export default class Radio {
props = { props = {
label: '', value: null
checked: false,
readonly: false,
disabled: false
} }
__init__() { __init__() {
/* render */ /* render */
}
this.__SWITCH__ = this.root.lastElementChild _updateChildrenStat() {
Array.from(this.children).forEach(it => {
if (it.tagName === 'WC-RADIO-ITEM' && it.root) {
if (it.value === this.props.value) {
it.checked = true
} else {
it.checked = false
}
}
})
} }
get value() { get value() {
return this.props.label return this.props.value
} }
set value(val) { set value(val) {
this.checked = this.props.label === val if (val === this.props.value) {
}
get checked() {
return this.props.checked
}
set checked(val) {
this.props.checked = !!val
this.__SWITCH__.classList.toggle('checked', this.props.checked)
}
get readOnly() {
return this.props.readonly
}
set readOnly(val) {
var type = typeof val
if (val === this.props.readonly) {
return return
} }
if ((type === 'boolean' && val) || type !== 'boolean') { this.props.value = val
this.props.readonly = true this._updateChildrenStat()
this.setAttribute('readonly', '')
} else {
this.props.readonly = false
this.removeAttribute('readonly')
}
}
get disabled() {
return this.props.disabled
}
set disabled(val) {
var type = typeof val
if (val === this.props.disabled) {
return
}
if ((type === 'boolean' && val) || type !== 'boolean') {
this.props.disabled = true
this.setAttribute('disabled', '')
} else {
this.props.disabled = false
this.removeAttribute('disabled')
}
} }
mounted() { mounted() {
this._handleClick = $.catch(this, 'click', ev => { this._pickedFn = $.bind(this, 'child-picked', ev => {
if (this.disabled || this.readOnly || this.checked) { log('radio picked: ', ev.detail)
return this.value = ev.detail
}
this.checked = true
this.dispatchEvent(new CustomEvent('input')) this.dispatchEvent(new CustomEvent('input'))
}) })
} }
unmount() { unmount() {
$.unbind(this, 'click', this._handleClick) $.unbind(this, 'child-picked', this._pickedFn)
} }
watch() { watch() {
switch (name) { switch (name) {
case 'label': case 'value':
this.props.label = val this.value = val
break
case 'checked':
case 'readonly':
case 'disabled':
var k = name
if (k === 'readonly') {
k = 'readOnly'
}
this[k] = true
break break
} }
} }

View File

@ -18,7 +18,7 @@
display: inline-block; display: inline-block;
user-select: none; user-select: none;
-moz-user-select: none; -moz-user-select: none;
color: nth($cd, 2); color: var(--color-dark-2);
border-radius: 2px; border-radius: 2px;
} }
.label { .label {
@ -30,7 +30,7 @@
height: 32px; height: 32px;
line-height: 0; line-height: 0;
font-size: 14px; font-size: 14px;
border: 1px solid nth($cp, 3); border: 1px solid var(--color-plain-3);
border-radius: inherit; border-radius: inherit;
background: #fff; background: #fff;
color: inherit; color: inherit;
@ -53,7 +53,7 @@
cursor: inherit; cursor: inherit;
&::placeholder { &::placeholder {
color: nth($cgr, 1); color: var(--color-grey-1);
} }
} }
@ -66,14 +66,14 @@
height: 30px; height: 30px;
padding: 0 10px; padding: 0 10px;
white-space: nowrap; white-space: nowrap;
background: nth($cp, 1); background: var(--color-plain-1);
} }
.prepend { .prepend {
border-right: 1px solid nth($cp, 3); border-right: 1px solid var(--color-plain-3);
border-radius: 2px 0 0 2px; border-radius: 2px 0 0 2px;
} }
.append { .append {
border-left: 1px solid nth($cp, 3); border-left: 1px solid var(--color-plain-3);
border-radius: 0 2px 2px 0; border-radius: 0 2px 2px 0;
} }
&[prepend] .prepend, &[prepend] .prepend,
@ -137,7 +137,7 @@
dt { dt {
font-size: 12px; font-size: 12px;
color: nth($cgr, 1); color: var(--color-grey-1);
} }
dd { dd {
@ -145,16 +145,16 @@
&:hover, &:hover,
&[focus] { &[focus] {
background: nth($cp, 1); background: var(--color-plain-1);
} }
&[focus] { &[focus] {
color: nth($ct, 1); color: var(--color-teal-1);
} }
&[sub] { &[sub] {
text-indent: 1em; text-indent: 1em;
} }
&[disabled] { &[disabled] {
color: nth($cgr, 1); color: var(--color-grey-1);
cursor: not-allowed; cursor: not-allowed;
background: none; background: none;
} }
@ -163,7 +163,7 @@
/* --- */ /* --- */
:host([disabled]) .label { :host([disabled]) .label {
background: nth($cp, 1); background: var(--color-plain-1);
cursor: not-allowed; cursor: not-allowed;
opacity: 0.6; opacity: 0.6;
} }

View File

@ -29,7 +29,7 @@ label {
wc-icon { wc-icon {
margin: 0 3px; margin: 0 3px;
@include ts(transform); transition: transform 0.1s easein-out;
&:hover { &:hover {
transform: scale(1.05); transform: scale(1.05);
@ -55,25 +55,25 @@ label {
} }
:host([color='red']) label span { :host([color='red']) label span {
color: nth($cr, 1); color: var(--color-red-1);
} }
:host([color='teal']) label span { :host([color='teal']) label span {
color: nth($ct, 1); color: var(--color-teal-1);
} }
:host([color='green']) label span { :host([color='green']) label span {
color: nth($cg, 1); color: var(--color-green-1);
} }
:host([color='grey']) label span { :host([color='grey']) label span {
color: nth($cgr, 1); color: var(--color-grey-1);
} }
:host([color='blue']) label span { :host([color='blue']) label span {
color: nth($cb, 1); color: var(--color-blue-1);
} }
:host([color='purple']) label span { :host([color='purple']) label span {
color: nth($cpp, 1); color: var(--color-purple-1);
} }
:host([color='orange']) label span { :host([color='orange']) label span {
color: nth($co, 1); color: var(--color-orange-1);
} }
:host([disabled]) { :host([disabled]) {

View File

@ -27,12 +27,12 @@
padding: 3px; padding: 3px;
margin: 5px; margin: 5px;
border-radius: 21px; border-radius: 21px;
background: nth($cp, 3); background: var(--color-plain-3);
cursor: inherit; cursor: inherit;
&.checked { &.checked {
flex-direction: row-reverse; flex-direction: row-reverse;
background: nth($cgr, 3); background: var(--color-grey-3);
} }
} }
.dot { .dot {
@ -83,31 +83,31 @@
} }
:host([color='red']) label.checked { :host([color='red']) label.checked {
background: nth($cr, 1); background: var(--color-red-1);
} }
:host([color='blue']) label.checked { :host([color='blue']) label.checked {
background: nth($cb, 1); background: var(--color-blue-1);
} }
:host([color='green']) label.checked { :host([color='green']) label.checked {
background: nth($cg, 1); background: var(--color-green-1);
} }
:host([color='teal']) label.checked { :host([color='teal']) label.checked {
background: nth($ct, 1); background: var(--color-teal-1);
} }
:host([color='orange']) label.checked { :host([color='orange']) label.checked {
background: nth($co, 1); background: var(--color-orange-1);
} }
:host([color='dark']) label.checked { :host([color='dark']) label.checked {
background: nth($cd, 1); background: var(--color-dark-1);
} }
:host([color='purple']) label.checked { :host([color='purple']) label.checked {
background: nth($cpp, 1); background: var(--color-purple-1);
} }
</style> </style>

View File

@ -40,35 +40,35 @@
height: 20px; height: 20px;
} }
:host([color='red']) { :host([color='red']) {
color: nth($cr, 1); color: var(--color-red-1);
} }
:host([color='blue']) { :host([color='blue']) {
color: nth($cb, 1); color: var(--color-blue-1);
} }
:host([color='green']) { :host([color='green']) {
color: nth($cg, 1); color: var(--color-green-1);
} }
:host([color='teal']) { :host([color='teal']) {
color: nth($ct, 1); color: var(--color-teal-1);
} }
:host([color='orange']) { :host([color='orange']) {
color: nth($co, 1); color: var(--color-orange-1);
} }
:host([color='dark']) { :host([color='dark']) {
color: nth($cd, 1); color: var(--color-dark-1);
} }
:host([color='purple']) { :host([color='purple']) {
color: nth($cpp, 1); color: var(--color-purple-1);
} }
:host([color='grey']) { :host([color='grey']) {
color: nth($cgr, 1); color: var(--color-grey-1);
} }
@keyframes circle { @keyframes circle {

305
src/keyboard/index.wc Normal file
View File

@ -0,0 +1,305 @@
<template>
<div class="keyboard">
<main>
<div class="row">
<span class="reverse">Esc</span>
<label>
<span>F1</span>
<span>F2</span>
<span>F3</span>
<span>F4</span>
</label>
<label>
<span class="reverse">F5</span>
<span class="reverse">F6</span>
<span class="reverse">F7</span>
<span class="reverse">F8</span>
</label>
<label>
<span>F9</span>
<span>F10</span>
<span>F11</span>
<span>F12</span>
</label>
</div>
<div class="row mt12">
<span>\`</span>
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
<span>5</span>
<span>6</span>
<span>7</span>
<span>8</span>
<span>9</span>
<span>0</span>
<span>-</span>
<span>=</span>
<span class="w20 reverse">Backspace</span>
</div>
<div class="row">
<span class="w15 reverse">Tab</span>
<span>Q</span>
<span>W</span>
<span>E</span>
<span>R</span>
<span>T</span>
<span>Y</span>
<span>U</span>
<span>I</span>
<span>O</span>
<span>P</span>
<span>[</span>
<span>]</span>
<span class="w15">\\</span>
</div>
<div class="row">
<span class="w20 reverse">Caps</span>
<span>A</span>
<span>S</span>
<span>D</span>
<span>F</span>
<span>G</span>
<span>H</span>
<span>J</span>
<span>K</span>
<span>L</span>
<span>;</span>
<span>'</span>
<span class="w25 reverse">Enter</span>
</div>
<div class="row">
<span class="w25 reverse">Shift</span>
<span>Z</span>
<span>X</span>
<span>C</span>
<span>V</span>
<span>B</span>
<span>N</span>
<span>M</span>
<span>,</span>
<span>.</span>
<span>/</span>
<span class="w30 reverse">Shift</span>
</div>
<div class="row">
<span class="w15 reverse">Ctrl</span>
<span class="reverse">Win</span>
<span class="w12 reverse">Alt</span>
<span class="w65">Space</span>
<span class="w12 reverse">Alt</span>
<span class="reverse">Win</span>
<span class="reverse">Menu</span>
<span class="w15 reverse">Ctrl</span>
</div>
</main>
<div class="tool">
<div class="row">
<span class="reverse">Prt</span>
<span class="reverse">Scr</span>
<span class="reverse">Pau</span>
</div>
<div class="row mt12">
<span class="reverse">Ins</span>
<span class="reverse">Home</span>
<span class="reverse">Pg▴</span>
</div>
<div class="row">
<span class="reverse">Del</span>
<span class="reverse">End</span>
<span class="reverse">Pg▾</span>
</div>
<div class="row">
<span class="null"></span>
<span class="null"></span>
<span class="null"></span>
</div>
<div class="row">
<span class="null"></span>
<span class="reverse">↑</span>
<span class="null"></span>
</div>
<div class="row">
<span class="reverse">←</span>
<span class="reverse">↓</span>
<span class="reverse">→</span>
</div>
</div>
<div class="numpad">
<div class="row">
<span class="null"></span>
</div>
<div class="row mt12">
<span class="reverse">Lock</span>
<span class="reverse">/</span>
<span class="reverse">*</span>
</div>
<div class="row">
<span>7</span>
<span>8</span>
<span>9</span>
</div>
<div class="row">
<span>4</span>
<span>5</span>
<span>6</span>
</div>
<div class="row">
<span>1</span>
<span>2</span>
<span>3</span>
</div>
<div class="row">
<span class="w22">0</span>
<span>.</span>
</div>
</div>
<div class="numpad-1">
<div class="row">
<span class="null"></span>
</div>
<div class="row mt12">
<span class="reverse">-</span>
</div>
<div class="row">
<span class="h20 reverse">+</span>
</div>
<div class="row">
<span class="h20 reverse">Ent</span>
</div>
</div>
</div>
</template>
<style lang="scss">
.keyboard {
display: flex;
justify-content: space-between;
width: 1198px;
padding: 6px 6px 12px;
background: #f7f8fb;
color: #4caf50;
font-size: 14px;
border-radius: 4px;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
user-select: none;
span {
width: 48px;
height: 48px;
margin: 0 3px;
line-height: 48px;
border-radius: 4px;
background: #fff;
text-align: center;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
cursor: pointer;
transition: box-shadow 0.05s ease-in, background 0.05s ease-in;
&.w12 {
width: 60px;
}
&.w15 {
width: 72px;
}
&.w20 {
width: 96px;
}
&.w22 {
width: 102px;
}
&.w25 {
width: 120px;
}
&.w30 {
width: 144px;
}
&.w65 {
width: 312px;
}
&.h20 {
height: 102px;
line-height: 102px;
}
&.reverse {
background: #4caf50;
color: #fff;
}
&.null {
background: transparent;
box-shadow: none;
cursor: default;
}
&:active {
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
background: #f7f8fb;
}
&.reverse:active {
background: #46a149;
}
}
main {
display: flex;
flex-direction: column;
width: 780px;
.row,
label {
display: flex;
justify-content: space-between;
}
.row {
margin: 6px 0 0;
&.mt12 {
margin-top: 12px;
}
}
}
.tool,
.numpad,
.numpad-1 {
display: flex;
flex-direction: column;
width: 160px;
margin: 0 16px;
.row {
display: flex;
justify-content: space-between;
margin: 6px 0 0;
&.mt12 {
margin-top: 12px;
}
}
}
.numpad,
.numpad-1 {
margin: 0;
}
.numpad-1 {
width: 54px;
}
}
</style>
<script>
export default class Keyboard {
props = {}
__init__() {
/* render */
}
}
</script>

View File

@ -73,13 +73,13 @@
height: 60px; height: 60px;
padding: 15px; padding: 15px;
font-size: 16px; font-size: 16px;
color: nth($cd, 2); color: var(--color-dark-2);
wc-icon { wc-icon {
--size: 14px; --size: 14px;
&:hover { &:hover {
color: nth($cr, 1); color: var(--color-red-1);
} }
} }
} }
@ -122,24 +122,24 @@
} }
::slotted(&__toast.style-info) { ::slotted(&__toast.style-info) {
border: 1px solid nth($cp, 3); border: 1px solid var(--color-plain-3);
background: nth($cp, 1); background: var(--color-plain-1);
color: nth($cgr, 3); color: var(--color-grey-3);
} }
::slotted(&__toast.style-success) { ::slotted(&__toast.style-success) {
border: 1px solid #b3e19d; border: 1px solid #b3e19d;
background: #f0f9eb; background: #f0f9eb;
color: nth($cg, 2); color: var(--color-green-2);
} }
::slotted(&__toast.style-warn) { ::slotted(&__toast.style-warn) {
border: 1px solid #faebb4; border: 1px solid #faebb4;
background: #fffbed; background: #fffbed;
color: nth($co, 3); color: var(--color-orange-3);
} }
::slotted(&__toast.style-error) { ::slotted(&__toast.style-error) {
border: 1px solid #f5c4c4; border: 1px solid #f5c4c4;
background: #fef0f0; background: #fef0f0;
color: nth($cr, 1); color: var(--color-red-1);
} }
} }
@ -159,7 +159,7 @@
height: 30px; height: 30px;
padding: 0 10px; padding: 0 10px;
margin: 0 5px; margin: 0 5px;
border: 1px solid nth($cp, 3); border: 1px solid var(--color-plain-3);
border-radius: 2px; border-radius: 2px;
white-space: nowrap; white-space: nowrap;
background: #fff; background: #fff;
@ -169,11 +169,11 @@
color: inherit; color: inherit;
&:hover { &:hover {
background: nth($cp, 1); background: var(--color-plain-1);
} }
&:active { &:active {
border-color: nth($cgr, 1); border-color: var(--color-grey-1);
} }
&:focus { &:focus {
@ -182,14 +182,14 @@
&:last-child { &:last-child {
color: #fff; color: #fff;
background: nth($ct, 2); background: var(--color-teal-2);
border-color: transparent; border-color: transparent;
&:hover { &:hover {
background: nth($ct, 1); background: var(--color-teal-1);
} }
&:active { &:active {
background: nth($ct, 3); background: var(--color-teal-3);
} }
} }
@ -275,7 +275,7 @@ function renderBtns(list) {
return html return html
} }
class Layer { export default class Layer {
props = { props = {
left: 'auto', left: 'auto',
right: 'auto', right: 'auto',
@ -789,6 +789,4 @@ Object.assign(_layer, {
}) })
window.layer = _layer window.layer = _layer
export default _layer
</script> </script>

View File

@ -4,37 +4,32 @@
* @date 2020/02/07 17:14:19 * @date 2020/02/07 17:14:19
*/ */
'use strict'
const HR_LIST = ['=', '-', '_', '*'] const HR_LIST = ['=', '-', '_', '*']
const LIST_REG = /^(([\+\-\*])|(\d+\.))\s/ const LIST_REG = /^(([\+\-\*])|(\d+\.))\s/
const TODO_REG = /^\-\s\[(x|\s)\]\s/ const TODO_REG = /^\-\s\[(x|\s)\]\s/
const ESCAPE_REG = /\\([-+*_`])/g const ESCAPE_REG = /\\([-+*_`])/g
const QLINK_REG = /^\[(\d+)\]: ([^\s]+)\s*?/ const QLINK_REG = /^\[(\d+)\]: ([\S]+)\s*?((['"])[\s\S]*?\4)?\s*?$/
const TAG_REG = /<([\w\-]+)([\w\W]*?)>/g
const ATTR_REG = /^[\s\S]*?(style="[^"]*?")[\s\S]*?$/
const INLINE = { const INLINE = {
strong: [ code: /`([^`]*?[^`\\\s])`/g,
/__([^\s_])__(?!_)/g, strong: [/__([\s\S]*?[^\s\\])__(?!_)/g, /\*\*([\s\S]*?[^\s\\])\*\*(?!\*)/g],
/\*\*([^\s*])\*\*(?!\*)/g, em: [/_([\s\S]*?[^\s\\])_(?!_)/g, /\*([\s\S]*?[^\s\\*])\*(?!\*)/g],
/__([^\s][\s\S]*?[^\s])__(?!_)/g, del: /~~([\s\S]*?[^\s\\~])~~/g,
/\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)/g qlink: /\[([^\]]*?)\]\[(\d*?)\]/g, // 引用链接
], img: /\!\[([^\]]*?)\]\(([^)]*?)\)/g,
em: [ a: /\[([^\]]*?)\]\(([^)]*?)(\s+"([\s\S]*?)")*?\)/g,
/_([^\s_])_(?!_)/g, qlist: /((<blockquote class="md\-quote">)*?)([\+\-\*]|\d+\.) (.*)/ // 引用中的列表
/\*([^\s*])\*(?!\*)/g,
/_([^\s][\s\S]*?[^\s])_(?!_)/g,
/\*([^\s][\s\S]*?[^\s])\*(?!\*)/g
],
del: [/~~([^\~])~~(?!~)/g, /~~([^\s][\s\S]*?[^\s])~~(?!~)/g],
qlink: /\[(.*?)\]\[(\d*?)\]/g
} }
const log = console.log
const Helper = { 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.startsWith(s.repeat(3)) var reg = new RegExp('^\\' + escape(s) + '{3,}$')
return reg.test(str)
} }
return false return false
}, },
@ -67,7 +62,7 @@ const Helper = {
}, },
isQLink(str) { isQLink(str) {
if (QLINK_REG.test(str)) { if (QLINK_REG.test(str)) {
return RegExp.$2 return { [RegExp.$1]: { l: RegExp.$2, t: RegExp.$3 } }
} }
return false return false
}, },
@ -80,21 +75,37 @@ const Decoder = {
// 内联样式 // 内联样式
inline(str) { inline(str) {
return str return str
.replace(/`([^`]*?[^`\\\s])`/g, '<code class="inline">$1</code>') .replace(INLINE.code, '<code class="inline">$1</code>')
.replace(INLINE.strong[0], '<strong>$1</strong>') .replace(INLINE.strong[0], '<strong>$1</strong>')
.replace(INLINE.strong[1], '<strong>$1</strong>') .replace(INLINE.strong[1], '<strong>$1</strong>')
.replace(INLINE.strong[2], '<strong>$1</strong>')
.replace(INLINE.strong[3], '<strong>$1</strong>')
.replace(INLINE.em[0], '<em>$1</em>') .replace(INLINE.em[0], '<em>$1</em>')
.replace(INLINE.em[1], '<em>$1</em>') .replace(INLINE.em[1], '<em>$1</em>')
.replace(INLINE.em[2], '<em>$1</em>') .replace(INLINE.del, '<del>$1</del>')
.replace(INLINE.em[3], '<em>$1</em>') .replace(INLINE.img, '<img src="$2" alt="$1">')
.replace(INLINE.del[0], '<del>$1</del>') .replace(INLINE.a, (m1, txt, link, m2, attr = '') => {
.replace(INLINE.del[1], '<del>$1</del>') var tmp = attr
.replace(/\!\[([^]*?)\]\(([^)]*?)\)/g, '<img src="$2" alt="$1">') .split(';')
.replace(/\[([^]*?)\]\(([^)]*?)\)/g, '<a href="$2">$1</a>') .filter(_ => _)
.replace(INLINE.qlink, (m, s, n) => { .map(_ => {
return `<a href="${this.__LINKS__[n - 1]}">${s}</a>` var a = _.split('=')
if (a.length > 1) {
return `${a[0]}="${a[1]}"`
} else {
return `title="${_}"`
}
})
.join(' ')
return `<a href="${link.trim()}" ${tmp}>${txt}</a>`
})
.replace(INLINE.qlink, (m, txt, n) => {
var _ = this.__LINKS__[n]
if (_) {
var a = _.t ? `title=${_.t}` : ''
return `<a href="${_.l}" ${a}>${txt}</a>`
} else {
return m
}
}) })
.replace(ESCAPE_REG, '$1') // 处理转义字符 .replace(ESCAPE_REG, '$1') // 处理转义字符
}, },
@ -131,7 +142,7 @@ const Decoder = {
var stat = todoChecked === 1 ? 'checked' : '' var stat = todoChecked === 1 ? 'checked' : ''
var txt = todoChecked === 1 ? `<del>${word}</del>` : word var txt = todoChecked === 1 ? `<del>${word}</del>` : word
return `<section><wc-checkbox readonly ${stat}>${txt}</wc-checkbox></section>` return `<section><wc-checkbox-item readonly ${stat}>${txt}</wc-checkbox-item></section>`
} }
return false return false
} }
@ -151,8 +162,15 @@ class Tool {
.replace(/\t/g, ' ') .replace(/\t/g, ' ')
.replace(/\u00a0/g, ' ') .replace(/\u00a0/g, ' ')
.replace(/\u2424/g, '\n') .replace(/\u2424/g, '\n')
.replace(TAG_REG, (m, name, attr) => {
attr = attr.replace(/\n/g, '⨨☇') // 标签内的换行, 转为一组特殊字符, 方便后面还原
if (attr) {
attr = ' ' + attr
}
return `<${name + attr}>`
})
var links = [] var links = {}
var list = [] var list = []
var lines = str.split('\n') var lines = str.split('\n')
var isCodeBlock = false // 是否代码块 var isCodeBlock = false // 是否代码块
@ -184,10 +202,28 @@ class Tool {
) )
isTable = true isTable = true
} else { } else {
var isQlink = Helper.isQLink(it) var qlink
if (isCodeBlock) {
it = it
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/⨨☇/g, '\n') // 代码块要还原回换行
} else {
it = it
.replace(/(⨨☇)+/g, ' ') // 非代码块直接转为空格, 并进行xss过滤
.replace(INLINE.code, (m, txt) => {
return `\`${txt.replace(/</g, '&lt;').replace(/>/g, '&gt;')}\``
})
.replace(/<(\/?)script[^>]*?>/g, '&lt;$1script&gt;')
.replace(TAG_REG, (m, name, attr) => {
attr = attr.replace(ATTR_REG, '$1').trim()
return `<${name} ${attr}>`
})
}
qlink = Helper.isQLink(it)
if (isQlink) { if (qlink) {
links.push(isQlink) Object.assign(links, qlink)
} else { } else {
list.push(it) list.push(it)
} }
@ -205,7 +241,6 @@ class Tool {
list.push(tmp) list.push(tmp)
} }
} }
return new this(list, links) return new this(list, links)
} }
@ -223,50 +258,13 @@ class Tool {
var orderListLevel = -1 var orderListLevel = -1
var unorderListLevel = -1 var unorderListLevel = -1
var isQuoteList = false // 引用中的列表, 只支持一层级
var quoteListStyle = 0 // 1有序, 2 无序
// //
for (let it of this.list) { for (let it of this.list) {
// 空行 // 非空行
if (!it) { if (it) {
// 如果是在代码中, 直接拼接, 并加上换行
if (isCodeBlock) {
html += it + '\n'
} else {
emptyLineLength++
// 引用结束
if (isBlockquote) {
isBlockquote = false
if (emptyLineLength > 0) {
emptyLineLength = 0
while (blockquoteLevel > 0) {
blockquoteLevel--
html += '</blockquote>'
}
}
continue
}
if (isList) {
while (orderListLevel > -1 || unorderListLevel > -1) {
if (orderListLevel > unorderListLevel) {
html += '</ol>'
orderListLevel--
} else {
html += '</ul>'
unorderListLevel--
}
}
isList = false
continue
}
//
if (isParagraph) {
isParagraph = false
html += '</p>'
}
}
} else {
if (~it.indexOf('<table>') || ~it.indexOf('</table>')) { if (~it.indexOf('<table>') || ~it.indexOf('</table>')) {
html += it html += it
isTable = !isTable isTable = !isTable
@ -308,7 +306,7 @@ class Tool {
// 同上代码块的处理 // 同上代码块的处理
if (isCodeBlock) { if (isCodeBlock) {
html += it + '\n' html += '\n' + it
continue continue
} }
@ -331,10 +329,8 @@ class Tool {
// 引用 // 引用
if (it.startsWith('>')) { if (it.startsWith('>')) {
if (isBlockquote) { let innerQuote // 是否有缩进引用
html += '<br>' it = it.replace(/^(>+) /, (p, m) => {
}
html += it.replace(/^(>+) /, (p, m) => {
let len = m.length let len = m.length
let tmp = '' let tmp = ''
let loop = len let loop = len
@ -350,9 +346,49 @@ class Tool {
} }
blockquoteLevel = len blockquoteLevel = len
innerQuote = !!tmp
return tmp return tmp
}) })
if (isBlockquote) {
// 没有新的缩进引用时, 才添加换行
if (innerQuote) {
// 之前有引用的列表时, 直接结束列表
if (isQuoteList) {
html += `</${quoteListStyle === 1 ? 'ul' : 'ul'}>`
isQuoteList = false
}
}
}
let qListChecked = it.match(INLINE.qlist)
if (qListChecked) {
let tmp1 = qListChecked[1] // 缩进的标签
let tmp2 = +qListChecked[3] // 有序还是无序
let tmp3 = qListChecked.pop() // 文本
let currListStyle = tmp2 === tmp2 ? 1 : 2
var qlist = ''
// 已有列表
if (isQuoteList) {
// 因为只支持一层级的列表, 所以同一级别不区分有序无序, 强制统一
} else {
isQuoteList = true
if (currListStyle === 1) {
qlist += '<ol>'
} else {
qlist += '<ul>'
}
}
quoteListStyle = currListStyle
qlist += `<li>${tmp3}</li>`
html += tmp1 + qlist
} else {
html += '<br>' + it
}
isParagraph = false isParagraph = false
isBlockquote = true isBlockquote = true
continue continue
@ -375,15 +411,7 @@ class Tool {
let level = Math.floor(ltrim / 2) let level = Math.floor(ltrim / 2)
let tag = listChecked > 0 ? 'ol' : 'ul' let tag = listChecked > 0 ? 'ol' : 'ul'
if (!isList) { if (isList) {
html += `<${tag}>`
if (listChecked === 1) {
orderListLevel = level
} else {
unorderListLevel = level
}
html += `<li>${word}</li>`
} else {
if (listChecked === 1) { if (listChecked === 1) {
if (level > orderListLevel) { if (level > orderListLevel) {
html = html.replace(/<\/li>$/, '') html = html.replace(/<\/li>$/, '')
@ -405,19 +433,72 @@ class Tool {
} }
unorderListLevel = level unorderListLevel = level
} }
} else {
html += `<${tag}>`
if (listChecked === 1) {
orderListLevel = level
} else {
unorderListLevel = level
}
html += `<li>${word}</li>`
} }
isList = true isList = true
continue continue
} }
// log('it => ', isParagraph, it) // 无"> "前缀的引用, 继续拼到之前的, 并且不换行
if (isBlockquote) {
html += it
continue
}
if (isParagraph) { if (isParagraph) {
html += `${it}<br>` html += `${it}<br>`
} else { } else {
html += `<p>${it}<br>` html += `<p>${it}<br>`
} }
isParagraph = true isParagraph = true
} else {
// 如果是在代码中, 直接拼接, 并加上换行
if (isCodeBlock) {
html += it + '\n'
} else {
emptyLineLength++
// 引用结束
if (isBlockquote) {
isBlockquote = false
if (emptyLineLength > 0) {
emptyLineLength = 0
while (blockquoteLevel > 0) {
blockquoteLevel--
html += '</blockquote>'
}
}
continue
}
if (isList) {
while (orderListLevel > -1 || unorderListLevel > -1) {
if (orderListLevel > unorderListLevel) {
html += '</ol>'
orderListLevel--
} else {
html += '</ul>'
unorderListLevel--
}
}
isList = false
continue
}
//
if (isParagraph) {
isParagraph = false
html += '</p>'
}
}
} }
} }
delete this.list delete this.list

View File

@ -6,28 +6,27 @@
:host { :host {
display: block; display: block;
line-height: 1.5; line-height: 1.5;
color: nth($cd, 1); color: var(--color-dark-1);
font-size: 14px; font-size: 14px;
} }
a { a {
text-decoration: underline; text-decoration: underline;
color: nth($ct, 2); color: var(--color-teal-2);
} }
a:hover { a:hover {
color: nth($ct, 1); color: var(--color-teal-1);
text-decoration: none; text-decoration: none;
} }
em, em,
del { del {
color: nth($cgr, 2); color: var(--color-grey-2);
} }
strong, strong,
strong em, strong em,
strong, strong {
del { color: var(--color-dark-3);
color: nth($cd, 3);
} }
a { a {
strong, strong,
@ -41,7 +40,7 @@ del {
padding: 0 2px; padding: 0 2px;
} }
p { p {
margin: 15px 0; margin: 12px 0;
} }
img { img {
max-width: 100%; max-width: 100%;
@ -51,93 +50,24 @@ blockquote.md-quote {
margin: 10px 0; margin: 10px 0;
padding: 5px 10px; padding: 5px 10px;
line-height: 1.5; line-height: 1.5;
border-left: 5px solid nth($ct, 1); border-left: 5px solid var(--color-teal-1);
background: #f2faf7; background: #f2faf7;
color: nth($cgr, 1); color: var(--color-grey-2);
p { p {
margin: 0; margin: 0;
} }
} }
/* 提醒文本 */
.md-warn,
.md-mark {
display: inline-block;
position: relative;
min-height: 35px;
margin: 3px 0;
padding: 3px 8px 3px 35px;
line-height: 27px;
border: 1px solid nth($co, 2);
border-radius: 5px;
background: #fffbed;
color: nth($co, 3);
word-break: break-all;
p {
margin: 0 !important;
}
i {
position: absolute;
left: 8px;
top: 6px;
line-height: 1;
font-size: 20px;
color: nth($cr, 2);
}
}
.md-mark {
border-color: nth($ct, 1);
color: nth($ct, 3);
background: #edfbf8;
i {
color: nth($ct, 3);
}
}
.md-task {
position: relative;
display: inline-block;
width: auto;
height: 30px;
padding-right: 10px;
line-height: 30px;
text-align: center;
cursor: default;
&__box {
float: left;
width: 18px;
height: 18px;
margin: 6px;
margin-left: 0;
line-height: 1;
border: 1px solid nth($cgr, 1);
border-radius: 3px;
font-size: 16px;
text-align: center;
}
&.done {
.md-task__box {
color: nth($cgr, 1);
border-color: nth($cp, 3);
background: nth($cp, 3);
}
.md-task__text {
color: nth($cgr, 1);
text-decoration: line-through;
}
}
}
fieldset.md-hr { fieldset.md-hr {
margin: 30px 0; margin: 30px 0;
border: 0; border: 0;
border-top: 1px dashed nth($cp, 3); border-top: 1px dashed var(--color-plain-3);
legend { legend {
padding: 0 5px; padding: 0 5px;
color: nth($cgr, 1); color: var(--color-grey-1);
text-align: center; text-align: center;
font-size: 12px;
&::before { &::before {
content: '华丽丽的分割线'; content: '华丽丽的分割线';
} }
@ -191,8 +121,8 @@ h5,
h6 { h6 {
a { a {
&::before { &::before {
content: '# '; content: ' ';
color: nth($ct, 1); color: var(--color-teal-1);
font-weight: normal; font-weight: normal;
} }
} }
@ -205,7 +135,7 @@ h1 {
h2 { h2 {
margin: 20px 0; margin: 20px 0;
font-size: 22px; font-size: 22px;
border-bottom: 1px solid nth($cp, 2); border-bottom: 1px solid var(--color-plain-2);
} }
h3 { h3 {
margin: 20px 0 15px; margin: 20px 0 15px;
@ -224,27 +154,27 @@ table {
background-color: #fff; background-color: #fff;
} }
thead tr { thead tr {
background: nth($cp, 1); background: var(--color-plain-1);
} }
th, th,
td { td {
padding: 6px 13px; padding: 6px 13px;
border: 1px solid #ddd; border: 1px solid var(--color-plain-2);
} }
th { th {
font-weight: bold; font-weight: bold;
} }
tr:nth-child(2n) { tr:nth-child(2n) {
background-color: #fbfbfb; background-color: #fcfdff;
} }
} }
code.inline { code.inline {
display: inline-block; display: inline;
margin: 0 2px; margin: 0 2px;
padding: 0 2px; padding: 0 2px;
color: nth($co, 3); color: var(--color-orange-3);
background: nth($cp, 1); background: var(--color-plain-1);
border-radius: 2px; border-radius: 2px;
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
} }
@ -252,10 +182,11 @@ code.inline {
<script> <script>
import $ from '../utils' import $ from '../utils'
import '../code/index'
import markd from './core' import markd from './core'
import '../code/index'
import '../form/checkbox'
export default class Markd { export default class Markd {
props = { props = {
toc: false toc: false
@ -265,20 +196,51 @@ export default class Markd {
/* render */ /* render */
var elem = this.root.children[1] var elem = this.root.children[1]
this.__BOX__ = elem this.__BOX__ = elem
} }
mounted() { __parse__() {
this.__BOX__.innerHTML = markd(this.textContent) var txt = this.textContent.trim()
this.textContent = '' this.value = txt
}
$.bind(this.__BOX__, 'click', ev => { set value(txt) {
if (txt) {
this.__BOX__.innerHTML = markd(txt)
this.textContent = ''
}
}
clear() {
this.__BOX__.textContent = ''
this.textContent = ''
}
mounted() {
this.__parse__()
this._headClickFn = $.bind(this.__BOX__, 'click', ev => {
if (ev.target.className === 'md-head-link') { if (ev.target.className === 'md-head-link') {
var ot = ev.target.offsetTop var ot = ev.target.offsetTop
document.documentElement.scrollTop = ot document.documentElement.scrollTop = ot
} }
}) })
this.__observer = new MutationObserver(_ => {
this.__parse__()
})
this.__observer.observe(this, {
childList: true,
subtree: true,
characterData: true
})
}
unmount() {
$.unbind(this.__BOX__, 'click', this._headClickFn)
this.__observer.disconnect()
this.clear()
} }
} }
</script> </script>

131
src/meditor/addon.js Normal file
View File

@ -0,0 +1,131 @@
/**
* 基础拓展
* @author yutent<yutent.io@gmail.com>
* @date 2020/10/14 17:52:44
*/
import $ from '../utils'
var 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) {
showDialog(this.__HEADER_ADDON__, elem)
},
h(level) {
var wrap = this.selection(true) || placeholder
wrap = wrap.replace(/^(#+ )?/, '#'.repeat(level) + ' ')
this.insert(wrap, true)
},
quote(elem) {
var wrap = this.selection(true) || placeholder
wrap = wrap.replace(/^(>+ )?/, '> ')
this.insert(wrap, true)
},
bold(elem) {
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
var unwrap = trim(wrap, '_')
wrap = wrap === unwrap ? `_${wrap}_` : unwrap
this.insert(wrap, true)
},
through(elem) {
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
wrap = wrap.replace(/^([+\-*] )?/, '+ ')
this.insert(wrap, true)
},
order(elem) {
var wrap = this.selection(true) || placeholder
wrap = wrap.replace(/^(\d+\. )?/, '1. ')
this.insert(wrap, true)
},
line(elem) {
this.insert('\n\n---\n\n', false)
},
code(elem) {
var wrap = this.selection() || placeholder
var unwrap = trim(wrap, '`')
wrap = wrap === unwrap ? `\`${wrap}\`` : unwrap
this.insert(wrap, true)
},
codeblock(elem) {
this.insert('\n```language\n\n```\n')
},
table(elem) {
showDialog(this.__TABLE_ADDON__, elem)
},
link(elem) {
showDialog(this.__LINK_ADDON__, elem).then(dialog => {
var wrap = this.selection() || placeholder
dialog.__txt__.value = wrap
})
},
image(elem) {
this._attach = 'image'
showDialog(this.__ATTACH_ADDON__, elem)
},
attach(elem) {
this._attach = 'file'
showDialog(this.__ATTACH_ADDON__, elem)
},
fullscreen(elem) {
//
this.props.fullscreen = !this.props.fullscreen
this.classList.toggle('fullscreen', this.props.fullscreen)
elem.classList.toggle('active', this.props.fullscreen)
},
preview(elem) {
this.props.preview = !this.props.preview
this.__VIEW__.classList.toggle('active', this.props.preview)
elem.classList.toggle('active', this.props.preview)
}
}

225
src/meditor/helper.js Normal file
View File

@ -0,0 +1,225 @@
/**
* 一些公共的东西
* @author yutent<yutent.io@gmail.com>
* @date 2020/10/12 18:23:23
*/
import ICONS from './svg'
const ELEMS = {
a: function(str, attr, inner) {
let href = attr.match(attrExp('href'))
let title = attr.match(attrExp('title'))
let tar = attr.match(attrExp('target'))
let attrs = ''
href = (href && href[1]) || null
title = (title && title[1]) || null
tar = (tar && tar[1]) || '_self'
if (!href) {
return inner || href
}
href = href.replace('viod(0)', '')
attrs = `target=${tar}`
attrs += title ? `;title=${title}` : ''
return `[${inner || href}](${href} "${attrs}")`
},
em: function(str, attr, inner) {
return (inner && '_' + inner + '_') || ''
},
strong: function(str, attr, inner) {
return (inner && '**' + inner + '**') || ''
},
pre: function(str, attr, inner) {
inner = inner.replace(/<[/]?code>/g, '')
return '\n\n```\n' + inner + '\n```\n'
},
code: function(str, attr, inner) {
return (inner && '`' + inner + '`') || ''
},
blockquote: function(str, attr, inner) {
return '> ' + inner.trim()
},
img: function(str, attr, inner) {
var src = attr.match(attrExp('src')),
alt = attr.match(attrExp('alt'))
src = (src && src[1]) || ''
alt = (alt && alt[1]) || ''
return '![' + alt + '](' + src + ')'
},
p: function(str, attr, inner) {
return inner ? '\n' + inner : ''
},
br: '\n',
'h([1-6])': function(str, level, attr, inner) {
let h = '#'.repeat(level)
return '\n' + h + ' ' + inner + '\n'
},
hr: '\n\n---\n\n'
}
const DEFAULT_TOOLS = [
'header',
'quote',
'bold',
'italic',
'through',
'list',
'order',
'line',
'code',
'codeblock',
'table',
'link',
'image',
'attach',
'fullscreen',
'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: '预览'
}
export const IMAGE_EXP = /image\/(jpeg|gif|png|webp|bmp|vnd\.microsoft\.icon|svg\+xml)/
// html标签的属性正则
function attrExp(field, flag = 'i') {
return new RegExp(field + '\\s?=\\s?["\']?([^"\']*)["\']?', flag)
}
// 生成html标签的正则
function tagExp(tag, open) {
var exp = ''
if (['br', 'hr', 'img'].indexOf(tag) > -1) {
exp = '<' + tag + '([^>]*?)\\/?>'
} else {
exp = '<' + tag + '([^>]*?)>([\\s\\S]*?)<\\/' + tag + '>'
}
return new RegExp(exp, 'gi')
}
/**
* 渲染工具栏图标
*/
export function renderToolbar(list, tag = 'span', dict = {}, showText = false) {
return (list || DEFAULT_TOOLS)
.map(it => {
var title = showText ? '' : `title="${dict[it] || ''}"`
var text = showText ? dict[it] || '' : ''
return `<${tag} data-act="${it}" ${title}><svg class="icon" viewBox="0 0 1024 1024"><path d="${
ICONS[it]
}"/></svg>${text}</${tag}>`
})
.join('')
}
/**
* html转成md
*/
export function html2md(str) {
try {
str = decodeURIComponent(str)
} catch (err) {}
str = str
.replace(/\t/g, ' ')
.replace(/<meta [^>]*>/, '')
.replace(attrExp('class', 'g'), '')
.replace(attrExp('style', 'g'), '')
.replace(/<(?!a |img )(\w+) [^>]*>/g, '<$1>')
.replace(/<svg[^>]*>.*?<\/svg>/g, '{invalid image}')
// log(str)
for (let i in ELEMS) {
let cb = ELEMS[i]
let exp = tagExp(i)
if (i === 'blockquote') {
while (str.match(exp)) {
str = str.replace(exp, cb)
}
} else {
str = str.replace(exp, cb)
}
// 对另外3种同类标签做一次处理
if (i === 'p') {
exp = tagExp('div')
str = str.replace(exp, cb)
}
if (i === 'em') {
exp = tagExp('i')
str = str.replace(exp, cb)
}
if (i === 'strong') {
exp = tagExp('b')
str = str.replace(exp, cb)
}
}
let liExp = /<(ul|ol)>(?:(?!<ul|<ol)[\s\S])*?<\/\1>/gi
while (str.match(liExp)) {
str = str.replace(liExp, function(match) {
match = match.replace(/<(ul|ol)>([\s\S]*?)<\/\1>/gi, function(
m,
t,
inner
) {
let li = inner.split('</li>')
li.pop()
for (let i = 0, len = li.length; i < len; i++) {
let pre = t === 'ol' ? i + 1 + '. ' : '* '
li[i] =
pre +
li[i]
.replace(/\s*<li>([\s\S]*)/i, function(m, n) {
n = n.trim().replace(/\n/g, '\n ')
return n
})
.replace(/<[\/]?[\w]*[^>]*>/g, '')
}
return li.join('\n')
})
return '\n' + match.trim()
})
}
str = str
.replace(/<[\/]?[\w]*[^>]*>/g, '')
.replace(/```([\w\W]*)```/g, function(str, inner) {
inner = inner
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
return '```' + inner + '```'
})
return str
}

View File

@ -1,29 +1,824 @@
<template> <template>
<div class="meditor"> <div class="meditor">
<section class="toolbar"></section> <section class="toolbar"></section>
<textarea cols="30" rows="10"></textarea> <div class="editor-outbox">
<wc-markd></wc-markd> <textarea class="editor" spellcheck="false"></textarea>
<wc-markd class="preview"></wc-markd>
</div>
</div> </div>
<wc-layer id="header"><ul class="addon-header"></ul></wc-layer>
<wc-layer id="table"><ul class="addon-table"></ul></wc-layer>
<wc-layer id="link">
<ul class="addon-link">
<li>
<label>链接文本</label><wc-input class="txt" size="mini"></wc-input>
</li>
<li>
<label>链接地址</label><wc-input class="link" size="mini"></wc-input>
</li>
<li>
<label>打开方式</label>
<wc-radio-group value="_self" class="mode">
<wc-radio value="_self" size="mini">当前窗口</wc-radio>
<wc-radio value="_blank" size="mini">新窗口</wc-radio>
</wc-radio-group>
</li>
<li>
<label></label>
<wc-button class="submit" color="teal" size="mini">确定</wc-button>
</li>
</ul>
</wc-layer>
<wc-layer id="attach">
<div class="addon-attach">
<header class="tabs">
<span data-key="remote" class="active">远程文件</span>
<span data-key="locale">本地上传</span>
</header>
<ul class="remote active">
<li>
<label>文件描述</label><wc-input class="txt" size="mini"></wc-input>
</li>
<li>
<label>文件地址</label><wc-input class="link" size="mini"></wc-input>
</li>
<li>
<label></label>
<wc-button class="submit" color="teal" size="mini">确定</wc-button>
</li>
</ul>
<div class="locale">
<div class="button"><input type="file" /></div>
</div>
</div>
</wc-layer>
</template> </template>
<style lang="scss"></style> <style lang="scss">
:host {
display: flex;
min-width: 200px;
min-height: 100px;
// max-height: 360px;
border-radius: 2px;
}
.meditor {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
border: 1px solid var(--color-plain-2);
border-radius: inherit;
font-size: 14px;
}
.toolbar {
display: none;
height: 34px;
padding: 5px;
line-height: 24px;
border-bottom: 1px solid var(--color-plain-2);
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-dark-1);
.icon {
overflow: hidden;
width: 70%;
height: 70%;
fill: currentColor;
}
&:hover,
&.active {
background: var(--color-plain-1);
}
&.active {
color: var(--color-teal-1);
}
}
&.active {
display: flex;
}
}
.editor-outbox {
flex: 1;
display: flex;
min-height: 300px;
background: #fff;
.editor,
.preview {
flex: 1;
}
.editor {
height: 100%;
padding: 5px 8px;
line-height: 1.5;
border: 0;
font-size: 14px;
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
color: var(--color-grey-3);
background: none;
outline: none;
resize: none;
}
.preview {
overflow: hidden;
overflow-y: auto;
display: none;
padding: 6px 12px;
border-left: 1px solid var(--color-plain-2);
&.active {
display: block;
}
}
}
.addon-table {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 222px;
height: 222px;
padding: 2px;
background: #fff;
li {
display: flex;
justify-content: space-between;
height: 20px;
span {
width: 20px;
height: 20px;
background: var(--color-plain-1);
&.active {
background: rgba(77, 182, 172, 0.3);
}
}
}
}
.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;
padding: 8px 5px;
background: #fff;
font-size: 13px;
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;
}
}
}
:host([disabled]) {
.meditor {
cursor: not-allowed;
opacity: 0.6;
}
}
:host(.fullscreen) {
position: fixed;
left: 0;
top: 0;
z-index: 1024;
width: 100%;
height: 100%;
}
</style>
<script> <script>
import $ from '../utils'
import '../form/input'
import '../form/button'
import '../form/radio-group'
import '../layer/index'
import '../markd/index' import '../markd/index'
import { renderToolbar, html2md, TOOL_TITLE, IMAGE_EXP } from './helper'
import Addon from './addon'
export default class Meditor { export default class Meditor {
props = { props = {
value: '' toolbar: null,
value: '',
readonly: false,
disabled: false,
height: 180,
preview: window.innerWidth > 768
} }
__init__() { __init__() {
/* render */ /* render */
var elem = this.root.children[1] var ct = this.root.children[1]
this.__TOOLBAR__ = ct.children[0]
this.__EDITOR__ = ct.children[1].firstElementChild
this.__VIEW__ = ct.children[1].lastElementChild
this.__TOOL__ = elem.children[0] this.__HEADER_ADDON__ = this.root.querySelector('#header')
this.__EDITOR__ = elem.children[1] this.__TABLE_ADDON__ = this.root.querySelector('#table')
this.__VIEW__ = elem.children[2] this.__LINK_ADDON__ = this.root.querySelector('#link')
this.__ATTACH_ADDON__ = this.root.querySelector('#attach')
}
_updateToolbar() {
var { toolbar, preview } = this.props
var isToolbarShow = true
if (Array.isArray(toolbar)) {
if (toolbar.length) {
this.__TOOLBAR__.innerHTML = renderToolbar(toolbar, 'span', TOOL_TITLE)
} else {
isToolbarShow = false
}
} else {
this.__TOOLBAR__.innerHTML = renderToolbar(null, 'span', TOOL_TITLE)
}
this.__TOOLBAR__.classList.toggle('active', isToolbarShow)
this.__TOOLBAR__.lastElementChild.classList.toggle('active', preview)
this.__VIEW__.classList.toggle('active', preview)
}
_createTableAddon() {
var html = ''
var $table = this.__TABLE_ADDON__.firstElementChild
var $item
var lastx = -1
var lasty = -1
for (let i = 0; i < 10; i++) {
html += '<li>'
for (let j = 0; j < 10; j++) {
html += `<span data-x="${i}" data-y="${j}"></span>`
}
html += '</li>'
}
$table.innerHTML = html
$item = Array.from($table.children).map(it => Array.from(it.children))
$.bind($table, 'mousemove', ev => {
if (ev.target.tagName === 'SPAN') {
var x = +ev.target.dataset.x
var y = +ev.target.dataset.y
if (x === lastx && y === lasty) {
return
}
lastx = x
lasty = y
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
$item[i][j].classList.toggle('active', i <= x && j <= y)
}
}
}
})
$.bind($table, 'mouseleave', ev => {
lastx = -1
lasty = -1
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
$item[i][j].classList.remove('active')
}
}
})
$.bind($table, 'click', ev => {
if (lastx < 0 || lasty < 0) {
return
}
var thead = `\n\n${'| 表头 '.repeat(lastx)}|\n`
var pipe = `${'| -- '.repeat(lastx)}|\n`
var tbody = ('| '.repeat(lastx) + '|\n').repeat(lasty)
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
$item[i][j].classList.remove('active')
}
}
lastx = -1
lasty = -1
this.insert(thead + pipe + tbody, false)
this.__TABLE_ADDON__.close()
})
$.outside($table, _ => {
lastx = -1
lasty = -1
this.__TABLE_ADDON__.close()
})
}
_createHeaderAddon() {
var items = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
var html = renderToolbar(items, 'li', TOOL_TITLE, true)
var $header = this.__HEADER_ADDON__.firstElementChild
$header.innerHTML = html
$.bind($header, 'click', ev => {
var elem = ev.target
var level
if (elem === ev.currentTarget) {
return
}
while (elem.tagName !== 'LI') {
elem = elem.parentNode
}
level = +elem.dataset.act.slice(1)
Addon.h.call(this, level)
this.__HEADER_ADDON__.close()
})
$.outside($header, _ => {
this.__HEADER_ADDON__.close()
})
}
_createLinkAddon() {
var $link = this.__LINK_ADDON__.firstElementChild
var $txt = $link.querySelector('.txt')
var $url = $link.querySelector('.link')
var $mode = $link.querySelector('.mode')
var $submit = $link.querySelector('.submit')
this.__LINK_ADDON__.__txt__ = $txt
// Addon.link.call(this, this.__TOOLBAR__.children[12])
$.bind($submit, 'click', ev => {
var txt = $txt.value.trim()
var url = $url.value.trim()
var mode = $mode.value.trim()
var attr = ''
if (mode === '_blank') {
attr = ` "target=${mode}"`
}
if (txt && url) {
this.insert(`[${txt}](${url + attr}) `)
$txt.value = ''
$url.value = ''
this.__LINK_ADDON__.close()
}
})
$.outside($link, _ => {
this.__LINK_ADDON__.close()
})
}
_createAttachAddon() {
var $attach = this.__ATTACH_ADDON__.firstElementChild
var $tabs = $attach.querySelector('.tabs')
var $remote = $attach.querySelector('.remote')
var $locale = $attach.querySelector('.locale')
var $submit = $attach.querySelector('.submit')
var $drag = $locale.querySelector('.button')
var $file = $locale.querySelector('input')
var $txt = $remote.querySelector('.txt')
var $url = $remote.querySelector('.link')
$.bind($tabs, 'click', ev => {
var key = ev.target.dataset.key
if (key) {
ev.target.classList.toggle('active', true)
//
if (ev.target.nextElementSibling) {
ev.target.nextElementSibling.classList.toggle('active', false)
$remote.classList.toggle('active', true)
$locale.classList.toggle('active', false)
} else {
ev.target.previousElementSibling.classList.toggle('active', false)
$remote.classList.toggle('active', false)
$locale.classList.toggle('active', true)
}
}
})
// 拖拽上传
$.bind($drag, 'dragover', ev => {
ev.stopPropagation()
ev.preventDefault()
$drag.classList.toggle('active', true)
})
$.bind($drag, 'dragleave', ev => {
ev.stopPropagation()
ev.preventDefault()
$drag.classList.toggle('active', false)
})
$.bind($drag, 'drop', ev => {
ev.stopPropagation()
ev.preventDefault()
$drag.classList.toggle('active', false)
var file = ev.dataTransfer.files[0]
if (file) {
if (this._attach === 'image') {
if (IMAGE_EXP.test(file.type)) {
this._handleUpload(file)
}
} else {
this._handleUpload(file, '')
}
}
})
// 点击上传
$.bind($file, 'change', ev => {
var file = ev.target.files[0]
if (file) {
ev.target.value = ''
if (this._attach === 'image') {
if (IMAGE_EXP.test(file.type)) {
this._handleUpload(file)
}
} else {
this._handleUpload(file, '')
}
}
})
// 远程地址
$.bind($submit, 'click', ev => {
var txt = $txt.value.trim()
var url = $url.value.trim()
if (txt && url) {
this.insert(`${this._attach === 'image' ? '!' : ''}[${txt}](${url}) `)
$txt.value = ''
$url.value = ''
this.__ATTACH_ADDON__.close()
}
})
$.outside($attach, _ => {
this.__ATTACH_ADDON__.close()
})
}
// 处理文件上传
_handleUpload(file, t = '!') {
this.dispatchEvent(
new CustomEvent('upload', {
detail: {
file,
send: link => {
this.insert(`${t}[${file.name}](${link})`)
this.__ATTACH_ADDON__.close()
}
}
})
)
}
// 往文本框中插入内容
insert(val, isSelect) {
var dom = this.__EDITOR__
if (document.selection) {
dom.focus()
let range = document.selection.createRange()
range.text = val
dom.focus()
range.moveStart('character', -1)
} else if (dom.selectionStart || dom.selectionStart === 0) {
let startPos = dom.selectionStart
let endPos = dom.selectionEnd
let scrollTop = dom.scrollTop
dom.value =
dom.value.slice(0, startPos) +
val +
dom.value.slice(endPos, dom.value.length)
dom.selectionStart = isSelect ? startPos : startPos + val.length
dom.selectionEnd = startPos + val.length
dom.scrollTop = scrollTop
dom.focus()
} else {
dom.value += val
dom.focus()
}
this.value = dom.value
}
/**
* [selection 获取选中的文本]
* @param {[type]} dom [要操作的元素]
* @param {[type]} forceHoleLine [是否强制光标所在的整行文本]
*/
selection(forceHoleLine) {
var dom = this.__EDITOR__
if (document.selection) {
return document.selection.createRange().text
} else {
let startPos = dom.selectionStart
let endPos = dom.selectionEnd
if (endPos) {
//强制选择整行
if (forceHoleLine) {
startPos = dom.value.slice(0, startPos).lastIndexOf('\n')
let tmpEnd = dom.value.slice(endPos).indexOf('\n')
tmpEnd = tmpEnd < 0 ? dom.value.slice(endPos).length : tmpEnd
startPos += 1 // 把\n加上
endPos += tmpEnd
dom.selectionStart = startPos
dom.selectionEnd = endPos
}
} else {
//强制选择整行
if (forceHoleLine) {
endPos = dom.value.indexOf('\n')
endPos = endPos < 0 ? dom.value.length : endPos
dom.selectionEnd = endPos
}
}
dom.focus()
return dom.value.slice(startPos, endPos)
}
}
get value() {
return this.__EDITOR__.value
}
set value(val) {
this.__EDITOR__.value = val
if (this.props.preview) {
this.__VIEW__.textContent = val
}
}
mounted() {
this._updateToolbar()
this._createHeaderAddon()
this._createTableAddon()
this._createLinkAddon()
this._createAttachAddon()
$.bind(this.__EDITOR__, 'input', _ => {
if (this.props.preview) {
var txt = this.__EDITOR__.value.trim()
if (txt) {
this.__VIEW__.textContent = txt
} else {
this.__VIEW__.clear()
}
}
})
$.bind(this.__EDITOR__, 'keydown', ev => {
let wrap = this.selection() || ''
let select = !!wrap
//tab键改为插入2个空格,阻止默认事件,防止焦点失去
if (ev.keyCode === 9) {
ev.preventDefault()
wrap = wrap
.split('\n')
.map(function(it) {
return ev.shiftKey ? it.replace(/^\s\s/, '') : ' ' + it
})
.join('\n')
this.insert(wrap, select)
}
//修复按退格键删除选中文本时,选中的状态不更新的bug
if (ev.keyCode === 8) {
if (select) {
ev.preventDefault()
this.insert('', select)
}
}
})
$.bind(this.__EDITOR__, 'paste', ev => {
ev.preventDefault()
var txt = ev.clipboardData.getData('text/plain').trim()
var html = ev.clipboardData.getData('text/html').trim()
var items = ev.clipboardData.items
if (html) {
html = html2md(html)
return this.insert(html)
}
if (txt) {
return this.insert(txt)
}
if (items && items.length) {
let file = null
for (let it of items) {
file = it.getAsFile()
break
}
if (file) {
if (IMAGE_EXP.test(file.type)) {
this._handleUpload(file)
} else {
this._handleUpload(file, '')
}
}
}
})
$.bind(this.__TOOLBAR__, 'click', ev => {
ev.preventDefault()
var elem = ev.target
var act
if (elem === ev.currentTarget) {
return
}
while (elem.tagName !== 'SPAN') {
elem = elem.parentNode
}
act = elem.dataset.act
Addon[act].call(this, elem)
})
}
watch() {
switch (name) {
case 'toolbar':
if (val) {
val = val.split(',').map(it => it.trim())
this.props.toolbar = val
}
break
case 'value':
this.value = val
break
case 'readonly':
case 'disabled':
var k = name
if (k === 'readonly') {
k = 'readOnly'
}
this[k] = true
break
default:
break
}
} }
} }
</script> </script>

55
src/meditor/svg.js Normal file
View File

@ -0,0 +1,55 @@
/**
* icon字典
* @author yutent<yutent.io@gmail.com>
* @date 2019/07/08 17:39:11
*/
'use strict'
export default {
header:
'M128 457.15h329.14v109.71H347.43v329.13H237.71V566.86H128V457.15z m767.98-219.43H680.21v658.27h-117V237.72H347.44V128.01H896l-0.02 109.71z',
h1:
'M567.23 896h-62.15V523.45H222.22V896h-62.93V128h62.93v326.11h282.86V128h62.15v768z m297.48-1.48h-230.2v-59.5h86.99V535.26h-69.38v-47.71l6.3-1.26c32.06-6.4 57.01-15.6 78.51-28.96l1.9-1.18h47.43v378.86h78.45v59.51z',
h2:
'M864.54 896H639.87v-41.55l2.05-2.24C745.85 738.4 792.2 657.17 792.2 588.8c0-67.27-37.32-74.53-53.37-74.53-26.94 0-48.8 24.36-62.39 44.79l-5.89 8.85-34.57-42.49 3.6-4.88C663.09 488.62 693.86 456 743.82 456c63.42 0 104.4 51.46 104.4 131.1 0 40.81-12.01 83.37-36.73 130.12-19.26 36.44-45.83 75.01-83 120.37 12.26-1.15 24.97-2.13 36.18-2.13h99.87V896z m-297.34-0.37h-62.12V523.26H222.36v372.37h-62.91V128h62.91v325.95h282.72V128h62.12v767.63z',
h3:
'M750.38 895.4c-25.28 0-48.37-6.06-68.62-18.01-16.41-9.68-30.85-22.96-44.14-40.59l-3.25-4.31 29.65-47.34 6.88 8.82c23.21 29.74 47.46 43.58 76.3 43.58 36.26 0 59.69-26.54 59.69-67.61 0-50.29-30.34-73.71-95.49-73.71h-7.84v-54.51h7.84c30.05 0 52.33-6.8 66.22-20.2 11.9-11.49 17.94-28.18 17.94-49.63 0-34.99-18.06-56.51-48.32-57.62-27.59 1.28-49.4 21.14-62.85 37.61l-6.6 8.08-31.61-45.8 4.05-4.59c30.51-34.64 62.83-51.48 98.79-51.48 30.23 0 56.07 9.96 74.74 28.81 19.38 19.58 29.63 47.62 29.63 81.1 0 43.53-17.82 77.85-50.55 98.17 15.76 7.15 29.34 18.23 39.81 32.57 14.44 19.79 22.07 44.99 22.07 72.85 0 36.77-11.54 68.12-33.36 90.68-20.67 21.37-49.43 33.13-80.98 33.13z m-183.15 0.6h-62.15V523.45H222.22V896h-62.94V128h62.94v326.11h282.86V128h62.15v768z',
h4:
'M823.31 894.76h-52.82V778.48H634.31v-44.16l133.11-277.17h55.89v263.64h41.41v57.7h-41.41v116.27zM695.34 720.79h75.15V599.11c0-9.81 0.37-22.31 0.86-35.25-3.17 7.66-6.55 15.63-10.16 23.98l-0.17 0.37-65.68 132.58zM567.23 896h-62.15V523.45H222.22V896h-62.94V128h62.94v326.11h282.86V128h62.15v768z',
h5:
'M567.23 896h-62.15V523.45H222.22V896h-62.94V128h62.94v326.11h282.86V128h62.15v768z m181.59-0.16c-58.28 0-91.28-33.08-111.04-57.97l-3.36-4.24 28.84-48.43 7.06 8.89c30.07 37.86 54.74 42.97 74.91 42.97 16.75 0 32.15-8.23 43.37-23.16 12.45-16.58 19.03-39.78 19.03-67.1 0-27.27-5.93-49.76-17.16-65.04-10.5-14.28-25.51-21.83-43.44-21.83-19.59 0-31.69 7.27-49.89 22.55l-4.83 4.06-30.32-23.38 10.97-206.03h176v59.91H723.73l-6.59 97.77c12.81-6.63 25.67-9.67 40.24-9.67 31.08 0 57.49 11.98 76.37 34.65 20.55 24.67 30.97 60.1 30.97 105.32-0.01 98.95-58.31 150.73-115.9 150.73z',
h6:
'M567.22 895.92h-62.14V523.41H222.25v372.51h-62.93V128h62.93v326.08h282.83V128h62.14v767.92z m191.63 0.08c-36.45 0-67.06-18.74-88.53-54.2-22-36.32-33.62-90.02-33.62-155.31 0-75.99 13.35-134.66 39.68-174.4 23.41-35.32 56.12-54 94.61-54 33.88 0 62.8 15.03 85.97 44.68l3.66 4.69-33.35 45.27-6.25-8.98c-12.1-17.38-30.28-27.75-48.64-27.75-22.59 0-40.82 11.25-54.18 33.43-14.23 23.61-22.73 58.91-25.36 105.12 20.59-21.5 45.78-34.22 69.27-34.22 31.54 0 57.57 12.13 75.26 35.06 18.11 23.48 27.3 57.31 27.3 100.55 0 39.09-10.89 74.77-30.67 100.48-19.64 25.52-46.33 39.58-75.15 39.58z m-65.74-175.89c2.63 39.75 9.87 70.41 21.54 91.16 11.06 19.65 25.93 29.62 44.2 29.62 28.99 0 50.02-35.72 50.02-84.94 0-26.16-4.82-47.37-13.95-61.34-9.03-13.82-22.42-20.82-39.81-20.82-21.46-0.01-43.98 16.84-62 46.32z',
codeblock:
'M819.2 864H204.8c-42.35 0-76.8-35.89-76.8-80V240c0-44.11 34.45-80 76.8-80h614.4c42.35 0 76.8 35.89 76.8 80v544c0 44.11-34.45 80-76.8 80zM233.74 221.09c-23.02 0-41.74 19.58-41.74 43.64v494.55c0 24.06 18.72 43.64 41.74 43.64h556.52c23.02 0 41.74-19.57 41.74-43.64V264.73c0-24.06-18.72-43.64-41.74-43.64H233.74z m410.77 417.59l124.33-106.53c4.56-3.9 7.17-9.61 7.16-15.66 0-6.04-2.62-11.75-7.18-15.64L644.44 394.58l-0.08-0.07c-4.15-3.45-9.38-5.07-14.74-4.55-5.37 0.52-10.21 3.12-13.65 7.33-6.96 8.51-5.91 21.33 2.34 28.56l106.15 90.68-106.04 90.86c-5.59 4.64-8.33 12-7.14 19.23 1.2 7.3 6.21 13.39 13.08 15.91 2.23 0.82 4.55 1.21 6.85 1.21a20.11 20.11 0 0 0 13.3-5.06zM414.63 774.64c5.1-1.98 9.11-5.86 11.29-10.93L635.93 276.7c4.44-10.29-0.19-22.37-10.3-26.93-4.92-2.22-10.41-2.37-15.44-0.41-5.1 1.98-9.11 5.86-11.29 10.93L388.88 747.3c-4.44 10.28 0.18 22.36 10.3 26.93 2.63 1.19 5.42 1.78 8.21 1.78 2.45 0 4.89-0.46 7.24-1.37z m-20.52-129.77c8.5 0 16.14-5.45 19.02-13.56 2.84-8.03 0.43-17.11-6.01-22.61L299.6 516.99l107.47-91.67c5.54-4.65 8.23-12.01 7.03-19.19-1.21-7.22-6.17-13.28-12.95-15.81a20.027 20.027 0 0 0-20.06 3.61L255.2 501.31c-4.57 3.9-7.2 9.61-7.2 15.68 0 6.06 2.62 11.77 7.2 15.67l125.88 107.38a20 20 0 0 0 12.99 4.82c0.02 0.01 0.02 0.01 0.04 0.01z',
code:
'M686.87 681.71c-2.86 0-5.73-0.45-8.5-1.38-8.85-2.96-15.33-10.29-16.91-19.12-1.6-8.91 2-17.92 9.4-23.53L829.48 513.1 670.76 388.8c-5.33-4.28-8.57-10.3-9.12-16.94-0.55-6.57 1.61-12.96 6.08-17.97 9.08-10.2 24.78-11.64 35.74-3.29l0.08 0.06 183.01 143.31c6 4.7 9.44 11.66 9.44 19.08 0 7.43-3.43 14.39-9.43 19.09l-182.94 143.7c-4.72 3.84-10.7 5.87-16.75 5.87zM356.91 865.09c-3.4 0-6.86-0.65-10.16-2.01-6.53-2.7-11.49-7.7-13.96-14.08-2.37-6.12-2.13-12.77 0.67-18.72l308.99-656.84c5.75-12.23 20.9-17.85 33.78-12.53 6.53 2.7 11.49 7.7 13.96 14.09 2.37 6.12 2.13 12.77-0.67 18.72L380.54 850.56c-4.28 9.09-13.75 14.53-23.63 14.53zM339 682.25c-5.93 0-11.73-1.98-16.32-5.58L137.47 531.83c-6.02-4.71-9.47-11.67-9.47-19.11 0-7.44 3.45-14.41 9.47-19.11L322.7 348.77c6.94-5.51 16.59-7.13 25.13-4.21 8.73 2.99 15.15 10.27 16.74 19.01 1.61 8.84-1.93 17.84-9.24 23.47L194.59 512.72l160.78 125.72c8.53 6.67 11.7 17.8 7.87 27.69-3.73 9.64-13.46 16.12-24.21 16.12H339z',
quote:
'M328.25 527.53h120.17V896H128V617.21C128 312.66 234.77 149.55 448.42 128v138.19c-80.08 25.76-120.17 99.55-120.17 221.61v39.73z m447.62 0H896V896H575.58V617.21c0-304.55 106.74-467.66 320.32-489.21v138.19c-80.05 25.76-120.13 99.55-120.13 221.61v39.72h0.1z',
bold:
'M573.71 758.85h-192V594.28h192c45.53 0 82.29 36.76 82.29 82.29 0 45.53-36.76 82.28-82.29 82.28m-192-493.71h164.57c45.53 0 82.29 36.75 82.29 82.29s-36.75 82.29-82.29 82.29H381.71m307.21 70.76c53.21-37.3 90.51-98.19 90.51-153.05 0-123.98-96-219.43-219.43-219.43H217.14v768h386.19c115.2 0 203.52-93.26 203.52-207.91 0.01-83.38-47.17-154.7-117.93-187.61z',
image:
'M550.34 397.58h210.87L550.34 186.71v210.87M281.96 129.2h306.72l230.04 230.04v460.08c0 42.17-34.51 76.68-76.68 76.68H281.96c-42.56 0-76.68-34.51-76.68-76.68V205.88a76.406 76.406 0 0 1 22.38-54.3 76.406 76.406 0 0 1 54.3-22.38m0 690.12h460.08V512.6L588.68 665.96 512 589.28 281.96 819.32m76.68-421.74c-42.17 0-76.68 34.51-76.68 76.68s34.51 76.68 76.68 76.68 76.68-34.51 76.68-76.68-34.51-76.68-76.68-76.68z',
attach:
'M821.2 466.43L501.65 785.98c-63.92 63.92-168.48 63.92-232.4 0-63.91-63.91-63.91-168.48 0-232.4l334.07-334.07c40.67-40.67 104.58-40.67 145.25 0s40.67 104.58 0 145.25L414.5 698.83c-15.97 15.97-42.13 15.97-58.1 0-15.97-15.97-15.97-42.13 0-58.1l275.97-275.97-43.57-43.57-275.98 275.96c-40.66 40.67-40.67 104.58 0 145.25s104.58 40.67 145.25 0l334.07-334.07c63.91-63.91 63.92-168.48 0-232.4s-168.49-63.91-232.4 0L225.68 510c-88.6 88.6-88.6 230.95 0 319.55s230.95 88.6 319.55 0L864.78 510l-43.58-43.57z',
link:
'M338.96 895.99c-27.5 0-53.33-10.68-72.73-30.08L158.08 757.76c-19.4-19.4-30.08-45.24-30.08-72.74 0-27.5 10.68-53.33 30.08-72.71l135.97-135.97c12.84-12.84 33.74-12.84 46.58 0 6.22 6.22 9.65 14.5 9.65 23.3s-3.43 17.07-9.65 23.29L204.66 658.88c-14.41 14.41-14.41 37.87 0 52.29l108.15 108.15c14.41 14.42 37.87 14.42 52.31 0l216.29-216.3c6.94-6.94 10.75-16.22 10.75-26.14 0-9.92-3.82-19.21-10.75-26.15l-59.47-59.49c-6.22-6.22-9.65-14.49-9.65-23.29 0-8.8 3.43-17.07 9.65-23.29 12.84-12.84 33.74-12.84 46.58 0L628 504.14c19.38 19.36 30.06 45.19 30.06 72.73 0 27.54-10.67 53.37-30.06 72.73L411.71 865.9c-19.39 19.41-45.23 30.09-72.75 30.09zM478.8 588.96c-8.8 0-17.07-3.43-23.29-9.65l-59.49-59.48c-19.38-19.37-30.06-45.2-30.06-72.73s10.67-53.37 30.06-72.75l216.31-216.3c19.36-19.37 45.19-30.04 72.73-30.04 27.51 0 53.34 10.66 72.73 30.03L865.94 266.2c19.38 19.38 30.06 45.21 30.06 72.74 0 27.53-10.68 53.37-30.06 72.74L729.98 547.64c-6.22 6.22-14.49 9.65-23.29 9.65s-17.07-3.43-23.29-9.65c-6.22-6.22-9.65-14.49-9.65-23.29 0-8.8 3.43-17.07 9.65-23.29l135.96-135.97c6.93-6.95 10.76-16.23 10.76-26.15 0-9.92-3.82-19.21-10.76-26.15L711.2 204.64c-6.59-6.58-16.12-10.36-26.15-10.36h-0.02c-10.03 0-19.55 3.78-26.12 10.36L442.6 420.94c-6.94 6.94-10.76 16.23-10.76 26.15 0 9.92 3.82 19.2 10.76 26.14l59.49 59.49c12.84 12.85 12.84 33.74 0 46.58-6.22 6.23-14.49 9.66-23.29 9.66z',
order:
'M320 215v82h576v-82M320 553h576v-82H320m0 338h576v-82H320m-192 9h85.33v16h-42.67v32h42.67v16H128v32h128V704H128v32z m0-256h76.8L128 547.2V576h128v-32h-76.8l76.8-67.2V448H128m64-128h42V192h-84v32h42',
italic:
'M391.7 128c0.67 38.67 1.33 77.33 2 116h146c-58 169.14-115.99 338.28-173.99 507.42-67.14 0.17-134.28 0.33-201.42 0.5 0.06 47.64 0.11 95.28 0.17 142.92 176.19 0.39 352.39 0.77 528.58 1.16V751.42H510.29C571.1 581.95 631.9 412.47 692.7 243h167V128h-468z',
through:
'M896 514.38v69.72H742.75c34.91 74.61 33.16 278.89-217.48 278.89-290.79 1.74-279.62-226.6-279.62-226.6l138.59 1.74c1.05 117.48 110.31 117.48 131.96 116.09 22.34-1.74 105.77-1.39 112.41-82.97 2.79-38-35.61-66.93-77.5-87.15H128v-69.72h768M770.68 371.1l-138.94-1.04s5.93-96.57-114.5-96.92c-120.44-0.7-109.96 76.69-109.96 86.45 1.4 9.77 11.87 57.88 104.73 80.88H292.42S170.59 205.85 468.01 165.75c304.06-41.82 303.36 206.04 302.67 205.35z',
list:
'M320 215v82h576v-82M320 553h576v-82H320m0 338h576v-82H320m-64-215c0 35.35-28.65 64-64 64s-64-28.65-64-64 28.65-64 64-64 64 28.65 64 64z m0 256c0 35.35-28.65 64-64 64s-64-28.65-64-64 28.65-64 64-64 64 28.65 64 64z m0-512c0 35.35-28.65 64-64 64s-64-28.65-64-64 28.65-64 64-64 64 28.65 64 64z',
line:
'M868.29 539.71H155.71c-15.28 0-27.71-12.43-27.71-27.71s12.43-27.71 27.71-27.71h712.58c15.28 0 27.71 12.43 27.71 27.71s-12.43 27.71-27.71 27.71z',
preview:
'M512 253.14c-174.51 0-322.97 106.97-384 258.86 61.03 151.89 209.49 258.86 384 258.86S834.97 663.89 896 512c-61.03-151.89-209.49-258.86-384-258.86z m0 431.49c-96 0-174.51-77.66-174.51-172.63S416 339.37 512 339.37 686.51 417.03 686.51 512 608 684.63 512 684.63z m0-276.17c-57.6 0-104.74 46.63-104.74 103.54S454.4 615.54 512 615.54 616.74 568.91 616.74 512 569.6 408.46 512 408.46z',
table:
'M860 159H164c-19.88 0-36 16.93-36 37.82v630.36c0 20.89 16.12 37.82 36 37.82h696c19.88 0 36-16.93 36-37.82V196.82c0-20.89-16.12-37.82-36-37.82z m-422 26.74c20.98 0 38 17.01 38 38 0 20.98-17.02 38-38 38-20.99 0-38-17.01-38-38s17.02-38 38-38z m-108 0c20.98 0 38 17.01 38 38 0 20.98-17.02 38-38 38-20.99 0-38-17.01-38-38s17.01-38 38-38z m-108 0c20.98 0 38 17.01 38 38 0 20.98-17.01 38-38 38s-38-17.01-38-38 17.01-38 38-38zM664 808V664h176v144H664z m-64-144v144H424V664h176z m-416 0h176v144H184V664z m480-190h176v144H664V474zM424 618V474h176v144H424z m-64 0H184V474h176v144z m480-338v144H664V280h176z m-240 0v144H424V280h176z m-385.54 0H360v144H184V280h30.46z',
fullscreen:
'M597.33 449.42l40.02 41.25 94.25-93.75 36.4 35V320H654.14l37.44 36.3-94.25 93.12z m-170.66 124.6l-40.41-40.68-93.53 93.93L256 592.76l0.95 111.24 113.96-0.9-37.78-35.78 93.54-93.3z m211.91-40.69l-41.25 40.02 93.75 94.24-35 36.4H768V590.14l-36.3 37.44-93.12-94.25z m-252.86-42.66l40.95-40.23-93.84-93.87L367.56 320l-111.56 0.5 0.48 113.91 36.02-37.62 93.22 93.88zM848 832H176c-26.47 0-48-19.57-48-43.64V235.64c0-24.06 21.53-43.64 48-43.64h672c26.47 0 48 19.58 48 43.64v552.73c0 24.06-21.53 43.63-48 43.63zM206.55 256c-8.02 0-14.55 5.74-14.55 12.8v486.4c0 7.06 6.53 12.8 14.55 12.8h610.91c8.02 0 14.55-5.74 14.55-12.8V268.8c0-7.06-6.53-12.8-14.55-12.8H206.55z'
}

View File

@ -1,372 +0,0 @@
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2017-04-19 21:17:26
*
*/
'use strict'
import '../../layer/index'
import 'css/meditor__attach.scss'
const $doc = Anot(document)
const LANGUAGES = {
zh: {
IMAGE: {
REMOTE: '远程图片',
LOCAL: '本地上传',
MANAGE: '图片管理',
ALT: '图片描述',
ADDRESS: '图片地址'
},
FILE: {
REMOTE: '远程附件',
LOCAL: '本地上传',
MANAGE: '附件管理',
ALT: '附件描述',
ADDRESS: '附件地址'
},
BTN: '确定',
INSERT: '插入',
CHOOSE: '选择文件',
LIMIT: '上传大小限制:单文件最大 ',
SCREENSHOT: '截图',
COMPRESS: '截图处理中...',
TABLE: {
NAME: '文件名',
PROGRESS: '上传进度',
HANDLE: '操作'
},
ERROR: {
TYPE: '文件类型错误',
SIZE: '文件体积过大',
EMPTY: '描述和地址不能为空',
UNDEFINED: '在node-webkit中saveAttach回调必须定义'
}
},
en: {
IMAGE: {
REMOTE: 'Remote image',
LOCAL: 'Local image',
MANAGE: 'Manage',
ALT: 'Image alt text',
ADDRESS: 'Image address'
},
FILE: {
REMOTE: 'Remote file',
LOCAL: 'Local file',
MANAGE: 'Manage',
ALT: 'File alt text',
ADDRESS: 'File address'
},
BTN: 'OK',
INSERT: 'insert',
CHOOSE: 'Choose file',
LIMIT: 'Size of upload file limit to ',
SCREENSHOT: 'screenshot',
COMPRESS: 'Screenshot compressing...',
TABLE: {
NAME: 'name',
PROGRESS: 'progress',
HANDLE: 'handle'
},
ERROR: {
TYPE: 'Forbidden type',
SIZE: 'Too large',
EMPTY: 'Alt text and address can not be null',
UNDEFINED: 'Function saveAttach is not defined'
}
}
}
LANGUAGES['zh-CN'] = LANGUAGES.zh
LANGUAGES['zh-TW'] = LANGUAGES.zh
const lang =
LANGUAGES[window.__ENV_LANG__ || navigator.language] || LANGUAGES.en
const fixCont = function(vm, tool) {
let limit = false
if (vm.props.uploadSizeLimit) {
limit = (vm.props.uploadSizeLimit / (1024 * 1024)).toFixed(2)
}
return `
<dl class="do-meditor-attach">
<dt class="tab-box" :drag="do-layer" data-limit="window">
<span class="item" :class="active:tab === 1" :click="switchTab(1)">
${lang[tool].REMOTE}
</span>
<span class="item" :class="active:tab === 2" :click="switchTab(2)">
${lang[tool].LOCAL}
</span>
<span class="item" :class="active:tab === 3" :click="switchTab(3)">
${lang[tool].MANAGE}
</span>
</dt>
<dd class="cont-box">
<div class="remote" :visible="tab === 1">
<section class="section do-fn-cl">
<input
class="do-meditor__input"
:duplex="attachAlt"
placeholder="${lang[tool].ALT}" />
</section>
<section class="section do-fn-cl">
<input
class="do-meditor__input"
:duplex="attach"
placeholder="${lang[tool].ADDRESS}" />
</section>
<section class="section do-fn-cl">
<a
href="javascript:;"
class="do-meditor__button submit"
:click="confirm">${lang.BTN}</a>
</section>
</div>
<div class="local" :visible="tab === 2">
<div class="select-file">
<input ref="attach" multiple :change="change" type="file" class="hide" />
<span class="file" :click="select">${lang.CHOOSE}</span>
${limit ? `<span class="tips">(${lang.LIMIT + limit} MB)</span>` : ''}
</div>
<ul class="upload-box">
<li class="thead">
<span class="col">${lang.TABLE.NAME}</span>
<span class="col">${lang.TABLE.PROGRESS}</span>
<span class="col">${lang.TABLE.HANDLE}</span>
</li>
<li class="tbody">
<p :for="uploadQueue">
<span
class="col do-fn-ell"
:text="el.name"
:layer-tips="el.name"></span>
<span class="col" :html="el.progress"></span>
<span class="col"><a class="insert" :click="insert(el)">${
lang.INSERT
}</a></span>
</p>
</li>
</ul>
</div>
<div class="manager" :visible="tab === 3">
<ul class="list-box">
<li
class="item"
:for="attachList"
:layer-tips="el.name"
:click="insert(el)">
<span class="thumb" :html="el.thumb"></span>
<p class="name" :text="el.name"></p>
</li>
</ul>
</div>
</dd>
</dl>`
}
/**
* [uploadFile 文件上传]
* @param {[type]} vm [vm对象]
* @param {[type]} tool [image/file]
*/
function uploadFile(vm, tool) {
for (let it of this.files) {
let ext = it.name.slice(it.name.lastIndexOf('.'))
if (tool === 'IMAGE' && !/^\.(jpg|jpeg|png|gif|bmp|webp|ico)$/.test(ext)) {
this.uploadQueue.push({
name: it.name,
progress: '<span class="red">0%(' + lang.ERROR.TYPE + ')</span>',
url: ''
})
continue
}
if (vm.props.uploadSizeLimit && it.size > vm.props.uploadSizeLimit) {
this.uploadQueue.push({
name: it.name,
progress: '<span class="red">0%(' + lang.ERROR.SIZE + ')</span>',
url: ''
})
continue
}
let fixName = new Date().format('YmdHis') + ext
let attach = { name: it.name, fixName, progress: '100%', url: '' }
if (vm.props.saveAttach) {
vm.props
.saveAttach(attach, it)
.then(url => {
attach.url = url
this.uploadQueue.push(attach)
})
.catch(err => {
Anot.error(err)
})
} else {
layer.toast(lang.ERROR.UNDEFINED, 'error')
}
}
}
function uploadScreenshot(vm, blob) {
let name = new Date().format('YmdHis') + '.jpg'
let attach = { name, url: '' }
if (vm.props.saveAttach) {
vm.props
.saveAttach(attach, blob)
.then(url => {
vm.insert(`![${lang.SCREENSHOT}](${url})`)
})
.catch(err => {
Anot.error(err)
})
} else {
layer.toast(lang.ERROR.UNDEFINED, 'error')
}
}
function showDialog(elem, vm, tool) {
let offset = Anot(elem).offset()
layer.open({
type: 7,
menubar: false,
fixed: true,
maskClose: true,
offset: [offset.top + 35 - $doc.scrollTop()],
shift: {
top: offset.top - $doc.scrollTop()
},
tab: 2,
attach: '',
attachAlt: '',
uploadQueue: [], //当前上传的列表
attachList: [], //附件管理列表
switchTab(id) {
this.tab = id
if (id === 3) {
this.attachList.clear()
if (vm.props.getAttachList) {
vm.props
.getAttachList(tool)
.then(list => {
list.forEach(it => {
let ext = it.name.slice(it.name.lastIndexOf('.'))
it.isImage = /^\.(jpg|jpeg|png|gif|bmp|webp|ico)$/.test(ext)
it.thumb = it.isImage
? `<img src="${it.url}" />`
: `<em class="do-icon-txt"></em>`
})
return list
})
.then(list => {
list = list.filter(it => {
if (tool === 'IMAGE') {
return it.isImage
}
return true
})
this.attachList = list
})
}
}
},
select() {
let ev = document.createEvent('MouseEvent')
ev.initEvent('click', false, false)
this.$refs.attach.dispatchEvent(ev)
},
change(ev) {
this.files = ev.target.files
uploadFile.call(this, vm, tool)
},
insert: function(it) {
if (!it.url) {
return
}
let val = `\n${tool === 'IMAGE' ? '!' : ''}[${it.name}](${it.url})`
vm.insert(val)
},
confirm: function() {
if (!this.attach || !this.attachAlt) {
return layer.toast(lang.ERROR.EMPTY, 'error')
}
let val = `\n${tool === 'IMAGE' ? '!' : ''}[${this.attachAlt}](${
this.attach
})`
vm.insert(val)
this.close()
},
content: fixCont(vm, tool)
})
}
const plugin = {
__init__(ME) {
Object.assign(ME.vm.addon, {
attach(elem) {
showDialog(elem, this, 'FILE')
},
image(elem) {
showDialog(elem, this, 'IMAGE')
}
})
ME.vm.$refs.editor.addEventListener('paste', function(ev) {
ev.preventDefault()
let txt = ev.clipboardData.getData('text/plain')
//文本类型直接默认处理
if (txt) {
return
}
if (ev.clipboardData.items) {
let items = ev.clipboardData.items
let len = items.length
let blob = null
for (let it of items) {
if (it.type.indexOf('image') > -1) {
blob = it.getAsFile()
}
}
if (blob !== null) {
layer.toast(lang.COMPRESS)
// 压缩截图,避免文件过大
let reader = new FileReader()
reader.onload = function() {
let img = document.createElement('img')
let canvas = document.createElement('canvas')
img.onload = function() {
canvas.width = img.width
canvas.height = img.height
let ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(this, 0, 0, canvas.width, canvas.height)
canvas.toBlob(
obj => {
uploadScreenshot(ME.vm, obj)
},
'image/jpeg',
0.8
)
}
img.src = this.result
}
reader.readAsDataURL(blob)
}
}
})
}
}
export default plugin

View File

@ -1,497 +0,0 @@
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2017-04-19 21:17:26
*
*/
'use strict'
import '../../layer/index'
import 'css/meditor__attach.scss'
const $doc = Anot(document)
const LANGUAGES = {
zh: {
IMAGE: {
REMOTE: '远程图片',
LOCAL: '本地上传',
MANAGE: '图片管理',
ALT: '图片描述',
ADDRESS: '图片地址'
},
FILE: {
REMOTE: '远程附件',
LOCAL: '本地上传',
MANAGE: '附件管理',
ALT: '附件描述',
ADDRESS: '附件地址'
},
BTN: '确定',
INSERT: '插入',
CHOOSE: '选择文件',
LIMIT: '上传大小限制:单文件最大 ',
SCREENSHOT: '截图',
COMPRESS: '截图处理中...',
TABLE: {
NAME: '文件名',
PROGRESS: '上传进度',
HANDLE: '操作'
},
ERROR: {
TYPE: '文件类型错误',
SIZE: '文件体积过大',
EMPTY: '描述和地址不能为空'
}
},
en: {
IMAGE: {
REMOTE: 'Remote image',
LOCAL: 'Local image',
MANAGE: 'Manage',
ALT: 'Image alt text',
ADDRESS: 'Image address'
},
FILE: {
REMOTE: 'Remote file',
LOCAL: 'Local file',
MANAGE: 'Manage',
ALT: 'File alt text',
ADDRESS: 'File address'
},
BTN: 'OK',
INSERT: 'insert',
CHOOSE: 'Choose file',
LIMIT: 'Size of upload file limit to ',
SCREENSHOT: 'screenshot',
COMPRESS: 'Screenshot compressing...',
TABLE: {
NAME: 'name',
PROGRESS: 'progress',
HANDLE: 'handle'
},
ERROR: {
TYPE: 'Forbidden type',
SIZE: 'Too large',
EMPTY: 'Alt text and address can not be null'
}
}
}
LANGUAGES['zh-CN'] = LANGUAGES.zh
LANGUAGES['zh-TW'] = LANGUAGES.zh
const lang =
LANGUAGES[window.__ENV_LANG__ || navigator.language] || LANGUAGES.en
class Uploader {
constructor(url) {
this.url = url
this.xhr = new XMLHttpRequest()
this.form = new FormData()
}
field(key, val) {
this.form.append(key, val)
return this
}
onProgress(fn) {
this.progress = fn
return this
}
then(cb) {
if (!this.url) {
Anot.error('invalid upload url')
}
let defer = Promise.defer()
this.xhr.open('POST', this.url, true)
this.xhr.upload.addEventListener(
'progress',
evt => {
if (evt.lengthComputable && this.progress) {
let res = Math.round((evt.loaded * 100) / evt.total)
this.progress(res)
}
},
false
)
this.xhr.onreadystatechange = () => {
if (this.xhr.readyState === 4) {
if (this.xhr.status >= 200 && this.xhr.status < 205) {
let res = this.xhr.responseText
try {
res = JSON.parse(res)
} catch (err) {}
defer.resolve(cb(res))
} else {
defer.reject(this.xhr)
}
}
}
this.xhr.send(this.form)
return defer.promise
}
}
const fixCont = function(vm, tool) {
let limit = false
if (vm.props.uploadSizeLimit) {
limit = (vm.props.uploadSizeLimit / (1024 * 1024)).toFixed(2)
}
return `
<dl class="do-meditor-attach">
<dt class="tab-box" :drag="do-layer" data-limit="window">
<span class="item" :class="active:tab === 1" :click="switchTab(1)">
${lang[tool].REMOTE}
</span>
<span class="item" :class="active:tab === 2" :click="switchTab(2)">
${lang[tool].LOCAL}
</span>
<span class="item" :class="active:tab === 3" :click="switchTab(3)">
${lang[tool].MANAGE}
</span>
</dt>
<dd class="cont-box">
<div class="remote" :visible="tab === 1">
<section class="section do-fn-cl">
<input
class="do-meditor__input"
:duplex="attachAlt"
placeholder="${lang[tool].ALT}" />
</section>
<section class="section do-fn-cl">
<input
class="do-meditor__input"
:duplex="attach"
placeholder="${lang[tool].ADDRESS}" />
</section>
<section class="section do-fn-cl">
<a
href="javascript:;"
class="do-meditor__button submit"
:click="confirm">${lang.BTN}</a>
</section>
</div>
<div class="local" :visible="tab === 2">
<div class="select-file">
<input ref="attach" multiple :change="change" type="file" class="hide" />
<span class="file" :click="select">${lang.CHOOSE}</span>
${limit ? `<span class="tips">(${lang.LIMIT + limit} MB)</span>` : ''}
</div>
<ul class="upload-box">
<li class="thead">
<span class="col">${lang.TABLE.NAME}</span>
<span class="col">${lang.TABLE.PROGRESS}</span>
<span class="col">${lang.TABLE.HANDLE}</span>
</li>
<li class="tbody">
<p :for="uploadQueue">
<span
class="col do-fn-ell"
:text="el.name"
:layer-tips="el.name"></span>
<span class="col" :html="el.progress"></span>
<span class="col"><a class="insert" :click="insert(el)">${
lang.INSERT
}</a></span>
</p>
</li>
</ul>
</div>
<div class="manager" :visible="tab === 3">
<ul class="list-box">
<li
class="item"
:for="attachList"
:layer-tips="el.name"
:click="insert(el)">
<span class="thumb" :html="el.thumb"></span>
<p class="name" :text="el.name"></p>
</li>
</ul>
</div>
</dd>
</dl>`
}
/**
* [uploadFile 文件上传]
* @param {[type]} vm [vm对象]
* @param {[type]} tool [image/file]
*/
function uploadFile(vm, tool) {
for (let it of this.files) {
let ext = it.name.slice(it.name.lastIndexOf('.'))
if (tool === 'IMAGE' && !/^\.(jpg|jpeg|png|gif|bmp|webp|ico)$/.test(ext)) {
this.uploadQueue.push({
name: it.name,
progress: '<span class="red">0%(' + lang.ERROR.TYPE + ')</span>',
url: ''
})
continue
}
if (vm.props.uploadSizeLimit && it.size > vm.props.uploadSizeLimit) {
this.uploadQueue.push({
name: it.name,
progress: '<span class="red">0%(' + lang.ERROR.SIZE + ')</span>',
url: ''
})
continue
}
let idx = this.uploadQueue.length
let fixName = new Date().format('YmdHis') + ext
let attach = { name: it.name, fixName, progress: '0%', url: '' }
let upload = new Uploader(vm.props.uploadUrl).field('file', it)
this.uploadQueue.push(attach)
if (vm.props.beforeUpload) {
vm.props
.beforeUpload(attach, upload)
.then(next => {
if (!next) {
return Promise.reject('something wrong with beforeUpload')
}
return upload
.onProgress(val => {
this.uploadQueue[idx].progress = val + '%'
})
.then(res => {
if (vm.props.afterUpload) {
return vm.props.afterUpload(res)
} else {
return res.data.url
}
})
})
.then(url => {
this.uploadQueue[idx].url = url
})
.catch(err => {
Anot.error(err)
})
} else {
upload
.onProgress(val => {
this.uploadQueue[idx].progress = val + '%'
})
.then(res => {
if (vm.props.afterUpload) {
return vm.props.afterUpload(res)
} else {
return res.data.url
}
})
.then(url => {
this.uploadQueue[idx].url = url
})
.catch(err => {
Anot.error(err)
})
}
}
}
function uploadScreenshot(vm, blob) {
let name = new Date().format('YmdHis') + '.jpg'
let attach = { name, url: '' }
let upload = new Uploader(vm.props.uploadUrl).field('file', blob)
if (vm.props.beforeUpload) {
vm.props
.beforeUpload(attach, upload)
.then(next => {
if (!next) {
return Promise.reject('something wrong with beforeUpload')
}
return upload.then(res => {
if (vm.props.afterUpload) {
return vm.props.afterUpload(res)
} else {
return res.data.url
}
})
})
.then(url => {
vm.insert(`![${lang.SCREENSHOT}](${url})`)
})
.catch(err => {
Anot.error(err)
})
} else {
upload
.then(res => {
if (vm.props.afterUpload) {
return vm.props.afterUpload(res)
} else {
return res.data.url
}
})
.then(url => {
vm.insert(`![${lang.SCREENSHOT}](${url})`)
})
}
}
function showDialog(elem, vm, tool) {
let offset = Anot(elem).offset()
layer.open({
type: 7,
menubar: false,
fixed: true,
maskClose: true,
offset: [offset.top + 35 - $doc.scrollTop()],
shift: {
top: offset.top - $doc.scrollTop()
},
tab: 2,
attach: '',
attachAlt: '',
uploadQueue: [], //当前上传的列表
attachList: [], //附件管理列表
switchTab(id) {
this.tab = id
if (id === 3) {
this.attachList.clear()
if (vm.props.getAttachList) {
vm.props
.getAttachList(tool)
.then(list => {
list.forEach(it => {
let ext = it.name.slice(it.name.lastIndexOf('.'))
it.isImage = /^\.(jpg|jpeg|png|gif|bmp|webp|ico)$/.test(ext)
it.thumb = it.isImage
? `<img src="${it.url}" />`
: `<em class="do-icon-txt"></em>`
})
return list
})
.then(list => {
list = list.filter(it => {
if (tool === 'IMAGE') {
return it.isImage
}
return true
})
this.attachList = list
})
}
}
},
select() {
let ev = document.createEvent('MouseEvent')
ev.initEvent('click', false, false)
this.$refs.attach.dispatchEvent(ev)
},
change(ev) {
this.files = ev.target.files
uploadFile.call(this, vm, tool)
},
insert: function(it) {
if (!it.url) {
return
}
let val = `\n${tool === 'IMAGE' ? '!' : ''}[${it.name}](${it.url})`
vm.insert(val)
},
confirm: function() {
if (!this.attach || !this.attachAlt) {
return layer.toast(lang.ERROR.EMPTY, 'error')
}
let val = `\n${tool === 'IMAGE' ? '!' : ''}[${this.attachAlt}](${
this.attach
})`
vm.insert(val)
this.close()
},
content: fixCont(vm, tool)
})
}
const plugin = {
__init__(ME) {
Object.assign(ME.vm.addon, {
attach(elem) {
showDialog(elem, this, 'FILE')
},
image(elem) {
showDialog(elem, this, 'IMAGE')
}
})
ME.vm.$refs.editor.addEventListener('paste', function(ev) {
ev.preventDefault()
let txt = ev.clipboardData.getData('text/plain')
//文本类型直接默认处理
if (txt) {
return
}
if (ev.clipboardData.items) {
let items = ev.clipboardData.items
let len = items.length
let blob = null
for (let it of items) {
if (it.type.indexOf('image') > -1) {
blob = it.getAsFile()
}
}
if (blob !== null) {
layer.toast(lang.COMPRESS)
// 压缩截图,避免文件过大
let reader = new FileReader()
reader.onload = function() {
let img = document.createElement('img')
let canvas = document.createElement('canvas')
img.onload = function() {
canvas.width = img.width
canvas.height = img.height
let ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(this, 0, 0, canvas.width, canvas.height)
// chrome, Firefox, 以及支持toBlob 设置图片质量
if (canvas.toBlob && (window.chrome || window.sidebar)) {
canvas.toBlob(
function(obj) {
uploadScreenshot(ME.vm, obj)
},
'image/jpeg',
0.8
)
} else {
// IE和Safari的toBlob方法还不支持图片质量的设定
// 需要先转base64再转回Blob
let base64 = canvas.toDataURL('image/jpeg', 0.8)
let buf = atob(base64.split(',')[1])
let intArr = new Uint8Array(buf.length)
let obj = null
for (let i = 0; i < buf.length; i++) {
intArr[i] = buf.charCodeAt(i)
}
obj = new Blob([intArr], { type: 'image/jpeg' })
uploadScreenshot(ME.vm, obj)
}
}
img.src = this.result
}
reader.readAsDataURL(blob)
}
}
})
}
}
export default plugin

View File

@ -1,531 +0,0 @@
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2017-04-17 21:41:48
*
*/
'use strict'
import '../../layer/index'
function objArr(num) {
let arr = []
while (num > 0) {
arr.push({ v: 0 })
num--
}
return arr
}
function trim(str, sign) {
return str.replace(new RegExp('^' + sign + '|' + sign + '$', 'g'), '')
}
const $doc = Anot(document)
const addon = {
h1: function(elem) {
let that = this
let offset = Anot(elem).offset()
let wrap = this.selection(true) || Anot.ui.meditor.lang.PLACEHOLDER
layer.open({
type: 7,
menubar: false,
maskClose: true,
fixed: true,
insert: function(level) {
wrap = wrap.replace(/^(#{1,6} )?/, '#'.repeat(level) + ' ')
that.insert(wrap, true)
this.close()
},
offset: [
offset.top + 35 - $doc.scrollTop(),
'auto',
'auto',
offset.left - $doc.scrollLeft()
],
shift: {
top: offset.top - $doc.scrollTop(),
left: offset.left - $doc.scrollLeft()
},
content: `
<ul class="do-meditor-h1 do-fn-noselect">
<li :click="insert(1)" class="h1"><i class="do-meditor__icon icon-h1"></i>${
Anot.ui.meditor.lang.HEADERS.H1
}</li>
<li :click="insert(2)" class="h2"><i class="do-meditor__icon icon-h2"></i>${
Anot.ui.meditor.lang.HEADERS.H2
}</li>
<li :click="insert(3)" class="h3"><i class="do-meditor__icon icon-h3"></i>${
Anot.ui.meditor.lang.HEADERS.H3
}</li>
<li :click="insert(4)" class="h4"><i class="do-meditor__icon icon-h4"></i>${
Anot.ui.meditor.lang.HEADERS.H4
}</li>
<li :click="insert(5)" class="h5"><i class="do-meditor__icon icon-h5"></i>${
Anot.ui.meditor.lang.HEADERS.H5
}</li>
<li :click="insert(6)" class="h6"><i class="do-meditor__icon icon-h6"></i>${
Anot.ui.meditor.lang.HEADERS.H6
}</li>
</ul>`
})
},
quote: function(elem) {
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
wrap = '> ' + wrap
this.insert(wrap, true)
},
bold: function(elem) {
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
let wraped = trim(wrap, '\\*\\*')
wrap = wrap === wraped ? '**' + wrap + '**' : wraped
this.insert(wrap, true)
},
italic: function(elem) {
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
let wraped = trim(wrap, '_')
wrap = wrap === wraped ? '_' + wrap + '_' : wraped
this.insert(wrap, true)
},
through: function(elem) {
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
let wraped = trim(wrap, '~~')
wrap = wrap === wraped ? '~~' + wrap + '~~' : wraped
this.insert(wrap, true)
},
unordered: function(elem) {
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
wrap = '* ' + wrap
this.insert(wrap, false)
},
ordered: function(elem) {
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
wrap = '1. ' + wrap
this.insert(wrap, false)
},
hr: function(elem) {
this.insert('\n\n---\n\n', false)
},
link: function(elem) {
let that = this
let offset = Anot(elem).offset()
let wrap = this.selection() || ''
layer.open({
type: 7,
menubar: false,
maskClose: true,
fixed: true,
link: '',
linkName: wrap,
linkTarget: 1,
insert: function() {
if (!this.link || !this.linkName) {
return layer.toast(Anot.ui.meditor.lang.LINK.ERROR, 'error')
}
let val = `[${this.linkName}](${this.link} ${
this.linkTarget === 1 ? ' "target=_blank"' : ''
})`
that.insert(val, false)
this.close()
},
offset: [
offset.top + 35 - $doc.scrollTop(),
'auto',
'auto',
offset.left - $doc.scrollLeft()
],
shift: {
top: offset.top - $doc.scrollTop(),
left: offset.left - $doc.scrollLeft()
},
content: `
<div class="do-meditor-common">
<section>
<input class="do-meditor__input" :duplex="linkName" placeholder="${
Anot.ui.meditor.lang.LINK.ALT
}"/>
</section>
<section>
<input class="do-meditor__input" :duplex="link" placeholder="${
Anot.ui.meditor.lang.LINK.URL
}"/>
</section>
<section>
<label class="label">
<input
name="link"
type="radio"
class="radio"
:duplex-number="linkTarget"
value="1"/>
${Anot.ui.meditor.lang.TARGET.BLANK}
</label>
<label class="label">
<input
name="link"
type="radio"
class="radio"
:duplex-number="linkTarget"
value="2"/>
${Anot.ui.meditor.lang.TARGET.SELF}
</label>
</section>
<section>
<a
href="javascript:;"
class="do-meditor__button submit"
:click="insert">${Anot.ui.meditor.lang.BTN.YES}</a>
</section>
</div>`
})
},
time: function(elem) {
this.insert(new Date().format(), false)
},
face: function(elem) {
let that = this
let offset = Anot(elem).offset()
layer.open({
type: 7,
title: Anot.ui.meditor.lang.LAYER.FACE_TITLE,
fixed: true,
maskClose: true,
arr: [
'😀',
'😅',
'😂',
'🤣',
'😇',
'😉',
'😍',
'😗',
'😋',
'😛',
'😜',
'🤨',
'🧐',
'🤓',
'😎',
'😞',
'😔',
'😭',
'😤',
'😡',
'😱',
'😰',
'😓',
'😬',
'🙄',
'😴',
'😪',
'🤮',
'😷',
'💩',
'👻',
'💀',
'🤝',
'👎',
'👍',
'🙏'
],
offset: [
offset.top + 35 - $doc.scrollTop(),
'auto',
'auto',
offset.left - $doc.scrollLeft()
],
shift: {
top: offset.top - $doc.scrollTop(),
left: offset.left - $doc.scrollLeft()
},
content: `
<ul class="do-meditor-face">
<li class="item" :for="arr">
<span :html="el" :click="insert(el)"></span>
</li>
</ul>`,
insert: function(val) {
that.insert(val, false)
}
})
},
table: function(elem) {
let that = this
let offset = Anot(elem).offset()
layer.open({
type: 7,
title: `0 ${Anot.ui.meditor.lang.TABLE.ROW} x 0 ${
Anot.ui.meditor.lang.TABLE.COLUMN
}`,
fixed: true,
maskClose: true,
offset: [
offset.top + 35 - $doc.scrollTop(),
'auto',
'auto',
offset.left - $doc.scrollLeft()
],
shift: {
top: offset.top - $doc.scrollTop(),
left: offset.left - $doc.scrollLeft()
},
matrix: objArr(10).map(function() {
return objArr(10)
}),
content: `
<ul class="do-meditor-table" ref="table">
<li :for="matrix">
<span
:for="o in el"
:class="{active: o.v}"
:data="{x: $index, y: $outer.$index}"></span>
</li>
</ul>`,
success: function() {
let tb = this.$refs.table
let lastx, lasty
let { lang } = Anot.ui.meditor
Anot(tb).bind('mousemove', ev => {
if (ev.target.nodeName === 'SPAN') {
let x = ev.target.dataset.x - 0
let y = ev.target.dataset.y - 0
if (x === lastx && y === lasty) {
return
}
lastx = x
lasty = y
this.title = `${y + 1} ${lang.TABLE.ROW} x ${x + 1} ${
lang.TABLE.COLUMN
}`
for (let i = 0; i <= 9; i++) {
for (let j = 0; j <= 9; j++) {
this.matrix[i][j].v = i <= y && j <= x ? 1 : 0
}
}
}
})
Anot(tb).bind('mouseleave', ev => {
lastx = -1
lasty = -1
this.title = `0 ${lang.TABLE.ROW} x 0 ${lang.TABLE.COLUMN}`
for (let i = 0; i <= 9; i++) {
for (let j = 0; j <= 9; j++) {
this.matrix[i][j].v = 0
}
}
})
Anot(tb).bind('click', ev => {
if (ev.target.nodeName === 'SPAN') {
let x = ev.target.dataset.x - 0 + 1
let y = ev.target.dataset.y - 0 + 1
let thead = `\n\n${('| ' + lang.TABLE.THEAD + ' ').repeat(x)}|\n`
let pipe = `${'| -- '.repeat(x)}|\n`
let tbody = ('| '.repeat(x) + '|\n').repeat(y)
that.insert(thead + pipe + tbody, false)
this.close()
}
})
}
})
},
image: function(elem) {
let that = this
let offset = Anot(elem).offset()
let wrap = this.selection() || ''
layer.open({
type: 7,
menubar: false,
maskClose: true,
fixed: true,
img: '',
imgAlt: wrap,
insert: function() {
if (!this.img || !this.imgAlt) {
return layer.toast(Anot.ui.meditor.lang.LINK.ERROR, 'error')
}
let val = `![${this.imgAlt}](${this.img})`
that.insert(val, false)
this.close()
},
offset: [
offset.top + 35 - $doc.scrollTop(),
'auto',
'auto',
offset.left - $doc.scrollLeft()
],
shift: {
top: offset.top - $doc.scrollTop(),
left: offset.left - $doc.scrollLeft()
},
content: `
<div class="do-meditor-common">
<section>
<input class="do-meditor__input" :duplex="imgAlt" placeholder="${
Anot.ui.meditor.lang.IMAGE.ALT
}"/>
</section>
<section>
<input class="do-meditor__input" :duplex="img" placeholder="${
Anot.ui.meditor.lang.IMAGE.URL
}"/>
</section>
<section>
<a
href="javascript:;"
class="do-meditor__button submit"
:click="insert">${Anot.ui.meditor.lang.BTN.YES}</a>
</section>
</div>
`
})
},
attach: function(elem) {
this.addon.link.call(this, elem)
},
inlinecode: function(elem) {
let wrap = this.selection() || Anot.ui.meditor.lang.PLACEHOLDER
let wraped = trim(wrap, '`')
wrap = wrap === wraped ? '`' + wrap + '`' : wraped
this.insert(wrap, true)
},
blockcode: function(elem) {
let that = this
let offset = Anot(elem).offset()
layer.open({
type: 7,
menubar: false,
fixed: true,
__lang__: [
{ id: 'asp' },
{ id: 'actionscript', name: 'ActionScript(3.0)/Flash/Flex' },
{ id: 'bash', name: 'Bash/Shell/Bat' },
{ id: 'css' },
{ id: 'c', name: 'C' },
{ id: 'cpp', name: 'C++' },
{ id: 'csharp', name: 'C#' },
{ id: 'coffeescript', name: 'CoffeeScript' },
{ id: 'd', name: 'D' },
{ id: 'dart' },
{ id: 'delphi', name: 'Delphi/Pascal' },
{ id: 'erlang' },
{ id: 'go', name: 'Golang' },
{ id: 'html' },
{ id: 'java' },
{ id: 'javascript' },
{ id: 'json' },
{ id: 'lua' },
{ id: 'less' },
{ id: 'markdown' },
{ id: 'nginx' },
{ id: 'objective-c' },
{ id: 'php' },
{ id: 'perl' },
{ id: 'python' },
{ id: 'r', name: 'R' },
{ id: 'ruby' },
{ id: 'sql' },
{ id: 'sass', name: 'SASS/SCSS' },
{ id: 'swift' },
{ id: 'typescript' },
{ id: 'xml' },
{ id: 'yaml' },
{ id: 'other', name: Anot.ui.meditor.lang.CODE.OTHER }
],
lang: 'javascript',
code: '',
maskClose: true,
offset: [offset.top + 35 - $doc.scrollTop()],
shift: { top: offset.top - $doc.scrollTop() },
insert: function() {
let val = `\n\`\`\`${this.lang}\n${this.code ||
'// ' + Anot.ui.meditor.lang.PLACEHOLDER}\n\`\`\`\n`
that.insert(val, false)
this.close()
},
content: `
<div class="do-meditor-codeblock">
<section class="do-fn-cl">
<div class="select">
<select :duplex="lang">
<option :for="__lang__" :attr-value="el.id">{{el.name || el.id}}</option>
</select>
<span class="trigon">
<i class="do-icon-trigon-up"></i>
<i class="do-icon-trigon-down"></i>
</span>
</div>
</section>
<section>
<textarea class="do-meditor__input area" :duplex="code" placeholder="${
Anot.ui.meditor.lang.PLACEHOLDER
}"></textarea>
</section>
<section class="do-fn-cl">
<a
href="javascript:;"
class="do-meditor__button submit"
:click="insert">${Anot.ui.meditor.lang.BTN.YES}</a>
</section>
</div>
`
})
},
preview: function() {
this.preview = !this.preview
if (this.preview) {
this.htmlTxt = this.__tmp__
}
},
fullscreen: function() {
this.fullscreen = !this.fullscreen
if (this.fullscreen) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = ''
}
if (typeof this.props.onFullscreen === 'function') {
this.props.onFullscreen(this.fullscreen)
}
},
about: function(elem) {
let offset = Anot(elem).offset()
layer.open({
type: 7,
title: Anot.ui.meditor.lang.LAYER.ABOUT_TITLE,
maskClose: true,
offset: [offset.top + 35 - $doc.scrollTop()],
shift: { top: offset.top - $doc.scrollTop() },
content: `<div class="do-meditor-about">
<pre>
__ __ _____ _ _ _
| \\/ | ____|__| (_) |_ ___ _ __
| |\\/| | _| / _\` | | __/ _ \\| '__|
| | | | |__| (_| | | || (_) | |
|_| |_|_____\\__,_|_|\\__\\___/|_| v${Anot.ui.meditor.version}</pre>
<p>${Anot.ui.meditor.lang.NAME}</p>
<p><a target="_blank" href="https://doui.cc/product/meditor">https://doui.cc/product/meditor</a></p>
<p>Copyright © 2017 Yutent, The MIT License.</p>
</div>`
})
}
}
export default addon

View File

@ -1,616 +0,0 @@
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2017-04-17 16:37:12
*
*/
'use strict'
import '../prism/base'
import '../marked/index'
import addon from './addon/base'
import 'css/meditor.scss'
const LANGUAGES = {
en: {
NAME: 'Open Source Markdown Editor',
TOOLBAR: {
PIPE: '',
H1: 'Header',
QUOTE: 'Quote text',
BOLD: 'Font-bold',
ITALIC: 'Font-italic',
THROUGH: 'Font-through',
UNORDERED: 'Unordered list',
ORDERED: 'Ordered list',
LINK: 'Hyperlink',
HR: 'Line',
TIME: 'Insert current time',
FACE: 'Face',
TABLE: 'Insert table',
IMAGE: 'Upload Pictures',
FILE: 'Upload Files',
INLINECODE: 'Inline code',
BLOCKCODE: 'Block code',
PREVIEW: 'Preview',
FULLSCREEN: 'Fullscreen',
ABOUT: 'About MEditor'
},
HEADERS: {
H1: '#{1}',
H2: '#{2}',
H3: '#{3}',
H4: '#{4}',
H5: '#{5}',
H6: '#{6}'
},
PLACEHOLDER: 'Type here',
LINK: {
ALT: 'Link text',
URL: 'Link address',
ERROR: 'Link address and text can not be null'
},
IMAGE: {
ALT: 'Image alt text',
URL: 'Image address'
},
TARGET: {
BLANK: 'New window open',
SELF: 'Current window open'
},
BTN: {
YES: 'OK'
},
TABLE: {
ROW: 'row',
COLUMN: 'column',
THEAD: 'thead'
},
LAYER: {
FACE_TITLE: 'Insert Face',
ABOUT_TITLE: 'About MEditor'
},
CODE: {
OTHER: 'Other language'
}
},
zh: {
NAME: '开源在线Markdown编辑器',
TOOLBAR: {
PIPE: '',
H1: '标题',
QUOTE: '引用文本',
BOLD: '粗体',
ITALIC: '斜体',
THROUGH: '删除线',
UNORDERED: '无序列表',
ORDERED: '有序列表',
LINK: '超链接',
HR: '横线',
TIME: '插入当前时间',
FACE: '表情',
TABLE: '插入表格',
IMAGE: '插入图片',
FILE: '插入附件',
INLINECODE: '行内代码',
BLOCKCODE: '代码块',
PREVIEW: '预览',
FULLSCREEN: '全屏',
ABOUT: '关于编辑器'
},
HEADERS: {
H1: '一级标题',
H2: '二级标题',
H3: '三级标题',
H4: '四级标题',
H5: '五级标题',
H6: '六级标题'
},
PLACEHOLDER: '在此输入文本',
LINK: {
ALT: '链接文字',
URL: '链接地址',
ERROR: '链接文字和地址不能为空'
},
IMAGE: {
ALT: '图片描述',
URL: '图片地址'
},
TARGET: {
BLANK: '新窗口打开',
SELF: '本窗口打开'
},
BTN: {
YES: '确定'
},
TABLE: {
ROW: '行',
COLUMN: '列',
THEAD: '表头'
},
LAYER: {
FACE_TITLE: '插入表情',
ABOUT_TITLE: '关于编辑器'
},
CODE: {
OTHER: '其他语言'
}
}
}
LANGUAGES['zh-CN'] = LANGUAGES.zh
LANGUAGES['zh-TW'] = LANGUAGES.zh
const lang =
LANGUAGES[window.__ENV_LANG__ || navigator.language] || LANGUAGES.en
marked.setOptions({
highlight: function(code, lang) {
return Prism.highlight(code, Prism.languages[lang])
}
})
if (!String.prototype.repeat) {
String.prototype.repeat = function(num) {
let result = ''
while (num > 0) {
result += this
num--
}
return result
}
}
Anot.ui.meditor = { version: '1.0.0', author: 'yutent', lang }
const log = console.log
const DEFAULT_TOOLBAR = [
'h1',
'quote',
'|',
'bold',
'italic',
'through',
'|',
'unordered',
'ordered',
'|',
'hr',
'link',
'time',
'face',
'|',
'table',
'image',
'attach',
'inlinecode',
'blockcode',
'|',
'preview',
'fullscreen',
'|',
'about'
]
const ELEMS = {
a: function(str, attr, inner) {
let href = attr.match(attrExp('href'))
let title = attr.match(attrExp('title'))
let tar = attr.match(attrExp('target'))
let attrs = ''
href = (href && href[1]) || null
title = (title && title[1]) || null
tar = (tar && tar[1]) || '_self'
if (!href) {
return inner || href
}
href = href.replace('viod(0)', '')
attrs = `target=${tar}`
attrs += title ? `;title=${title}` : ''
return `[${inner || href}](${href} "${attrs}")`
},
em: function(str, attr, inner) {
return (inner && '_' + inner + '_') || ''
},
strong: function(str, attr, inner) {
return (inner && '**' + inner + '**') || ''
},
pre: function(str, attr, inner) {
inner = inner.replace(/<[/]?code>/g, '')
return '\n\n```\n' + inner + '\n```\n'
},
code: function(str, attr, inner) {
return (inner && '`' + inner + '`') || ''
},
blockquote: function(str, attr, inner) {
return '> ' + inner.trim()
},
img: function(str, attr, inner) {
var src = attr.match(attrExp('src')),
alt = attr.match(attrExp('alt'))
src = (src && src[1]) || ''
alt = (alt && alt[1]) || ''
return '![' + alt + '](' + src + ')'
},
p: function(str, attr, inner) {
return inner ? '\n' + inner : ''
},
br: '\n',
'h([1-6])': function(str, level, attr, inner) {
let h = '#'.repeat(level)
return '\n' + h + ' ' + inner + '\n'
},
hr: '\n\n___\n\n'
}
function attrExp(field, flag = 'i') {
return new RegExp(field + '\\s?=\\s?["\']?([^"\']*)["\']?', flag)
}
function tagExp(tag, open) {
var exp = ''
if (['br', 'hr', 'img'].indexOf(tag) > -1) {
exp = '<' + tag + '([^>]*?)\\/?>'
} else {
exp = '<' + tag + '([^>]*?)>([\\s\\S]*?)<\\/' + tag + '>'
}
return new RegExp(exp, 'gi')
}
function html2md(str) {
try {
str = decodeURIComponent(str)
} catch (err) {}
str = str
.replace(/\t/g, ' ')
.replace(/<meta [^>]*>/, '')
.replace(attrExp('class', 'g'), '')
.replace(attrExp('style', 'g'), '')
.replace(/<(?!a |img )(\w+) [^>]*>/g, '<$1>')
.replace(/<svg[^>]*>.*?<\/svg>/g, '{invalid image}')
// log(str)
for (let i in ELEMS) {
let cb = ELEMS[i]
let exp = tagExp(i)
if (i === 'blockquote') {
while (str.match(exp)) {
str = str.replace(exp, cb)
}
} else {
str = str.replace(exp, cb)
}
// 对另外3种同类标签做一次处理
if (i === 'p') {
exp = tagExp('div')
str = str.replace(exp, cb)
}
if (i === 'em') {
exp = tagExp('i')
str = str.replace(exp, cb)
}
if (i === 'strong') {
exp = tagExp('b')
str = str.replace(exp, cb)
}
}
let liExp = /<(ul|ol)>(?:(?!<ul|<ol)[\s\S])*?<\/\1>/gi
while (str.match(liExp)) {
str = str.replace(liExp, function(match) {
match = match.replace(/<(ul|ol)>([\s\S]*?)<\/\1>/gi, function(
m,
t,
inner
) {
let li = inner.split('</li>')
li.pop()
for (let i = 0, len = li.length; i < len; i++) {
let pre = t === 'ol' ? i + 1 + '. ' : '* '
li[i] =
pre +
li[i]
.replace(/\s*<li>([\s\S]*)/i, function(m, n) {
n = n.trim().replace(/\n/g, '\n ')
return n
})
.replace(/<[\/]?[\w]*[^>]*>/g, '')
}
return li.join('\n')
})
return '\n' + match.trim()
})
}
str = str
.replace(/<[\/]?[\w]*[^>]*>/g, '')
.replace(/```([\w\W]*)```/g, function(str, inner) {
inner = inner
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
return '```' + inner + '```'
})
return str
}
function tool(name) {
name = (name + '').trim().toLowerCase()
name = '|' === name ? 'pipe' : name
let title = lang.TOOLBAR[name.toUpperCase()]
let extra = ''
switch (name) {
case 'preview':
extra = ':class="{active: preview}"'
break
case 'fullscreen':
extra = ':class="{active: fullscreen}"'
break
default:
break
}
return `
<span title="${title}" class="do-meditor__icon icon-${name}" data-name="${name}" ${extra}></span>`
}
class MEObject {
constructor(vm) {
this.vm = vm
this.id = vm.$id
}
getVal() {
return this.vm.value.trim()
}
getHtml() {
return this.vm.__tmp__
}
setVal(txt) {
this.vm.value = txt || ''
}
}
Anot.component('meditor', {
__init__: function(props, state, next) {
this.classList.add('do-meditor')
this.setAttribute(':css', '{height: height}')
this.setAttribute(
':class',
'{fullscreen: fullscreen, preview: preview, disabled: disabled}'
)
if (props.hasOwnProperty('disabled')) {
state.disabled = true
delete props.disabled
}
if (props.height) {
if (
(isFinite(props.height) && props.height > 180) ||
/%$/.test(props.height)
) {
state.height = props.height
}
delete props.height
}
next()
},
render: function() {
let toolbar = (this.props.toolbar || DEFAULT_TOOLBAR)
.map(it => tool(it))
.join('')
delete this.props.toolbar
return `
<div class="tool-bar do-fn-noselect" :click="onToolClick">${toolbar}</div>
<textarea
ref="editor"
class="editor-body"
spellcheck="false"
:attr="{disabled: disabled}"
:css="{'padding-bottom': padding}"
:duplex="value"></textarea>
<content
ref="preview"
class="md-preview do-marked-theme"
:css="{'padding-bottom': padding}"
:visible="preview"
:html="htmlTxt"></content>
`
},
componentDidMount: function(vm, elem) {
let $editor = Anot(this.$refs.editor)
let preview = this.$refs.preview
$editor.bind('keydown', ev => {
let wrap = this.selection() || ''
let select = !!wrap
//tab键改为插入2个空格,阻止默认事件,防止焦点失去
if (ev.keyCode === 9) {
ev.preventDefault()
wrap = wrap
.split('\n')
.map(function(it) {
return ev.shiftKey ? it.replace(/^\s\s/, '') : ' ' + it
})
.join('\n')
this.insert(wrap, select)
}
//修复按退格键删除选中文本时,选中的状态不更新的bug
if (ev.keyCode === 8) {
if (select) {
ev.preventDefault()
this.insert('', select)
}
}
})
$editor.bind('paste', ev => {
ev.preventDefault()
let txt = ev.clipboardData.getData('text/plain').trim()
let html = ev.clipboardData.getData('text/html').trim()
html = html2md(html)
if (html) {
this.insert(html)
} else if (txt) {
this.insert(txt)
}
this.value = this.$refs.editor.value
})
$editor.bind('scroll', ev => {
let st = ev.target.scrollTop
let sh = ev.target.scrollHeight
let ch = ev.target.clientHeight
let psh = preview.scrollHeight
let syncTop = (st / (sh - ch)) * (psh - ch)
preview.scrollTop = syncTop
})
//编辑器成功加载的回调
if (typeof this.props.created === 'function') {
this.props.created(new MEObject(this))
}
this.padding = (this.$elem.clientHeight / 2) >>> 0
this.compile()
if (this.preview) {
this.htmlTxt = this.__tmp__
}
},
watch: {
value: function(val) {
this.compile()
//只有开启实时预览,才会赋值给htmlTxt
if (this.preview) {
this.htmlTxt = this.__tmp__
}
if (typeof this.props.onUpdate === 'function') {
this.props.onUpdate(this.value, this.__tmp__)
}
}
},
state: {
padding: 90,
height: 180,
disabled: false, //禁用编辑器
fullscreen: false, //是否全屏
preview: window.innerWidth > 768, //是否显示预览
htmlTxt: '', //用于预览渲染
value: '', //纯md文本
addon // 已有插件
},
props: {
safeMode: true,
revise: Anot.PropsTypes.isFunction(),
created: Anot.PropsTypes.isFunction(),
onUpdate: Anot.PropsTypes.isFunction(),
onFullscreen: Anot.PropsTypes.isFunction()
},
skip: ['addon', 'insert', 'selection'],
methods: {
// 往文本框中插入内容
insert(val, isSelect) {
let dom = this.$refs.editor
if (document.selection) {
dom.focus()
let range = document.selection.createRange()
range.text = val
dom.focus()
range.moveStart('character', -1)
} else if (dom.selectionStart || dom.selectionStart === 0) {
let startPos = dom.selectionStart
let endPos = dom.selectionEnd
let scrollTop = dom.scrollTop
dom.value =
dom.value.slice(0, startPos) +
val +
dom.value.slice(endPos, dom.value.length)
dom.selectionStart = isSelect ? startPos : startPos + val.length
dom.selectionEnd = startPos + val.length
dom.scrollTop = scrollTop
dom.focus()
} else {
dom.value += val
dom.focus()
}
this.value = dom.value
},
/**
* [selection 获取选中的文本]
* @param {[type]} dom [要操作的元素]
* @param {[type]} forceHoleLine [是否强制光标所在的整行文本]
*/
selection(forceHoleLine) {
let dom = this.$refs.editor
if (document.selection) {
return document.selection.createRange().text
} else {
let startPos = dom.selectionStart
let endPos = dom.selectionEnd
if (endPos) {
//强制选择整行
if (forceHoleLine) {
startPos = dom.value.slice(0, startPos).lastIndexOf('\n')
let tmpEnd = dom.value.slice(endPos).indexOf('\n')
tmpEnd = tmpEnd < 0 ? dom.value.slice(endPos).length : tmpEnd
startPos += 1 // 把\n加上
endPos += tmpEnd
dom.selectionStart = startPos
dom.selectionEnd = endPos
}
} else {
//强制选择整行
if (forceHoleLine) {
endPos = dom.value.indexOf('\n')
endPos = endPos < 0 ? dom.value.length : endPos
dom.selectionEnd = endPos
}
}
dom.focus()
return dom.value.slice(startPos, endPos)
}
},
onToolClick: function(ev) {
if (ev.target.tagName.toLowerCase() !== 'span') {
return
}
let name = ev.target.dataset.name
if (this.disabled || name === 'pipe') {
return
}
if (this.addon[name]) {
this.addon[name].call(this, ev.target)
} else {
console.log('%c没有对应的插件%c[%s]', 'color:#f00;', '', name)
}
},
compile: function() {
let txt = this.value.trim()
if (this.props.safeMode) {
txt = txt
.replace(/<script([^>]*?)>/g, '&lt;script$1&gt;')
.replace(/<\/script>/g, '&lt;/script&gt;')
}
//只解析,不渲染
this.__tmp__ = marked(txt)
if (typeof this.props.revise === 'function') {
this.__tmp__ = this.props.revise(this.__tmp__)
}
}
}
})

View File

@ -49,12 +49,12 @@ table {
background: #fff; background: #fff;
} }
thead tr { thead tr {
background: nth($cp, 1); background: var(--color-plain-1);
} }
th, th,
td { td {
padding: 6px 12px; padding: 6px 12px;
border: 1px solid nth($cp, 3); border: 1px solid var(--color-plain-3);
} }
th { th {
font-weight: bold; font-weight: bold;

View File

@ -12,7 +12,7 @@
:host { :host {
display: block; display: block;
line-height: 1; line-height: 1;
color: nth($cd, 1); color: var(--color-dark-1);
font-size: 14px; font-size: 14px;
user-select: none; user-select: none;
-moz-user-select: none; -moz-user-select: none;
@ -29,7 +29,7 @@
height: 32px; height: 32px;
padding: 0 8px; padding: 0 8px;
margin: 0 3px; margin: 0 3px;
background: nth($cp, 1); background: var(--color-plain-1);
border: 0; border: 0;
border-radius: 2px; border-radius: 2px;
outline: none; outline: none;
@ -37,11 +37,11 @@
color: inherit; color: inherit;
&:hover { &:hover {
background: nth($cp, 2); background: var(--color-plain-2);
} }
&[curr] { &[curr] {
background: nth($ct, 1); background: var(--color-teal-1);
color: #fff; color: #fff;
} }
} }
@ -59,31 +59,31 @@
} }
:host([color='red']) button[curr] { :host([color='red']) button[curr] {
background: nth($cr, 1); background: var(--color-red-1);
} }
:host([color='blue']) button[curr] { :host([color='blue']) button[curr] {
background: nth($cb, 1); background: var(--color-blue-1);
} }
:host([color='green']) button[curr] { :host([color='green']) button[curr] {
background: nth($cg, 1); background: var(--color-green-1);
} }
:host([color='teal']) button[curr] { :host([color='teal']) button[curr] {
background: nth($ct, 1); background: var(--color-teal-1);
} }
:host([color='orange']) button[curr] { :host([color='orange']) button[curr] {
background: nth($co, 1); background: var(--color-orange-1);
} }
:host([color='dark']) button[curr] { :host([color='dark']) button[curr] {
background: nth($cd, 1); background: var(--color-dark-1);
} }
:host([color='purple']) button[curr] { :host([color='purple']) button[curr] {
background: nth($cpp, 1); background: var(--color-purple-1);
} }
</style> </style>

View File

View File

@ -33,7 +33,7 @@
min-width: 105px; min-width: 105px;
user-select: none; user-select: none;
-moz-user-select: none; -moz-user-select: none;
color: nth($cd, 2); color: var(--color-dark-2);
border-radius: 2px; border-radius: 2px;
} }
@ -44,7 +44,7 @@
width: 100%; width: 100%;
height: 32px; height: 32px;
font-size: 13px; font-size: 13px;
border: 1px solid nth($cp, 3); border: 1px solid var(--color-plain-3);
border-radius: inherit; border-radius: inherit;
white-space: nowrap; white-space: nowrap;
background: #fff; background: #fff;
@ -60,9 +60,9 @@
height: 30px; height: 30px;
padding: 0 10px; padding: 0 10px;
font-size: 14px; font-size: 14px;
border-right: 1px solid nth($cp, 3); border-right: 1px solid var(--color-plain-3);
border-radius: 2px 0 0 2px; border-radius: 2px 0 0 2px;
background: nth($cp, 1); background: var(--color-plain-1);
} }
span { span {
@ -70,7 +70,7 @@
padding: 0 5px; padding: 0 5px;
&::placeholder { &::placeholder {
color: nth($cgr, 1); color: var(--color-grey-1);
} }
} }
@ -123,7 +123,7 @@
wc-icon { wc-icon {
--size: 14px; --size: 14px;
margin: 0 3px; margin: 0 3px;
color: nth($cgr, 1); color: var(--color-grey-1);
cursor: pointer; cursor: pointer;
} }
span { span {
@ -160,7 +160,7 @@
} }
&[weekend] { &[weekend] {
color: nth($ct, 1); color: var(--color-teal-1);
} }
&[picked] { &[picked] {
color: #fff; color: #fff;
@ -175,14 +175,14 @@
width: 20px; width: 20px;
height: 20px; height: 20px;
border-radius: 50%; border-radius: 50%;
background: nth($ct, 1); background: var(--color-teal-1);
content: ''; content: '';
} }
} }
&[disabled] { &[disabled] {
border-color: transparent; border-color: transparent;
font-size: 12px; font-size: 12px;
color: nth($cp, 3); color: var(--color-plain-3);
} }
} }
} }
@ -197,8 +197,8 @@
height: 30px; height: 30px;
line-height: 28px; line-height: 28px;
background: #fffbed; background: #fffbed;
color: nth($co, 2); color: var(--color-orange-2);
border: 1px solid nth($co, 1); border: 1px solid var(--color-orange-1);
border-radius: 2px; border-radius: 2px;
} }

View File

@ -1,446 +0,0 @@
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2018-03-25 23:59:13
* @version $Id$
*/
'use strict'
import Format from './lib/format'
// 本地协议/头 判断正则
const rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/
const log = console.log
const noop = function(e, res) {
this.defer.resolve(res)
}
let isLocal = false
try {
isLocal = rlocalProtocol.test(location.protocol)
} catch (e) {}
let originAnchor = document.createElement('a')
originAnchor.href = location.href
const NOBODY_METHODS = ['GET', 'HEAD']
const ERRORS = {
10001: 'Argument url is required',
10012: 'Parse error',
10100: 'Request canceled',
10104: 'Request pending...',
10200: 'Ok',
10204: 'No content',
10304: 'Not modified',
10500: 'Internal Server Error',
10504: 'Connected timeout'
}
const FORM_TYPES = {
form: 'application/x-www-form-urlencoded; charset=UTF-8',
json: 'application/json; charset=UTF-8',
text: 'text/plain; charset=UTF-8'
}
const convert = {
text(val) {
return val
},
xml(val, xml) {
return xml !== undefined ? xml : Format.parseXML(val)
},
html(val) {
return Format.parseHTML(val)
},
json(val) {
return JSON.parse(val)
},
script(val) {
return Format.parseJS(val)
}
}
class _Request {
constructor(url = '', method = 'GET', param = {}) {
if (!url) {
throw new Error(ERRORS[10001])
}
// url规范化
url = url.replace(/#.*$/, '')
if (request.BASE_URL) {
if (!/^([a-z]+:|\/\/)/.test(url)) {
url = request.BASE_URL + url
}
}
method = method.toUpperCase()
this.xhr = new XMLHttpRequest()
this.defer = Promise.defer()
this.opt = {
url,
method,
headers: {},
data: {},
dataType: 'text',
withCredentials: false // 跨域选项,是否验证凭证
}
// 取消网络请求
this.defer.promise.abort = () => {
this.cancel = true
this.xhr.abort()
}
this.__next__(Object.assign({}, request.__INIT__, param))
return this.defer.promise
}
__next__(param) {
/* -------------------------------------------------------------- */
/* ------------------------ 1»» 配置头信息 ---------------------- */
/* -------------------------------------------------------------- */
if (param.headers) {
Object.assign(this.opt.headers, param.headers)
}
/* -------------------------------------------------------------- */
/* --------- 2»» 设置表单类型, 其中 form-data不能手动设置 ---------- */
/* -------------------------------------------------------------- */
let hasAttach = false
if (param.formType) {
switch (param.formType) {
case 'form':
this.__set__('form')
break
case 'json':
this.__set__('json')
break
case 'form-data':
this.opt.method = 'POST'
hasAttach = true
break
default:
if (NOBODY_METHODS.includes(this.opt.method)) {
this.__set__('form')
} else {
this.__set__('text')
}
}
} else {
this.__set__('form')
}
/* -------------------------------------------------------------- */
/* ------------------- 3»» 设置缓存 ---------------------------- */
/* -------------------------------------------------------------- */
if (param.cache) {
if (NOBODY_METHODS.includes(this.opt.method)) {
this.opt.cache = true
}
}
/* -------------------------------------------------------------- */
/* ------------------- 4»» 设置超时时间(毫秒) --------------------- */
/* -------------------------------------------------------------- */
param.timeout = param.timeout >>> 0
if (param.timeout > 0) {
this.opt.timeout = param.timeout
}
/* -------------------------------------------------------------- */
/* -------------------------- 5»» 请求的内容 --------------------- */
/* -------------------------------------------------------------- */
if (param.data) {
let type = typeof param.data
switch (type) {
case 'number':
case 'string':
this.__set__('text')
this.opt.data = param.data
break
case 'object':
// 解析表单DOM
if (param.data.nodeName === 'FORM') {
this.opt.method = param.data.method.toUpperCase() || 'POST'
this.opt.data = Format.parseForm(param.data)
hasAttach = this.opt.data.constructor === FormData
if (hasAttach) {
delete this.opt.headers['content-type']
}
// 如果是一个 FormData对象
// 则直接改为POST
} else if (param.data.constructor === FormData) {
hasAttach = true
this.opt.method = 'POST'
delete this.opt.headers['content-type']
this.opt.data = param.data
} else {
// 有附件,则改为FormData
if (hasAttach) {
this.opt.data = Format.mkFormData(param.data)
} else {
this.opt.data = param.data
}
}
}
}
/* -------------------------------------------------------------- */
/* -------------------------- 6»» 处理跨域 --------------------- */
/* -------------------------------------------------------------- */
if (param.withCredentials) {
this.opt.withCredentials = true
}
try {
let anchor = document.createElement('a')
anchor.href = this.opt.url
this.opt.crossDomain =
originAnchor.protocol !== anchor.protocol ||
originAnchor.host !== anchor.host
} catch (err) {}
// 6.1»» 进一步处理跨域
// 非跨域或跨域但支持Cors时自动加上一条header信息用以标识这是ajax请求
// 如果是跨域,开启Cors会需要服务端额外返回一些headers
if (this.opt.crossDomain) {
if (this.opt.withCredentials) {
this.xhr.withCredentials = true
this.opt.headers['X-Requested-With'] = 'XMLHttpRequest'
}
} else {
this.opt.headers['X-Requested-With'] = 'XMLHttpRequest'
}
/* -------------------------------------------------------------- */
/* ------------- 7»» 根据method类型, 处理g表单数据 ---------------- */
/* -------------------------------------------------------------- */
// 是否允许发送body
let allowBody = !NOBODY_METHODS.includes(this.opt.method)
if (allowBody) {
if (!hasAttach) {
if (param.formType === 'json') {
this.opt.data = JSON.stringify(this.opt.data)
} else {
this.opt.data = Format.param(this.opt.data)
}
}
} else {
// 否则拼接到url上
this.opt.data = Format.param(this.opt.data)
if (this.opt.data) {
this.opt.url += (/\?/.test(this.opt.url) ? '&' : '?') + this.opt.data
}
if (this.opt.cache === false) {
this.opt.url +=
(/\?/.test(this.opt.url) ? '&' : '?') + '_=' + Math.random()
}
}
/* -------------------------------------------------------------- */
/* ------------- 8»» 设置响应的数据类型 ---------------- */
/* -------------------------------------------------------------- */
// arraybuffer | blob | document | json | text
if (param.dataType) {
this.opt.dataType = param.dataType.toLowerCase()
}
this.xhr.responseType = this.opt.dataType
/* -------------------------------------------------------------- */
/* ------------- 9»» 构造请求 ---------------- */
/* -------------------------------------------------------------- */
// response ready
this.xhr.onreadystatechange = ev => {
if (this.opt.timeout > 0) {
this.opt['time' + this.xhr.readyState] = ev.timeStamp
if (this.xhr.readyState === 4) {
this.opt.isTimeout =
this.opt.time4 - this.opt.time1 > this.opt.timeout
}
}
if (this.xhr.readyState !== 4) {
return
}
this.__dispatch__(this.opt.isTimeout)
}
// 9.1»» 初始化xhr
this.xhr.open(this.opt.method, this.opt.url, true)
// 9.2»» 设置头信息
for (let i in this.opt.headers) {
this.xhr.setRequestHeader(i, this.opt.headers[i])
}
// 9.3»» 发起网络请求
this.xhr.send(this.opt.data)
// 9.4»» 超时处理
if (this.opt.timeout && this.opt.timeout > 0) {
this.xhr.timeout = this.opt.timeout
}
}
__set__(type) {
this.opt.headers['content-type'] = FORM_TYPES[type]
}
__dispatch__(isTimeout) {
let result = {
status: 200,
statusText: 'ok',
text: '',
body: '',
error: null
}
// 主动取消
if (this.cancel) {
return this.__cancel__(result)
}
// 超时
if (isTimeout) {
return this.__timeout__(result)
}
// 是否请求成功(resful规范)
let isSucc = this.xhr.status >= 200 && this.xhr.status < 400
let headers = this.xhr.getAllResponseHeaders().split('\n') || []
let contentType = ''
//处理返回的 Header, 拿到content-type
for (let it of headers) {
it = it.trim()
if (it) {
it = it.split(':')
let tmp = it.shift().toLowerCase()
if (tmp === 'content-type') {
contentType = it
.join(':')
.trim()
.toLowerCase()
break
}
}
}
if (isSucc) {
result.status = this.xhr.status
if (result.status === 204) {
result.statusText = ERRORS[10204]
} else if (result.status === 304) {
result.statusText = ERRORS[10304]
}
} else {
result.status = this.xhr.status || 500
result.statusText = this.xhr.statusText || ERRORS[10500]
result.error = new Error(result.statusText)
}
// log(this.opt.dataType, this.xhr)
switch (this.opt.dataType) {
case 'arraybuffer':
case 'blob':
case 'document':
case 'json':
result.text = result.body = this.xhr.response
break
// text
default:
try {
//处理返回的数据
let dataType = contentType.match(/json|xml|script|html/)
dataType = (dataType && dataType[0].toLowerCase()) || 'text'
result.text = this.xhr.response
result.body = convert[dataType](result.text, this.xhr.response)
} catch (err) {
result.error = err
result.statusText = ERRORS[10012]
}
break
}
this.__success__(isSucc, result)
}
__success__(isSucc, result) {
if (isSucc) {
this.defer.resolve(result)
} else {
this.defer.reject(result)
}
delete this.xhr
delete this.opt
delete this.defer
}
__cancel__(result) {
result.status = 0
result.statusText = ERRORS[10100]
result.error = new Error(ERRORS[10100])
this.defer.reject(result)
delete this.xhr
delete this.opt
delete this.defer
}
__timeout__(result) {
result.status = 504
result.statusText = ERRORS[10504]
result.error = new Error(ERRORS[10504])
this.defer.reject(result)
delete this.xhr
delete this.opt
delete this.defer
}
}
if (!window.request) {
window.request = {
get(url, param = {}) {
return new _Request(url, 'GET', param)
},
post(url, param = {}) {
return new _Request(url, 'POST', param)
},
upload(url, param = {}) {
param.formType = 'form-data'
return this.post(url, param)
},
download(url, param = {}) {
param.dataType = 'blob'
return this.get(url, param)
},
open(url, method = 'GET', param = {}) {
if (typeof method === 'object') {
param = method
method = 'GET'
}
return new _Request(url, method, param)
},
version: '2.0.0-normal',
init(param = {}) {
this.__INIT__ = param
}
}
}
export default request

View File

@ -1,225 +0,0 @@
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2016-11-26 16:35:45
*
*/
'use strict'
function serialize(p, obj, q) {
let k
if (Array.isArray(obj)) {
obj.forEach(function(it, i) {
k = p ? `${p}[${Array.isArray(it) ? i : ''}]` : i
// k = p ? p + '[' + (Array.isArray(it) ? i : '') + ']' : i
if (typeof it === 'object') {
serialize(k, it, q)
} else {
q(k, it)
}
})
} else {
for (let i in obj) {
k = p ? `${p}[${i}]` : i
// k = p ? p + '[' + i + ']' : i
if (typeof obj[i] === 'object') {
serialize(k, obj[i], q)
} else {
q(k, obj[i])
}
}
}
}
const toS = Object.prototype.toString
const doc = window.document
const encode = encodeURIComponent
const decode = decodeURIComponent
const TagHooks = function() {
this.option = doc.createElement('select')
this.thead = doc.createElement('table')
this.td = doc.createElement('tr')
this.area = doc.createElement('map')
this.tr = doc.createElement('tbody')
this.col = doc.createElement('colgroup')
this.legend = doc.createElement('fieldset')
this._default = doc.createElement('div')
this.g = doc.createElementNS('http://www.w3.org/2000/svg', 'svg')
this.optgroup = this.option
this.tbody = this.tfoot = this.colgroup = this.caption = this.thead
this.th = this.td
'circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use'.replace(
/,/g,
m => {
this[m] = this.g //处理svg
}
)
}
const Helper = {
tagHooks: new TagHooks(),
rtagName: /<([\w:]+)/,
rxhtml: /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
scriptTypes: {
'text/javascript': 1,
'text/ecmascript': 1,
'application/ecmascript': 1,
'application/javascript': 1
},
rhtml: /<|&#?\w+;/
}
export default {
parseJS: function(code) {
code = (code + '').trim()
if (code) {
if (code.indexOf('use strict') === 1) {
let script = doc.createElement('script')
script.text = code
doc.head.appendChild(script).parentNode.removeChild(script)
} else {
eval(code)
}
}
},
parseXML: function(data, xml, tmp) {
try {
tmp = new DOMParser()
xml = tmp.parseFromString(data, 'text/xml')
} catch (e) {
xml = void 0
}
if (
!xml ||
!xml.documentElement ||
xml.getElementsByTagName('parsererror').length
) {
console.error('Invalid XML: ' + data)
}
return xml
},
parseHTML: function(html) {
let fragment = doc.createDocumentFragment().cloneNode(false)
if (typeof html !== 'string') {
return fragment
}
if (!Helper.rhtml.test(html)) {
fragment.appendChild(document.createTextNode(html))
return fragment
}
html = html.replace(Helper.rxhtml, '<$1></$2>').trim()
let tag = (Helper.rtagName.exec(html) || ['', ''])[1].toLowerCase()
let wrap = Helper.tagHooks[tag] || Helper.tagHooks._default
let firstChild = null
//使用innerHTML生成的script节点不会触发请求与执行text属性
wrap.innerHTML = html
let script = wrap.getElementsByTagName('script')
if (script.length) {
for (let i = 0, el; (el = script[i++]); ) {
if (Helper.scriptTypes[el.type]) {
let tmp = doc.createElement('script').cloneNode(false)
el.attributes.forEach(function(attr) {
tmp.setAttribute(attr.name, attr.value)
})
tmp.text = el.text
el.parentNode.replaceChild(tmp, el)
}
}
}
while ((firstChild = wrap.firstChild)) {
fragment.appendChild(firstChild)
}
return fragment
},
parseForm: function(form) {
let data = {}
let hasAttach = false
for (let i = 0, field; (field = form.elements[i++]); ) {
switch (field.type) {
case 'select-one':
case 'select-multiple':
if (field.name.length && !field.disabled) {
for (let j = 0, opt; (opt = field.options[j++]); ) {
if (opt.selected) {
data[field.name] = opt.value || opt.text
}
}
}
break
case 'file':
if (field.name.length && !field.disabled) {
data[field.name] = field.files[0]
hasAttach = true
}
break
case undefined:
case 'submit':
case 'reset':
case 'button':
break //按钮啥的, 直接忽略
case 'radio':
case 'checkbox':
// 只处理选中的
if (!field.checked) break
default:
if (field.name.length && !field.disabled) {
data[field.name] = field.value
}
}
}
// 如果有附件, 改为FormData
if (hasAttach) {
return this.mkFormData(data)
} else {
return data
}
},
mkFormData(data) {
let form = new FormData()
for (let i in data) {
let el = data[i]
if (Array.isArray(el)) {
el.forEach(function(it) {
form.append(i + '[]', it)
})
} else {
form.append(i, data[i])
}
}
return form
},
param: function(obj) {
if (!obj || typeof obj === 'string' || typeof obj === 'number') {
return obj
}
let arr = []
let q = function(k, v) {
if (/native code/.test(v)) {
return
}
v = typeof v === 'function' ? v() : v
v = toS.call(v) !== '[object File]' ? encode(v) : v
arr.push(encode(k) + '=' + v)
}
if (typeof obj === 'object') {
serialize('', obj, q)
}
return arr.join('&')
}
}

View File

View File

@ -1,241 +0,0 @@
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2017-04-14 21:04:50
*
*/
'use strict'
//储存版本信息
//判定A标签的target属性是否指向自身
//thanks https://github.com/quirkey/sammy/blob/master/lib/sammy.js#L219
function targetIsThisWindow(targetWindow) {
if (
!targetWindow ||
targetWindow === window.name ||
targetWindow === '_self' ||
(targetWindow === 'top' && window == window.top)
) {
return true
}
return false
}
//hash前缀正则
const PREFIX_REGEXP = /^(#!|#)[\/]?/
const TRIM_REGEXP = /(^[/]+)|([/]+$)/g
const DEFAULT_OPTIONS = {
mode: 'hash', // hash | history
allowReload: true //连续点击同一个链接是否重新加载
}
const LINKS = []
const RULE_REGEXP = /(:id)|(\{id\})|(\{id:([A-z\d\,\[\]\{\}\-\+\*\?\!:\^\$]*)\})/g
class Router {
constructor(options) {
Anot.hideProperty(this, 'table', [])
Anot.hideProperty(this, 'last', '')
Anot.hideProperty(this, 'path', '')
Anot.hideProperty(this, 'pathArr', [])
Anot.hideProperty(this, 'ready', false)
Anot.hideProperty(this, 'noMatch', null)
Anot.hideProperty(
this,
'options',
Object.assign({}, DEFAULT_OPTIONS, options)
)
this.__listen__()
}
// 创建无new式Router实例
static init(options = {}) {
if (Anot.router) {
throw new Error('不允许重复创建Router实例...')
}
Anot.router = new this(options)
return Anot.router
}
// 事件监听
__listen__() {
let { mode } = this.options
Anot.bind(window, 'load, popstate', ev => {
if (ev.type === 'load') {
if (this.ready) {
return
}
this.ready = true
}
let path = mode === 'hash' ? location.hash : location.pathname
path = path.replace(PREFIX_REGEXP, '').trim()
path = path.replace(TRIM_REGEXP, '')
if (ev.type === 'load') {
this.go(path)
// hash模式要手动触发一下路由检测
if (mode === 'hash') {
this.__check__(path)
}
} else {
// 因为pushState不会触发popstate事件,
// 所以这里只在hash模式或有ev.state的情况下才会主动触发路由检测
if (mode === 'hash' || ev.state) {
this.__check__(path)
}
}
})
//劫持页面上所有点击事件,如果事件源来自链接或其内部,
//并且它不会跳出本页,并且以"#/"或"#!/"开头那么触发go方法
Anot.bind(document, 'click', ev => {
let prevented =
'defaultPrevented' in ev
? ev.defaultPrevented
: ev.returnValue === false
if (prevented || ev.ctrlKey || ev.metaKey || ev.which === 2) {
return
}
let target = ev.target
while (target.nodeName !== 'A') {
target = target.parentNode
if (!target || target.tagName === 'BODY') {
return
}
}
if (mode === 'history') {
if (targetIsThisWindow(target.target)) {
let href =
target.getAttribute('href') || target.getAttribute('xlink:href')
if (
!href ||
/^(http[s]?:|ftp:)?\/\//.test(href) ||
/^javascript:/.test(href)
) {
return
}
// hash地址,只管修正前缀即可, 会触发popstate事件,所以这里只处理非hash的情况
if (!PREFIX_REGEXP.test(href)) {
// 非hash地址,则需要阻止默认事件
// 并主动触发跳转, 同时强制清除hash
ev.preventDefault()
this.go(href, true)
}
}
}
})
}
__parseRule__(rule, opts) {
let re = rule.replace(RULE_REGEXP, function(m, p1, p2, p3, p4) {
let w = '([\\w.-]'
if (p1 || p2) {
return w + '+)'
} else {
if (!/^\{[\d\,]+\}$/.test(p4)) {
w = '('
}
return w + p4 + ')'
}
})
re = re
.replace(/(([^\\])([\/]+))/g, '$2\\/')
.replace(/(([^\\])([\.]+))/g, '$2\\.')
.replace(/(([^\\])([\-]+))/g, '$2\\-')
.replace(/(\(.*)(\\[\-]+)(.*\))/g, '$1-$3')
re = '^' + re + '$'
opts.regexp = new RegExp(re)
return opts
}
__add__(rule, callback) {
// 特殊值"!", 则自动作非匹配回调处理
if (rule === '!') {
this.noMatch = callback
return
}
if (rule.charAt(0) !== '/') {
console.error('路由规则必须以"/"开头')
return
}
rule = rule.replace(/^[\/]+|[\/]+$|\s+/g, '')
let opts = { rule, callback }
Anot.Array.ensure(this.table, this.__parseRule__(rule, opts))
}
// 路由检测
__check__(path) {
let { allowReload } = this.options
if (!allowReload && path === this.last) {
return
}
this.last = this.path
this.path = path
this.pathArr = path.split('/')
LINKS.forEach(vm => {
if (vm.rule.test(this.path)) {
vm.active = true
} else {
vm.active = false
}
})
for (let i = 0, route; (route = this.table[i++]); ) {
let args = path.match(route.regexp)
if (args) {
args.shift()
return route.callback.apply(route, args)
}
}
this.noMatch && this.noMatch(this.path)
}
// 跳转到路由
go(path, forceCleanHash = false) {
path = path.trim().replace(TRIM_REGEXP, '')
let { mode } = this.options
if (mode === 'hash') {
// 页面刷新时, 不主动添加空hash, 避免执行2次noMatch回调
if (!path && path === location.hash) {
return
}
location.hash = '!/' + path
} else {
let hash = forceCleanHash ? '' : location.hash
let search = forceCleanHash ? '' : location.search
if (forceCleanHash) {
window.history.pushState({ path }, null, `/${path + search + hash}`)
} else {
window.history.replaceState({ path }, null, `/${path + search + hash}`)
}
// pushState不会触发popstate事件,所以要手动触发路由检测
this.__check__(path)
}
}
// 绑定路由事件
on(rule, callback) {
if (Array.isArray(rule)) {
rule.forEach(it => {
this.__add__(it, callback)
})
} else {
this.__add__(rule, callback)
}
// 因为先初始化,才开始监听路由规则
// 所以会导致wondow load的时候, 规则还没生效, 而生效之后,load已经结束
// 所以这里需要手动再触发一次load
Anot.fireDom(window, 'load')
}
}
export default Router

View File

@ -13,7 +13,6 @@
.container { .container {
overflow: hidden; overflow: hidden;
position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
@ -110,13 +109,16 @@ export default class Scroll {
thumbX: 0, thumbX: 0,
thumbY: 0, thumbY: 0,
disabled: false, disabled: false,
axis: 'xy' // 滚动方向, 默认x轴和y轴都可以滚动 axis: 'xy', // 滚动方向, 默认x轴和y轴都可以滚动
delay: 1000, // 节流延迟
distance: 1 // 触发距离阀值, 单位像素
} }
__init__() { __init__() {
/* render */ /* render */
this.__BOX__ = this.root.children[1] this.__BOX__ = this.root.children[1]
this.__X__ = this.root.children[2].children[0] this.__X__ = this.root.children[2].children[0]
this.__Y__ = this.root.children[3].children[0] this.__Y__ = this.root.children[3].children[0]
this.__last__ = 0
} }
get scrollTop() { get scrollTop() {
@ -157,6 +159,10 @@ export default class Scroll {
return this.__BOX__.scrollHeight return this.__BOX__.scrollHeight
} }
get scrollWidth() {
return this.__BOX__.scrollWidth
}
get disabled() { get disabled() {
return this.props.disabled return this.props.disabled
} }
@ -203,6 +209,30 @@ export default class Scroll {
return moveY return moveY
} }
_fireReachEnd(action = 'reach-bottom') {
var { sh, oh, delay, disabled } = this.props
var top = this.__BOX__.scrollTop
var now = Date.now()
if (disabled) {
return
}
if (now - this.__last__ > delay) {
if (action === 'reach-bottom') {
if (oh + top < sh) {
return
}
} else {
if (top > 0) {
return
}
}
this.__last__ = now
this.dispatchEvent(new CustomEvent(action))
}
}
mounted() { mounted() {
// 初始化滚动条的位置和长度 // 初始化滚动条的位置和长度
this._initFn = $.bind(this.__BOX__, 'mouseenter', ev => { this._initFn = $.bind(this.__BOX__, 'mouseenter', ev => {
@ -215,8 +245,9 @@ export default class Scroll {
var oh = this.__BOX__.offsetHeight var oh = this.__BOX__.offsetHeight
var sh = this.__BOX__.scrollHeight var sh = this.__BOX__.scrollHeight
var yh = ((oh * oh) / sh) >> 0 var yh = ((oh * oh) / sh) >> 0 // 滚动条的高度
var xw = ((ow * ow) / sw) >> 0 var xw = ((ow * ow) / sw) >> 0 // 滚动条的宽度
if (yh < 50) { if (yh < 50) {
yh = 50 yh = 50
} }
@ -319,6 +350,10 @@ export default class Scroll {
this.props.thumbY = fixedY this.props.thumbY = fixedY
this.__Y__.style.transform = `translateY(${fixedY}px)` this.__Y__.style.transform = `translateY(${fixedY}px)`
if (Math.abs(deltaY) > this.props.distance) {
this._fireReachEnd(deltaY > 0 ? 'reach-bottom' : 'reach-top')
}
} }
} }
@ -373,6 +408,9 @@ export default class Scroll {
} }
}, },
mouseupFn = ev => { mouseupFn = ev => {
if (Math.abs(ev.pageY - startY) > this.props.distance) {
this._fireReachEnd(ev.pageY > startY ? 'reach-bottom' : 'reach-top')
}
startX = null startX = null
startY = null startY = null
this.props.thumbX = moveX this.props.thumbX = moveX
@ -410,21 +448,28 @@ export default class Scroll {
$.catch(document, 'keydown', ev => { $.catch(document, 'keydown', ev => {
if (this._active) { if (this._active) {
var { oh, sh } = this.props var { oh, sh } = this.props
var exec = false
switch (ev.keyCode) { switch (ev.keyCode) {
case 33: // pageUp case 33: // pageUp
exec = true
this.scrollTop -= oh this.scrollTop -= oh
break break
case 34: // pageDown case 34: // pageDown
exec = true
this.scrollTop += oh this.scrollTop += oh
break break
case 35: // End case 35: // End
exec = true
this.scrollTop = sh this.scrollTop = sh
break break
case 36: // Home case 36: // Home
exec = true
this.scrollTop = 0 this.scrollTop = 0
break break
} }
ev.preventDefault() if (exec) {
ev.preventDefault()
}
} }
}) })
@ -452,6 +497,12 @@ export default class Scroll {
case 'disabled': case 'disabled':
this[name] = true this[name] = true
break break
case 'delay':
this.props.delay = +val || 1000
break
case 'distance':
this.props.distance = +val || 1
break
} }
} }
} }

264
src/slider/index.wc Normal file
View File

@ -0,0 +1,264 @@
<template>
<div class="slider-box">
<div class="slider"></div>
<input type="range" max="100" min="0" step="1" />
</div>
</template>
<style lang="scss">
:host {
display: flex;
width: 100%;
height: 6px;
}
.slider-box {
display: flex;
position: relative;
width: 100%;
height: 100%;
background: var(--bg-color, --color-plain-3);
border-radius: 3px;
.slider {
position: relative;
height: 6px;
border-radius: 3px;
background: var(--color-teal-1);
}
input {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
outline: none;
opacity: 0;
cursor: -webkit-grab;
&:active {
cursor: -webkit-grabbing;
}
}
}
:host([vertical]) {
width: 6px;
height: 100%;
.slider-box {
align-items: flex-end;
.slider {
height: 0;
width: 6px;
}
input {
appearance: slider-vertical;
}
}
}
:host([color='red']) {
.slider-box .slider {
background: var(--color-red-1);
}
}
:host([color='orange']) {
.slider-box .slider {
background: var(--color-orange-1);
}
}
:host([color='green']) {
.slider-box .slider {
background: var(--color-green-1);
}
}
:host([color='blue']) {
.slider-box .slider {
background: var(--color-blue-1);
}
}
:host([color='purple']) {
.slider-box .slider {
background: var(--color-purple-1);
}
}
:host([color='grey']) {
.slider-box .slider {
background: var(--color-grey-1);
}
}
:host([color='dark']) {
.slider-box .slider {
background: var(--color-dark-1);
}
}
:host([readonly]) {
.slider-box,
input {
cursor: default;
}
}
:host([disabled]) {
opacity: 0.6;
.slider-box,
input {
cursor: not-allowed;
}
}
</style>
<script>
import $ from '../utils'
export default class Slider {
props = {
value: 0,
max: 100,
min: 0,
step: 1,
vertical: false, // 竖向
readonly: false,
disabled: false,
list: '' // input[type=range]的原生属性, 配合 datalist
}
state = {
decimal: 0
}
__init__() {
/* render */
var elem = this.root.children[1]
this.__SLIDER__ = elem.children[0]
this.__RANGE__ = elem.children[1]
}
_updateRange() {
var { max, min, step } = this.props
var _decimal = (step + '').split('.')
this.__RANGE__.setAttribute('max', max)
this.__RANGE__.setAttribute('min', min)
this.__RANGE__.setAttribute('step', step)
if (_decimal.length > 1) {
this.state.decimal = _decimal[1].length
} else {
this.state.decimal = 0
}
}
_updateValue(val) {
var { max, min, vertical } = this.props
this.props.value = +val
this.__RANGE__.value = +val
if (vertical) {
this.__SLIDER__.style.height = `${(100 * val) / (max - min)}%`
} else {
this.__SLIDER__.style.width = `${(100 * val) / (max - min)}%`
}
}
get value() {
return +this.props.value.toFixed(this.state.decimal)
}
set value(val) {
this._updateValue(+val || 0)
}
get readOnly() {
return this.props.readonly
}
set readOnly(val) {
var type = typeof val
if (val === this.props.readonly) {
return
}
if ((type === 'boolean' && val) || type !== 'boolean') {
this.props.readonly = true
this.setAttribute('readonly', '')
this.__RANGE__.setAttribute('disabled', '')
} else {
this.props.readonly = false
this.removeAttribute('readonly')
if (this.props.disabled === false) {
this.__RANGE__.removeAttribute('disabled')
}
}
}
get disabled() {
return this.props.disabled
}
set disabled(val) {
var type = typeof val
if (val === this.props.disabled) {
return
}
if ((type === 'boolean' && val) || type !== 'boolean') {
this.props.disabled = true
this.setAttribute('disabled', '')
this.__RANGE__.setAttribute('disabled', '')
} else {
this.props.disabled = false
this.removeAttribute('disabled')
if (this.props.readonly === false) {
this.__RANGE__.removeAttribute('disabled')
}
}
}
mounted() {
this._inputFn = $.bind(this.__RANGE__, 'input', ev => {
this._updateValue(this.__RANGE__.value)
})
}
unmount() {
$.bind(this.__RANGE__, 'input', this._inputFn)
delete this._inputFn
}
watch() {
switch (name) {
case 'max':
case 'min':
case 'step':
this.props[name] = +val
this._updateRange()
break
case 'value':
this._updateValue(+val || 0)
break
case 'vertical':
this.props.vertical = true
break
case 'readonly':
this.readOnly = true
break
case 'disabled':
this.disabled = true
break
case 'list':
this.__RANGE__.setAttribute('list', val)
break
}
}
}
</script>

View File

@ -1,22 +0,0 @@
<template>
<label></label>
</template>
<style lang="scss">
:host {
display: flex;
}
</style>
<script>
export default class Slider {
props = {
value: 0,
max: 100
}
__init__() {
/* render */
}
}
</script>

View File

@ -1,278 +0,0 @@
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2018-06-25 21:39:42
* @version $Id$
*/
const __STORE__ = {}
// 解析and条件
function parse$And(it) {
let result = ''
for (let k in it) {
let tmp = it[k]
switch (Anot.type(tmp)) {
case 'object':
if (tmp.$has) {
result += `it.${k}.indexOf(${JSON.stringify(tmp.$has)}) > -1`
break
}
if (tmp.$in) {
result += `${JSON.stringify(tmp.$in)}.indexOf(it.${k}) > -1`
break
}
if (tmp.$regex) {
result += `${tmp.$regex}.test(it.${k})`
break
}
// 区间解析
if (tmp.$lt || tmp.$lte) {
result += `it.${k} <${tmp.$lte ? '=' : ''} ${tmp.$lt || tmp.$lte}`
if (tmp.$gt || tmp.$gte) {
result += ` && it.${k} >${tmp.$gte ? '=' : ''} ${tmp.$gt ||
tmp.$gte}`
}
break
}
if (tmp.$gt || tmp.$gte) {
result += `it.${k} >${tmp.$gte ? '=' : ''} ${tmp.$gt || tmp.$gte}`
break
}
if (tmp.$eq) {
result += `it.${k} === ${tmp.$eq}`
break
}
default:
result += `it.${k} === ${JSON.stringify(it[k])}`
break
}
result += ' && '
}
result = result.slice(0, -4)
if (!result) {
result = 'true'
}
return result
}
// 解析or条件
function parse$Or(arr) {
let result = ''
arr.forEach(it => {
result += '('
result += parse$And(it)
result += ') || '
})
return result.slice(0, -4)
}
class AnotStore {
constructor(name) {
Anot.hideProperty(this, '__name__', name)
Anot.hideProperty(this, '__LAST_QUERY__', '')
Anot.hideProperty(this, '__QUERY_HISTORY__', [])
if (!__STORE__[name]) {
__STORE__[name] = []
__STORE__[`${name}Dict`] = {}
}
}
static collection(name) {
return new this(name)
}
__MAKE_FN__(opt) {
let fnStr = `
let result = [];
let num = 0;
for (let it of arr) {
if(`
if (opt.$or) {
fnStr += parse$Or(opt.$or)
} else {
fnStr += parse$And(opt)
}
fnStr += `){
result.push(it)
num++
if(limit > 0 && num >= limit){
break
}
}
}
return result;`
return Function('arr', 'limit', fnStr)
}
// 清除当前集合的数据及缓存, 默认只清除缓存
clear(force) {
this.__QUERY_HISTORY__ = []
this.__LAST_QUERY__ = ''
if (force) {
__STORE__[this.__name__] = []
__STORE__[`${this.__name__}Dict`] = {}
}
}
// 查询多条记录,返回数组
getAll({ filter, limit = [] } = {}) {
const collection = __STORE__[this.__name__]
let result = []
let forceLimited = false // 强制限制查询结果集
if (!collection || !collection.length) {
return result
}
if (limit.length < 1) {
limit = [0]
}
if (limit.length < 2 && filter) {
forceLimited = true
if (limit[0] > 0) {
limit.unshift(0)
}
}
if (filter) {
let query = JSON.stringify(filter)
if (this.__LAST_QUERY__ === query) {
result = this.__QUERY_HISTORY__.slice.apply(
this.__QUERY_HISTORY__,
limit
)
} else {
let tmpFn = this.__MAKE_FN__(filter)
result = tmpFn(collection, forceLimited ? limit[1] || 0 : 0)
// 非强制限制的查询, 缓存结果集
if (!forceLimited) {
this.__LAST_QUERY__ = query
this.__QUERY_HISTORY__ = result
result = this.__QUERY_HISTORY__.slice.apply(
this.__QUERY_HISTORY__,
limit
)
}
}
} else {
result = collection.slice.apply(collection, limit)
}
return Anot.deepCopy(result)
}
// 查询单条记录, 返回Object对象
get(_id) {
const collectionDict = __STORE__[`${this.__name__}Dict`]
return Anot.deepCopy(collectionDict[_id]) || null
}
// 查询总数
count({ filter } = {}) {
if (filter) {
if (this.__LAST_QUERY__ === JSON.stringify(filter)) {
return this.__QUERY_HISTORY__.length
} else {
return this.getAll({ filter, limit: [0] }).length
}
}
return __STORE__[this.__name__].length
}
__INSERT__(item, primary) {
let collection = __STORE__[this.__name__]
let collectionDict = __STORE__[`${this.__name__}Dict`]
let _id = item[primary || 'id']
let tmp = collectionDict[_id]
// 已存在, 则直接更新
if (tmp) {
this.update(_id, item)
} else {
collection.push(item)
collectionDict[_id] = item
}
}
// 插入数据, 可以同时插入多条
insert(items, primary) {
if (!Array.isArray(items)) {
items = [items]
}
items.forEach(item => {
this.__INSERT__(item, primary)
})
this.clear()
}
// 按指定字段排序, 是否字符串排序, 是否逆序
sort(key, locale, desc) {
let fnStr = ''
if (locale && window.Intl) {
fnStr += `
let col = new Intl.Collator('zh')
`
}
if (desc) {
fnStr += 'return arr.sort((b, a) => {'
} else {
fnStr += 'return arr.sort((a, b) => {'
}
fnStr += `
let filter = function(val) {
try {
return val.${key} || ''
} catch (err) {
return ''
}
}
`
if (locale) {
if (window.Intl) {
fnStr += `return col.compare(filter(a), filter(b))`
} else {
fnStr += `return (filter(a) + '').localeCompare(filter(b), 'zh')`
}
} else {
fnStr += `return filter(a) - filter(b)`
}
fnStr += '\n})'
Function('arr', fnStr).call(this, __STORE__[this.__name__])
this.clear()
}
// 更新集合中的数据
update(_id, data) {
let collection = __STORE__[this.__name__]
let collectionDict = __STORE__[`${this.__name__}Dict`]
let tmp = collectionDict[_id]
let idx = collection.indexOf(tmp)
collection.splice(idx, 1, data)
collectionDict[_id] = data
}
// 删除集合中单条数据
remove(_id) {
let collection = __STORE__[this.__name__]
let collectionDict = __STORE__[`${this.__name__}Dict`]
let tmp = collectionDict[_id]
let idx = collection.indexOf(tmp)
collection.splice(idx, 1)
delete collectionDict[_id]
}
}
Anot.store = window.store = AnotStore
export default AnotStore

View File

@ -11,7 +11,7 @@
:host { :host {
display: flex; display: flex;
width: 100%; width: 100%;
color: nth($cd, 1); color: var(--color-dark-1);
} }
.table { .table {
display: flex; display: flex;
@ -23,9 +23,9 @@
.tfoot { .tfoot {
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
border: 1px solid nth($cp, 3); border: 1px solid var(--color-plain-3);
--border-bottom: 0; --border-bottom: 0;
background: nth($cp, 1); background: var(--color-plain-1);
user-select: none; user-select: none;
-moz-user-select: none; -moz-user-select: none;
} }
@ -38,7 +38,7 @@
} }
.tbody { .tbody {
flex: 1; flex: 1;
border: 1px solid nth($cp, 3); border: 1px solid var(--color-plain-3);
::slotted(wc-tr:last-child) { ::slotted(wc-tr:last-child) {
--border-bottom: 0; --border-bottom: 0;

View File

@ -10,7 +10,7 @@
min-width: 60px; min-width: 60px;
// min-height: 36px; // min-height: 36px;
// height: auto; // height: auto;
border-left: 1px solid nth($cp, 3); border-left: 1px solid var(--color-plain-3);
.cell { .cell {
padding: var(--padding, 5px 8px); padding: var(--padding, 5px 8px);

View File

@ -12,7 +12,7 @@
flex: 1; flex: 1;
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
border-bottom: var(--border-bottom, 1px solid nth($cp, 3)); border-bottom: var(--border-bottom, 1px solid var(--color-plain-3));
} }
} }
:host(:hover) { :host(:hover) {

View File

@ -31,6 +31,28 @@ export default {
} }
})(), })(),
//取得距离页面左上角的坐标
offset(node) {
try {
var rect = node.getBoundingClientRect()
if (rect.width || rect.height || node.getClientRects().length) {
var doc = node.ownerDocument
var root = doc.documentElement
var win = doc.defaultView
return {
top: rect.top + win.pageYOffset - root.clientTop,
left: rect.left + win.pageXOffset - root.clientLeft
}
}
} catch (e) {
return {
left: 0,
top: 0
}
}
},
/** /**
* 对象/数组遍历 * 对象/数组遍历
* 支持跳出 * 支持跳出
@ -112,6 +134,7 @@ export default {
fn(ev) fn(ev)
}) })
}, },
clearOutside(fn = noop) { clearOutside(fn = noop) {
this.unbind(document, 'mousedown', fn) this.unbind(document, 'mousedown', fn)
} }