完成基础布局
							parent
							
								
									59157e181b
								
							
						
					
					
						commit
						a82c5391c2
					
				| 
						 | 
					@ -12,7 +12,7 @@
 | 
				
			||||||
  <script type="importmap">{{importmap}}</script>
 | 
					  <script type="importmap">{{importmap}}</script>
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
  <div class="app noselect"></div>
 | 
					  <div class="app"></div>
 | 
				
			||||||
  <script src="main.js"></script>
 | 
					  <script src="main.js"></script>
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					<svg width="41" height="41" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M37.532 16.87a9.963 9.963 0 00-.856-8.184 10.078 10.078 0 00-10.855-4.835A9.964 9.964 0 0018.306.5a10.079 10.079 0 00-9.614 6.977 9.967 9.967 0 00-6.664 4.834 10.08 10.08 0 001.24 11.817 9.965 9.965 0 00.856 8.185 10.079 10.079 0 0010.855 4.835 9.965 9.965 0 007.516 3.35 10.078 10.078 0 009.617-6.981 9.967 9.967 0 006.663-4.834 10.079 10.079 0 00-1.243-11.813zM22.498 37.886a7.474 7.474 0 01-4.799-1.735c.061-.033.168-.091.237-.134l7.964-4.6a1.294 1.294 0 00.655-1.134V19.054l3.366 1.944a.12.12 0 01.066.092v9.299a7.505 7.505 0 01-7.49 7.496zM6.392 31.006a7.471 7.471 0 01-.894-5.023c.06.036.162.099.237.141l7.964 4.6a1.297 1.297 0 001.308 0l9.724-5.614v3.888a.12.12 0 01-.048.103l-8.051 4.649a7.504 7.504 0 01-10.24-2.744zM4.297 13.62A7.469 7.469 0 018.2 10.333c0 .068-.004.19-.004.274v9.201a1.294 1.294 0 00.654 1.132l9.723 5.614-3.366 1.944a.12.12 0 01-.114.01L7.04 23.856a7.504 7.504 0 01-2.743-10.237zm27.658 6.437l-9.724-5.615 3.367-1.943a.121.121 0 01.113-.01l8.052 4.648a7.498 7.498 0 01-1.158 13.528v-9.476a1.293 1.293 0 00-.65-1.132zm3.35-5.043c-.059-.037-.162-.099-.236-.141l-7.965-4.6a1.298 1.298 0 00-1.308 0l-9.723 5.614v-3.888a.12.12 0 01.048-.103l8.05-4.645a7.497 7.497 0 0111.135 7.763zm-21.063 6.929l-3.367-1.944a.12.12 0 01-.065-.092v-9.299a7.497 7.497 0 0112.293-5.756 6.94 6.94 0 00-.236.134l-7.965 4.6a1.294 1.294 0 00-.654 1.132l-.006 11.225zm1.829-3.943l4.33-2.501 4.332 2.5v5l-4.331 2.5-4.331-2.5V18z" fill="#fff"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.5 KiB  | 
							
								
								
									
										70
									
								
								src/app.vue
								
								
								
								
							
							
						
						
									
										70
									
								
								src/app.vue
								
								
								
								
							| 
						 | 
					@ -1,63 +1,45 @@
 | 
				
			||||||
 | 
					 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <header>
 | 
					  <Topbar />
 | 
				
			||||||
    <img alt="Vue logo" class="logo" src="/assets/logo.svg" width="125" height="125" />
 | 
					  <div class="main">
 | 
				
			||||||
 | 
					    <Aside />
 | 
				
			||||||
    <div class="wrapper">
 | 
					    <Chat />
 | 
				
			||||||
      <Hello msg="It works!!!" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <nav>
 | 
					 | 
				
			||||||
        <router-link to="/">Home</router-link>
 | 
					 | 
				
			||||||
        <router-link to="/about">About</router-link>
 | 
					 | 
				
			||||||
      </nav>
 | 
					 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  </header>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <router-view />
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
import Hello from './components/hello.vue'
 | 
					import Topbar from './views/topbar.vue'
 | 
				
			||||||
 | 
					import Aside from './views/aside.vue'
 | 
				
			||||||
 | 
					import Chat from './views/chat.vue'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  components: { Hello }
 | 
					  components: { Topbar, Aside, Chat }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
.app {
 | 
					body {
 | 
				
			||||||
  padding: 16px;
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 100vh;
 | 
				
			||||||
 | 
					  padding: 32px;
 | 
				
			||||||
 | 
					  font-size: 14px;
 | 
				
			||||||
 | 
					  color: var(--color-dark-1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.app {
 | 
				
			||||||
header {
 | 
					 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  flex-direction: column;
 | 
					  flex-direction: column;
 | 
				
			||||||
  align-items: center;
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  max-width: 1280px;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  border-radius: 4px;
 | 
				
			||||||
 | 
					  background: #fff;
 | 
				
			||||||
 | 
					  box-shadow: 0 0 16px rgba(0, 0, 0, 0.15);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
a {
 | 
					.main {
 | 
				
			||||||
  color: var(--color-teal-1);
 | 
					  flex: 1;
 | 
				
			||||||
  transition: 0.2s;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
a:hover {
 | 
					 | 
				
			||||||
  color: var(--color-teal-3);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
nav {
 | 
					 | 
				
			||||||
  margin-top: 32px;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  justify-content: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  a {
 | 
					 | 
				
			||||||
    margin: 0 16px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main {
 | 
					 | 
				
			||||||
  margin: 32px;
 | 
					 | 
				
			||||||
  text-align: center;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
  
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1 +0,0 @@
 | 
				
			||||||
<svg viewBox="0 0 261.76 226.69" xmlns="http://www.w3.org/2000/svg"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/><path d="M36.21 192.639l160.921-74.805-81.778-5.063 119.519-67.69L49.06 126.138l88.8 2.712z" fill="rgb(252, 118, 97)"/></svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 394 B  | 
| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div v-if="name" class="avatar name">{{ name.slice(-1) }}</div>
 | 
				
			||||||
 | 
					  <img v-else class="avatar" src="/img/chatgpt.svg" />
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    name: ''
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.avatar {
 | 
				
			||||||
 | 
					  flex-shrink: 0;
 | 
				
			||||||
 | 
					  width: 36px;
 | 
				
			||||||
 | 
					  height: 36px;
 | 
				
			||||||
 | 
					  padding: 8px;
 | 
				
			||||||
 | 
					  border-radius: 50%;
 | 
				
			||||||
 | 
					  background: var(--color-dark-1);
 | 
				
			||||||
 | 
					  color: #fff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &.name {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    padding: 4px;
 | 
				
			||||||
 | 
					    font-size: 18px;
 | 
				
			||||||
 | 
					    background: var(--color-orange-1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -1,40 +0,0 @@
 | 
				
			||||||
 | 
					 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
  <div class="greetings">
 | 
					 | 
				
			||||||
    <h1 class="green">{{ msg }}</h1>
 | 
					 | 
				
			||||||
    <h3>
 | 
					 | 
				
			||||||
      你已经成功运行了一个项目, 项目基于
 | 
					 | 
				
			||||||
      <a href="//github.com/bytedo/vue-live" target="_blank">Vue-live</a> +
 | 
					 | 
				
			||||||
      <a href="//vuejs.org" target="_blank">Vue 3</a>.
 | 
					 | 
				
			||||||
    </h3>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
export default {
 | 
					 | 
				
			||||||
  props: {
 | 
					 | 
				
			||||||
    msg: String
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style>
 | 
					 | 
				
			||||||
h1 {
 | 
					 | 
				
			||||||
  font-size: 52px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
h3 {
 | 
					 | 
				
			||||||
  font-size: 1.2rem;
 | 
					 | 
				
			||||||
  font-weight: normal;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.green {
 | 
					 | 
				
			||||||
  font-family: 'Courier New', Courier, monospace;
 | 
					 | 
				
			||||||
  color: var(--color-blue-1);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.greetings {
 | 
					 | 
				
			||||||
  text-align: center;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
   
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author yutent<yutent.io@gmail.com>
 | 
				
			||||||
 | 
					 * @date 2021/03/24 11:50:09
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import fetch from 'fetch'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fetch.BASE_URL = localStorage.getItem('BASE_URL')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fetch.inject.request(conf => {
 | 
				
			||||||
 | 
					  conf.headers['content-type'] = 'application/json'
 | 
				
			||||||
 | 
					  conf.timeout = 60 * 1000
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fetch.inject.response(res => res.json())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function post(url, body = {}) {
 | 
				
			||||||
 | 
					  return fetch(url, {
 | 
				
			||||||
 | 
					    method: 'post',
 | 
				
			||||||
 | 
					    body
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export function get(url, body = {}) {
 | 
				
			||||||
 | 
					  return fetch(url, { body })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default fetch
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,586 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * markdown解析器
 | 
				
			||||||
 | 
					 * @author yutent<yutent.io@gmail.com>
 | 
				
			||||||
 | 
					 * @date 2020/02/07 17:14:19
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const HR_LIST = ['=', '-', '_', '*']
 | 
				
			||||||
 | 
					const LIST_RE = /^(([\+\-\*])|(\d+\.))\s/
 | 
				
			||||||
 | 
					const TODO_RE = /^[\+\-\*]\s\[(x|\s)\]\s/
 | 
				
			||||||
 | 
					const ESCAPE_RE = /\\([-+*_`\]\[\(\)])/g
 | 
				
			||||||
 | 
					const QLINK_RE = /^\[(\d+)\]: ([\S]+)\s*?((['"])[\s\S]*?\4)?\s*?$/
 | 
				
			||||||
 | 
					const TAG_RE = /<([\w\-]+)([\w\W]*?)>/g
 | 
				
			||||||
 | 
					const ATTR_RE = /\s*?on[a-zA-Z]+="[^"]*?"\s*?/g
 | 
				
			||||||
 | 
					const CODEBLOCK_RE = /```(.*?)([\w\W]*?)```/g
 | 
				
			||||||
 | 
					const BLOCK_RE = /<([\w\-]+)([^>]*?)>([\w\W]*?)<\/\1>/g
 | 
				
			||||||
 | 
					const IS_DOM_RE = /^<([\w\-]+)[^>]*?>.*?<\/\1>$/
 | 
				
			||||||
 | 
					const STYLE_RE = /<style[^>]*?>([\w\W]*?)<\/style>/g
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const INLINE = {
 | 
				
			||||||
 | 
					  code: /`([^`]*?[^`\\\s])`/g,
 | 
				
			||||||
 | 
					  strong: [/__([\s\S]*?[^\s\\])__(?!_)/g, /\*\*([\s\S]*?[^\s\\])\*\*(?!\*)/g],
 | 
				
			||||||
 | 
					  em: [/_([\s\S]*?[^\s\\])_(?!_)/g, /\*([\s\S]*?[^\s\\*])\*(?!\*)/g],
 | 
				
			||||||
 | 
					  del: /~~([\s\S]*?[^\s\\~])~~/g,
 | 
				
			||||||
 | 
					  qlink: /\[([^\]]*?)\]\[(\d*?)\]/g, // 引用链接
 | 
				
			||||||
 | 
					  img: /\!\[([^\]]*?)\]\(([^)]*?)\)/g,
 | 
				
			||||||
 | 
					  a: /\[([^\]]*?)\]\(([^)]*?)(\s+"([\s\S]*?)")*?\)/g,
 | 
				
			||||||
 | 
					  qlist: /((<blockquote class="md\-quote">)*?)([\+\-\*]|\d+\.) (.*)/ // 引用中的列表
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ATTR_BR_SYMBOL = '⨨☇'
 | 
				
			||||||
 | 
					const NODE_BR_SYMBOL = '⨨⤶'
 | 
				
			||||||
 | 
					const ATTR_BR_EXP = new RegExp(ATTR_BR_SYMBOL, 'g')
 | 
				
			||||||
 | 
					const NODE_BR_EXP = new RegExp(NODE_BR_SYMBOL, 'g')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Helper = {
 | 
				
			||||||
 | 
					  // 是否分割线
 | 
				
			||||||
 | 
					  isHr(str) {
 | 
				
			||||||
 | 
					    var s = str[0]
 | 
				
			||||||
 | 
					    if (HR_LIST.includes(s)) {
 | 
				
			||||||
 | 
					      return str.slice(0, 3) === s.repeat(3) ? str.slice(3) : false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  // 是否列表, -1不是, 1为有序列表, 0为无序列表
 | 
				
			||||||
 | 
					  isList(str) {
 | 
				
			||||||
 | 
					    var v = str.trim()
 | 
				
			||||||
 | 
					    if (LIST_RE.test(v)) {
 | 
				
			||||||
 | 
					      var n = +v[0]
 | 
				
			||||||
 | 
					      if (n === n) {
 | 
				
			||||||
 | 
					        return 1
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return 0
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return -1
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  // 是否任务列表
 | 
				
			||||||
 | 
					  isTodo(str) {
 | 
				
			||||||
 | 
					    var v = str.trim()
 | 
				
			||||||
 | 
					    if (TODO_RE.test(v)) {
 | 
				
			||||||
 | 
					      return v[3] === 'x' ? 1 : 0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return -1
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  ltrim(str) {
 | 
				
			||||||
 | 
					    if (str.trimStart) {
 | 
				
			||||||
 | 
					      return str.trimStart()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return str.replace(/^\s+/, '')
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  isQLink(str) {
 | 
				
			||||||
 | 
					    if (QLINK_RE.test(str)) {
 | 
				
			||||||
 | 
					      // l: link,  t: title, $1: index
 | 
				
			||||||
 | 
					      return { [RegExp.$1]: { l: RegExp.$2, t: RegExp.$3 } }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  isTable(str) {
 | 
				
			||||||
 | 
					    return /^\|.+?\|$/.test(str)
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  // 是否原生dom节点
 | 
				
			||||||
 | 
					  isNativeDom(str) {
 | 
				
			||||||
 | 
					    return IS_DOM_RE.test(str)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Decoder = {
 | 
				
			||||||
 | 
					  // 内联样式
 | 
				
			||||||
 | 
					  inline(str) {
 | 
				
			||||||
 | 
					    return str
 | 
				
			||||||
 | 
					      .replace(INLINE.code, '<code class="inline">$1</code>')
 | 
				
			||||||
 | 
					      .replace(INLINE.strong[0], '<strong>$1</strong>')
 | 
				
			||||||
 | 
					      .replace(INLINE.strong[1], '<strong>$1</strong>')
 | 
				
			||||||
 | 
					      .replace(INLINE.em[0], '<em>$1</em>')
 | 
				
			||||||
 | 
					      .replace(INLINE.em[1], '<em>$1</em>')
 | 
				
			||||||
 | 
					      .replace(INLINE.del, '<del>$1</del>')
 | 
				
			||||||
 | 
					      .replace(INLINE.img, '<img src="$2" alt="$1">')
 | 
				
			||||||
 | 
					      .replace(INLINE.a, (m1, txt, link, m2, attr = '') => {
 | 
				
			||||||
 | 
					        var tmp = attr
 | 
				
			||||||
 | 
					          .split(';')
 | 
				
			||||||
 | 
					          .filter(_ => _)
 | 
				
			||||||
 | 
					          .map(_ => {
 | 
				
			||||||
 | 
					            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_RE, '$1') // 处理转义字符
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  // 分割线
 | 
				
			||||||
 | 
					  hr(name = '') {
 | 
				
			||||||
 | 
					    return `<fieldset class="md-hr"><legend name="${name}"></legend></fieldset>`
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  // 标题
 | 
				
			||||||
 | 
					  head(str) {
 | 
				
			||||||
 | 
					    if (str.startsWith('#')) {
 | 
				
			||||||
 | 
					      return str.replace(/^(#{1,6}) (.*)/, (p, m1, m2) => {
 | 
				
			||||||
 | 
					        m2 = m2.trim()
 | 
				
			||||||
 | 
					        let level = m1.trim().length
 | 
				
			||||||
 | 
					        let hash = m2.replace(/\s/g, '').replace(/<\/?[^>]*?>/g, '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (level === 1) {
 | 
				
			||||||
 | 
					          return `<h1>${m2}</h1>`
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          return `<h${level}><a href="#${hash}" id="${hash}" class="md-head-link">${m2}</a></h${level}>`
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  // 引用模块
 | 
				
			||||||
 | 
					  blockquote(str) {
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  // 任务
 | 
				
			||||||
 | 
					  task(str) {
 | 
				
			||||||
 | 
					    var todoChecked = Helper.isTodo(str)
 | 
				
			||||||
 | 
					    if (~todoChecked) {
 | 
				
			||||||
 | 
					      var word = str.replace(TODO_RE, '').trim()
 | 
				
			||||||
 | 
					      var stat = todoChecked === 1 ? 'checked' : ''
 | 
				
			||||||
 | 
					      var txt = todoChecked === 1 ? `<del>${word}</del>` : word
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return `<section><wc-checkbox readonly ${stat}>${txt}</wc-checkbox></section>`
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function fixed(str) {
 | 
				
			||||||
 | 
					  // 去掉\r, 将\t转为空格(2个)
 | 
				
			||||||
 | 
					  return str
 | 
				
			||||||
 | 
					    .replace(/\r\n|\r/g, '\n')
 | 
				
			||||||
 | 
					    .replace(/\t/g, '  ')
 | 
				
			||||||
 | 
					    .replace(/\u00a0/g, ' ')
 | 
				
			||||||
 | 
					    .replace(/\u2424/g, '\n')
 | 
				
			||||||
 | 
					    .replace(TAG_RE, (m, name, attr) => {
 | 
				
			||||||
 | 
					      // 标签内的换行, 转为一组特殊字符, 方便后面还原
 | 
				
			||||||
 | 
					      return `<${name + attr.replace(/\n/g, ATTR_BR_SYMBOL)}>`
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .replace(BLOCK_RE, (m, tag, attr, txt) => {
 | 
				
			||||||
 | 
					      return `<${tag + attr}>${txt.replace(/\n/g, NODE_BR_SYMBOL)}</${tag}>`
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .replace(CODEBLOCK_RE, (m, lang, txt) => {
 | 
				
			||||||
 | 
					      // 还原换行
 | 
				
			||||||
 | 
					      let rollback = txt.replace(NODE_BR_EXP, '\n').replace(ATTR_BR_EXP, '\n')
 | 
				
			||||||
 | 
					      return '```' + lang + rollback + '```'
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .replace(BLOCK_RE, (m, tag, attr, txt) => {
 | 
				
			||||||
 | 
					      return `<${tag + attr.replace(ATTR_BR_EXP, ' ')}>${txt
 | 
				
			||||||
 | 
					        .replace(NODE_BR_EXP, '\n')
 | 
				
			||||||
 | 
					        .replace(ATTR_BR_EXP, ' ')}</${tag}>`
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Tool {
 | 
				
			||||||
 | 
					  constructor(list, links) {
 | 
				
			||||||
 | 
					    this.list = list
 | 
				
			||||||
 | 
					    this.__LINKS__ = links
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // 初始化字符串, 处理多余换行等
 | 
				
			||||||
 | 
					  static init(str) {
 | 
				
			||||||
 | 
					    var links = {}
 | 
				
			||||||
 | 
					    var list = []
 | 
				
			||||||
 | 
					    var lines = str.split('\n')
 | 
				
			||||||
 | 
					    var isCodeBlock = false // 是否代码块
 | 
				
			||||||
 | 
					    var isTable = false // 是否表格
 | 
				
			||||||
 | 
					    var emptyLineLength = 0 //连续空行的数量
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // console.log(lines)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let it of lines) {
 | 
				
			||||||
 | 
					      let tmp = it.trim()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // 非空行
 | 
				
			||||||
 | 
					      if (tmp) {
 | 
				
			||||||
 | 
					        emptyLineLength = 0
 | 
				
			||||||
 | 
					        if (tmp.startsWith('```')) {
 | 
				
			||||||
 | 
					          if (isCodeBlock) {
 | 
				
			||||||
 | 
					            list.push('</xmp></wc-code>')
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            list.push(
 | 
				
			||||||
 | 
					              tmp.replace(/^```([\w\#\-]*?)$/, `<wc-code lang="$1"><xmp>`)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          isCodeBlock = !isCodeBlock
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          var qlink
 | 
				
			||||||
 | 
					          if (isCodeBlock) {
 | 
				
			||||||
 | 
					            it = it
 | 
				
			||||||
 | 
					              .replace(/<(\/?)([a-z][a-z\d\-]*?)([^>]*?)>/g, '<$1$2$3>')
 | 
				
			||||||
 | 
					              .replace('\\`\\`\\`', '```')
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            if (Helper.isTable(tmp) && !isTable) {
 | 
				
			||||||
 | 
					              var thead = tmp.split('|')
 | 
				
			||||||
 | 
					              // 去头去尾
 | 
				
			||||||
 | 
					              thead.shift()
 | 
				
			||||||
 | 
					              thead.pop()
 | 
				
			||||||
 | 
					              list.push(
 | 
				
			||||||
 | 
					                `<table><thead><tr>${thead
 | 
				
			||||||
 | 
					                  .map(_ => `<th>${_}</th>`)
 | 
				
			||||||
 | 
					                  .join('')}</tr></thead><tbody>`
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					              isTable = true
 | 
				
			||||||
 | 
					              continue
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              it = it
 | 
				
			||||||
 | 
					                // 非代码块进行xss过滤
 | 
				
			||||||
 | 
					                .replace(INLINE.code, (m, txt) => {
 | 
				
			||||||
 | 
					                  return `\`${txt
 | 
				
			||||||
 | 
					                    .replace(/</g, '<')
 | 
				
			||||||
 | 
					                    .replace(/>/g, '>')}\``
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .replace(/<(\/?)script[^>]*?>/g, '<$1script>')
 | 
				
			||||||
 | 
					                .replace(TAG_RE, (m, name, attr = '') => {
 | 
				
			||||||
 | 
					                  // 过滤所有onXX=""事件属性
 | 
				
			||||||
 | 
					                  attr = attr.replace(ATTR_RE, ' ').trim()
 | 
				
			||||||
 | 
					                  if (attr) {
 | 
				
			||||||
 | 
					                    attr = ' ' + attr
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  return `<${name}${attr}>`
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					              // 不在代码块中, 才判断引用声明
 | 
				
			||||||
 | 
					              qlink = Helper.isQLink(it)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (qlink) {
 | 
				
			||||||
 | 
					            Object.assign(links, qlink)
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            list.push(it)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        if (isTable) {
 | 
				
			||||||
 | 
					          isTable = false
 | 
				
			||||||
 | 
					          list.push('</tbody></table>')
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (list.length === 0 || (!isCodeBlock && emptyLineLength > 0)) {
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        emptyLineLength++
 | 
				
			||||||
 | 
					        list.push(tmp)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return new this(list, links)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parse() {
 | 
				
			||||||
 | 
					    var html = ''
 | 
				
			||||||
 | 
					    var isCodeBlock = false // 是否代码块
 | 
				
			||||||
 | 
					    var emptyLineLength = 0 //连续空行的数量
 | 
				
			||||||
 | 
					    var isBlockquote = false
 | 
				
			||||||
 | 
					    var isTable = false
 | 
				
			||||||
 | 
					    var tableAlign = null
 | 
				
			||||||
 | 
					    var blockquoteLevel = 0
 | 
				
			||||||
 | 
					    var isParagraph = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var isList = false
 | 
				
			||||||
 | 
					    var orderListLevel = -1
 | 
				
			||||||
 | 
					    var unorderListLevel = -1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var isQuoteList = false // 引用中的列表, 只支持一层级
 | 
				
			||||||
 | 
					    var quoteListStyle = 0 // 1有序,  2 无序
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    for (let it of this.list) {
 | 
				
			||||||
 | 
					      // 非空行
 | 
				
			||||||
 | 
					      if (it) {
 | 
				
			||||||
 | 
					        emptyLineLength = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (~it.indexOf('<table>') || ~it.indexOf('</table>')) {
 | 
				
			||||||
 | 
					          html += it
 | 
				
			||||||
 | 
					          isTable = !isTable
 | 
				
			||||||
 | 
					          tableAlign = true
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isTable) {
 | 
				
			||||||
 | 
					          let tmp = it.split('|').map(_ => _.trim())
 | 
				
			||||||
 | 
					          tmp.shift()
 | 
				
			||||||
 | 
					          tmp.pop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // 表格分割行, 配置对齐方式的
 | 
				
			||||||
 | 
					          if (tableAlign === true) {
 | 
				
			||||||
 | 
					            tableAlign = tmp.map(a => {
 | 
				
			||||||
 | 
					              a = a.split(/\-+/)
 | 
				
			||||||
 | 
					              if (a[0] === ':' && a[1] === ':') {
 | 
				
			||||||
 | 
					                return 'align="center"'
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              if (a[1] === ':') {
 | 
				
			||||||
 | 
					                return 'align="right"'
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              return ''
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          html += `<tr>${tmp
 | 
				
			||||||
 | 
					            .map(
 | 
				
			||||||
 | 
					              (_, i) =>
 | 
				
			||||||
 | 
					                `<td ${tableAlign[i]}>${Decoder.inline.call(this, _)}</td>`
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .join('')}</tr>`
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // wc-code标签直接拼接, 判断时多拼一个 < 和 >,
 | 
				
			||||||
 | 
					        // 是为了避免在 wc-markd嵌入代码块示例时, 将其内容编译为html
 | 
				
			||||||
 | 
					        if (~it.indexOf('<wc-code') || ~it.indexOf('wc-code>')) {
 | 
				
			||||||
 | 
					          if (isParagraph) {
 | 
				
			||||||
 | 
					            isParagraph = false
 | 
				
			||||||
 | 
					            html += '</p>'
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          html += it
 | 
				
			||||||
 | 
					          isCodeBlock = !isCodeBlock
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 同上代码块的处理
 | 
				
			||||||
 | 
					        if (isCodeBlock) {
 | 
				
			||||||
 | 
					          html += '\n' + it
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 无属性标签
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let hrName = Helper.isHr(it)
 | 
				
			||||||
 | 
					        if (typeof hrName === 'string') {
 | 
				
			||||||
 | 
					          html += Decoder.hr(hrName)
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 优先处理一些常规样式
 | 
				
			||||||
 | 
					        it = Decoder.inline.call(this, it)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 标题只能是单行
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let head = Decoder.head(it)
 | 
				
			||||||
 | 
					        if (head) {
 | 
				
			||||||
 | 
					          isParagraph = false
 | 
				
			||||||
 | 
					          html += head
 | 
				
			||||||
 | 
					          // console.log(html)
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 引用
 | 
				
			||||||
 | 
					        if (it.startsWith('>')) {
 | 
				
			||||||
 | 
					          let innerQuote // 是否有缩进引用
 | 
				
			||||||
 | 
					          it = it.replace(/^(>+) /, (p, m) => {
 | 
				
			||||||
 | 
					            let len = m.length
 | 
				
			||||||
 | 
					            let tmp = ''
 | 
				
			||||||
 | 
					            let loop = len
 | 
				
			||||||
 | 
					            // 若之前已经有一个未闭合的引用, 需要减去已有缩进级别, 避免产生新的引用标签
 | 
				
			||||||
 | 
					            if (isBlockquote) {
 | 
				
			||||||
 | 
					              loop = len - blockquoteLevel
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while (loop > 0) {
 | 
				
			||||||
 | 
					              loop--
 | 
				
			||||||
 | 
					              tmp += '<blockquote class="md-quote">'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            blockquoteLevel = len
 | 
				
			||||||
 | 
					            innerQuote = !!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 {
 | 
				
			||||||
 | 
					            if (innerQuote === false) {
 | 
				
			||||||
 | 
					              html += '<br>'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            html += it
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          isParagraph = false
 | 
				
			||||||
 | 
					          isBlockquote = true
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 任务
 | 
				
			||||||
 | 
					        let task = Decoder.task(it)
 | 
				
			||||||
 | 
					        if (task) {
 | 
				
			||||||
 | 
					          html += task
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 列表
 | 
				
			||||||
 | 
					        let listChecked = Helper.isList(it)
 | 
				
			||||||
 | 
					        if (~listChecked) {
 | 
				
			||||||
 | 
					          // 左侧空格长度
 | 
				
			||||||
 | 
					          let tmp = Helper.ltrim(it)
 | 
				
			||||||
 | 
					          let ltrim = it.length - tmp.length
 | 
				
			||||||
 | 
					          let word = tmp.replace(LIST_RE, '').trim()
 | 
				
			||||||
 | 
					          let level = Math.floor(ltrim / 2)
 | 
				
			||||||
 | 
					          let tag = listChecked > 0 ? 'ol' : 'ul'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (isList) {
 | 
				
			||||||
 | 
					            if (listChecked === 1) {
 | 
				
			||||||
 | 
					              if (level > orderListLevel) {
 | 
				
			||||||
 | 
					                html = html.replace(/<\/li>$/, '')
 | 
				
			||||||
 | 
					                html += `<${tag}><li>${word}</li>`
 | 
				
			||||||
 | 
					              } else if (level === orderListLevel) {
 | 
				
			||||||
 | 
					                html += `<li>${word}</li>`
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                html += `</${tag}></li><li>${word}</li>`
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              orderListLevel = level
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              if (level > unorderListLevel) {
 | 
				
			||||||
 | 
					                html = html.replace(/<\/li>$/, '')
 | 
				
			||||||
 | 
					                html += `<${tag}><li>${word}</li>`
 | 
				
			||||||
 | 
					              } else if (level === unorderListLevel) {
 | 
				
			||||||
 | 
					                html += `<li>${word}</li>`
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                html += `</${tag}></li><li>${word}</li>`
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              unorderListLevel = level
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            html += `<${tag}>`
 | 
				
			||||||
 | 
					            if (listChecked === 1) {
 | 
				
			||||||
 | 
					              orderListLevel = level
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              unorderListLevel = level
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            html += `<li>${word}</li>`
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          isList = true
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 无"> "前缀的引用, 继续拼到之前的, 并且不换行
 | 
				
			||||||
 | 
					        if (isBlockquote) {
 | 
				
			||||||
 | 
					          html += it
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (Helper.isNativeDom(it)) {
 | 
				
			||||||
 | 
					          html += it
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isParagraph) {
 | 
				
			||||||
 | 
					          html += `${it}<br>`
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          html += `<p>${it}<br>`
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        isParagraph = true
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        // 如果是在代码中, 直接拼接, 并加上换行
 | 
				
			||||||
 | 
					        if (isCodeBlock) {
 | 
				
			||||||
 | 
					          html += it + '\n'
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          emptyLineLength++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // 引用结束
 | 
				
			||||||
 | 
					          if (isBlockquote) {
 | 
				
			||||||
 | 
					            isBlockquote = false
 | 
				
			||||||
 | 
					            if (emptyLineLength > 1) {
 | 
				
			||||||
 | 
					              emptyLineLength = 0
 | 
				
			||||||
 | 
					              while (blockquoteLevel > 0) {
 | 
				
			||||||
 | 
					                blockquoteLevel--
 | 
				
			||||||
 | 
					                html += '</blockquote>'
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (isList) {
 | 
				
			||||||
 | 
					            console.log('isList: ', emptyLineLength)
 | 
				
			||||||
 | 
					            if (emptyLineLength > 1) {
 | 
				
			||||||
 | 
					              while (orderListLevel > -1 || unorderListLevel > -1) {
 | 
				
			||||||
 | 
					                if (orderListLevel > unorderListLevel) {
 | 
				
			||||||
 | 
					                  html += '</ol>'
 | 
				
			||||||
 | 
					                  orderListLevel--
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                  html += '</ul>'
 | 
				
			||||||
 | 
					                  unorderListLevel--
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              isList = false
 | 
				
			||||||
 | 
					              emptyLineLength = 0
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          //
 | 
				
			||||||
 | 
					          if (isParagraph) {
 | 
				
			||||||
 | 
					            if (emptyLineLength > 1) {
 | 
				
			||||||
 | 
					              isParagraph = false
 | 
				
			||||||
 | 
					              html += '</p>'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 修正内嵌样式
 | 
				
			||||||
 | 
					    html = html.replace(STYLE_RE, (m, code) => {
 | 
				
			||||||
 | 
					      return `<style>${code
 | 
				
			||||||
 | 
					        .replace(/<br>/g, '')
 | 
				
			||||||
 | 
					        .replace(/<p>/g, '')
 | 
				
			||||||
 | 
					        .replace(/<\/p>/g, '')}</style>`
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    delete this.list
 | 
				
			||||||
 | 
					    delete this.__LINKS__
 | 
				
			||||||
 | 
					    return html
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function (str) {
 | 
				
			||||||
 | 
					  return Tool.init(fixed(str)).parse()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,130 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {}
 | 
				
			||||||
 | 
					 * @author yutent<yutent.io@gmail.com>
 | 
				
			||||||
 | 
					 * @date 2023/03/20 18:02:01
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import { html, css, Component, nextTick } from '@bd/core'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Code extends Component {
 | 
				
			||||||
 | 
					  static props = {
 | 
				
			||||||
 | 
					    code: { type: String, default: '', attribute: false },
 | 
				
			||||||
 | 
					    lang: ''
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static styles = [
 | 
				
			||||||
 | 
					    css`
 | 
				
			||||||
 | 
					      :host {
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        border-radius: 3px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .code-box {
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					        position: relative;
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					        margin: 10px 0;
 | 
				
			||||||
 | 
					        border-radius: 3px;
 | 
				
			||||||
 | 
					        background: #f7f8fb;
 | 
				
			||||||
 | 
					        color: var(--color-dark-1);
 | 
				
			||||||
 | 
					        box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .title {
 | 
				
			||||||
 | 
					        flex-shrink: 0;
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        justify-content: space-between;
 | 
				
			||||||
 | 
					        align-items: center;
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					        height: 32px;
 | 
				
			||||||
 | 
					        padding: 0 12px;
 | 
				
			||||||
 | 
					        line-height: 1;
 | 
				
			||||||
 | 
					        font-size: 14px;
 | 
				
			||||||
 | 
					        user-select: none;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .title section {
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        align-items: center;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .title i {
 | 
				
			||||||
 | 
					        display: block;
 | 
				
			||||||
 | 
					        width: 12px;
 | 
				
			||||||
 | 
					        height: 12px;
 | 
				
			||||||
 | 
					        margin-right: 6px;
 | 
				
			||||||
 | 
					        border-radius: 50%;
 | 
				
			||||||
 | 
					        background: var(--color-red-1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .title i:nth-child(2) {
 | 
				
			||||||
 | 
					        background: var(--color-orange-1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .title i:nth-child(3) {
 | 
				
			||||||
 | 
					        background: var(--color-green-1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    `,
 | 
				
			||||||
 | 
					    css`
 | 
				
			||||||
 | 
					      .code-block {
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					        overflow: hidden;
 | 
				
			||||||
 | 
					        overflow-y: auto;
 | 
				
			||||||
 | 
					        line-height: 20px;
 | 
				
			||||||
 | 
					        font-size: 14px;
 | 
				
			||||||
 | 
					        color: var(--color-dark-1);
 | 
				
			||||||
 | 
					        cursor: text;
 | 
				
			||||||
 | 
					        counter-reset: code;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .code-block code {
 | 
				
			||||||
 | 
					        display: block;
 | 
				
			||||||
 | 
					        position: relative;
 | 
				
			||||||
 | 
					        min-height: 20px;
 | 
				
			||||||
 | 
					        padding: 0 8px 0 45px;
 | 
				
			||||||
 | 
					        font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
 | 
				
			||||||
 | 
					        white-space: pre-wrap;
 | 
				
			||||||
 | 
					        word-break: break-word;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .code-block code::before {
 | 
				
			||||||
 | 
					        position: absolute;
 | 
				
			||||||
 | 
					        left: 0;
 | 
				
			||||||
 | 
					        width: 40px;
 | 
				
			||||||
 | 
					        height: 100%;
 | 
				
			||||||
 | 
					        padding-right: 5px;
 | 
				
			||||||
 | 
					        text-align: right;
 | 
				
			||||||
 | 
					        color: var(--color-grey-1);
 | 
				
			||||||
 | 
					        content: counter(code);
 | 
				
			||||||
 | 
					        counter-increment: code;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    `
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  mounted() {
 | 
				
			||||||
 | 
					    var txt = this.innerHTML || this.textContent
 | 
				
			||||||
 | 
					    txt = txt.trim().replace(/^[\r\n]|\s{2,}$/g, '')
 | 
				
			||||||
 | 
					    if (txt.startsWith('<xmp>') && txt.endsWith('</xmp>')) {
 | 
				
			||||||
 | 
					      txt = txt.slice(5, -6).trim()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (txt) {
 | 
				
			||||||
 | 
					      this.textContent = ''
 | 
				
			||||||
 | 
					      nextTick(_ => {
 | 
				
			||||||
 | 
					        this.code = txt.replace(/</g, '<').replace(/>/g, '>')
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  render() {
 | 
				
			||||||
 | 
					    return html`
 | 
				
			||||||
 | 
					      <div class="code-box">
 | 
				
			||||||
 | 
					        <header class="title">
 | 
				
			||||||
 | 
					          <section><i></i><i></i><i></i></section>
 | 
				
			||||||
 | 
					          <section>${this.lang}</section>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					        <div class="code-block">
 | 
				
			||||||
 | 
					          ${this.code.split('\n').map(s => html`<code>${s}</code>`)}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    `
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Code.reg('code')
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,8 @@
 | 
				
			||||||
 | 
					 | 
				
			||||||
import { createApp } from 'vue'
 | 
					import { createApp } from 'vue'
 | 
				
			||||||
import App from './app.vue'
 | 
					import App from './app.vue'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import router from './router'
 | 
					 | 
				
			||||||
import store from './store'
 | 
					import store from './store'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const app = createApp(App)
 | 
					const app = createApp(App)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.use(router).use(store).mount('.app')
 | 
					app.use(store).mount('.app')
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,23 +0,0 @@
 | 
				
			||||||
 | 
					 | 
				
			||||||
import { createRouter, createWebHistory } from 'vue-router'
 | 
					 | 
				
			||||||
import Home from './views/home.vue'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const router = createRouter({
 | 
					 | 
				
			||||||
  history: createWebHistory(),
 | 
					 | 
				
			||||||
  routes: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      path: '/',
 | 
					 | 
				
			||||||
      name: 'home',
 | 
					 | 
				
			||||||
      component: Home
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      path: '/about',
 | 
					 | 
				
			||||||
      name: 'about',
 | 
					 | 
				
			||||||
      component: () => import('./views/about.vue')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default router
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
							
								
								
									
										12
									
								
								src/store.js
								
								
								
								
							
							
						
						
									
										12
									
								
								src/store.js
								
								
								
								
							| 
						 | 
					@ -1,9 +1,15 @@
 | 
				
			||||||
 | 
					 | 
				
			||||||
import { reactive } from 'vue'
 | 
					import { reactive } from 'vue'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const store = reactive({
 | 
					const store = reactive({
 | 
				
			||||||
  foo: 'bar',
 | 
					  API_KEY: localStorage.getItem('API_KEY'),
 | 
				
			||||||
  version: '0.4.0'
 | 
					  BASE_URL: localStorage.getItem('BASE_URL'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  conversations: [],
 | 
				
			||||||
 | 
					  conversation: {
 | 
				
			||||||
 | 
					    id: '',
 | 
				
			||||||
 | 
					    lastMessageId: ''
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  records: []
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function (app) {
 | 
					export default function (app) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,18 +0,0 @@
 | 
				
			||||||
 | 
					 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
export default {
 | 
					 | 
				
			||||||
  data(){
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      content: '这是关于我们页面'
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
  <main>
 | 
					 | 
				
			||||||
    <h1>{{content}}</h1>
 | 
					 | 
				
			||||||
    <cite>当前vue-live版本: v{{$store.version}}</cite>
 | 
					 | 
				
			||||||
  </main>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,131 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <aside class="history noselect">
 | 
				
			||||||
 | 
					    <el-button type="warning" plain class="new" @click="createNewConversation"
 | 
				
			||||||
 | 
					      >+ 新建会话</el-button
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					    <ul class="list">
 | 
				
			||||||
 | 
					      <li
 | 
				
			||||||
 | 
					        class="item"
 | 
				
			||||||
 | 
					        v-for="(it, i) in $store.conversations"
 | 
				
			||||||
 | 
					        :key="it.id"
 | 
				
			||||||
 | 
					        @click="pickThisConversation(it)"
 | 
				
			||||||
 | 
					        :class="{ active: it.id === $store.conversation.id }"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <span class="text-ell">{{ it.name }}</span>
 | 
				
			||||||
 | 
					        <a class="close" @click.stop="removeConversation(it, i)"> ╳ </a>
 | 
				
			||||||
 | 
					      </li>
 | 
				
			||||||
 | 
					    </ul>
 | 
				
			||||||
 | 
					  </aside>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  data() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      conversations: []
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    createNewConversation() {
 | 
				
			||||||
 | 
					      this.conversation = {
 | 
				
			||||||
 | 
					        id: '',
 | 
				
			||||||
 | 
					        lastMessageId: ''
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.records = []
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    removeConversation(it, idx) {
 | 
				
			||||||
 | 
					      this.$confirm(`是否删除此会话【${it.name}】`)
 | 
				
			||||||
 | 
					        .then(_ => {
 | 
				
			||||||
 | 
					          removeConversation(it.id)
 | 
				
			||||||
 | 
					            .then(r => {
 | 
				
			||||||
 | 
					              this.$message.success('删除会话成功')
 | 
				
			||||||
 | 
					              this.conversations.splice(idx, 1)
 | 
				
			||||||
 | 
					              if (this.conversations.length) {
 | 
				
			||||||
 | 
					                this.pickThisConversation(this.conversations[0])
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                this.createNewConversation()
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(r => {
 | 
				
			||||||
 | 
					              this.$message.success('删除会话失败')
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch(function () {})
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pickThisConversation(it) {
 | 
				
			||||||
 | 
					      if (it.id === this.conversation.id) {
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.conversation = { id: it.id }
 | 
				
			||||||
 | 
					      this.records = []
 | 
				
			||||||
 | 
					      this.loading = true
 | 
				
			||||||
 | 
					      this.getRecords()
 | 
				
			||||||
 | 
					        .then(list => {
 | 
				
			||||||
 | 
					          this.conversation.lastMessageId = list.at(-1).id
 | 
				
			||||||
 | 
					          nextTick(_ => {
 | 
				
			||||||
 | 
					            this.$refs.records.scrollTop = Number.MAX_SAFE_INTEGER
 | 
				
			||||||
 | 
					            this.$refs.input.focus()
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .finally(_ => {
 | 
				
			||||||
 | 
					          this.loading = false
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.history {
 | 
				
			||||||
 | 
					  flex-shrink: 0;
 | 
				
			||||||
 | 
					  width: 220px;
 | 
				
			||||||
 | 
					  padding: 8px 16px;
 | 
				
			||||||
 | 
					  border-right: 1px solid var(--color-plain-2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .new {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .list {
 | 
				
			||||||
 | 
					    margin-top: 16px;
 | 
				
			||||||
 | 
					    font-size: 14px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .item {
 | 
				
			||||||
 | 
					      display: flex;
 | 
				
			||||||
 | 
					      align-items: center;
 | 
				
			||||||
 | 
					      width: 100%;
 | 
				
			||||||
 | 
					      height: 32px;
 | 
				
			||||||
 | 
					      margin-top: 8px;
 | 
				
			||||||
 | 
					      padding: 0 8px;
 | 
				
			||||||
 | 
					      border-radius: 4px;
 | 
				
			||||||
 | 
					      color: var(--color-dark-1);
 | 
				
			||||||
 | 
					      transition: color 0.2s ease-in, background 0.2s ease-in;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      span {
 | 
				
			||||||
 | 
					        flex: 1;
 | 
				
			||||||
 | 
					        padding-left: 6px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .close {
 | 
				
			||||||
 | 
					        opacity: 0.1;
 | 
				
			||||||
 | 
					        transition: opacity 0.2s ease-in;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      &.active,
 | 
				
			||||||
 | 
					      &:hover {
 | 
				
			||||||
 | 
					        color: var(--color-blue-1);
 | 
				
			||||||
 | 
					        background: #ecf5ff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .close {
 | 
				
			||||||
 | 
					          opacity: 1;
 | 
				
			||||||
 | 
					          cursor: pointer;
 | 
				
			||||||
 | 
					          transform: scale(1.2);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,280 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="current-session">
 | 
				
			||||||
 | 
					    <div class="records" ref="records">
 | 
				
			||||||
 | 
					      <section class="item" v-for="it in records" :key="it.id">
 | 
				
			||||||
 | 
					        <Avatar :name="it.role === 0 ? '我' : ''" />
 | 
				
			||||||
 | 
					        <div class="content" v-html="markd(it.content)"></div>
 | 
				
			||||||
 | 
					      </section>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <textarea
 | 
				
			||||||
 | 
					      ref="input"
 | 
				
			||||||
 | 
					      class="question"
 | 
				
			||||||
 | 
					      autofocus
 | 
				
			||||||
 | 
					      :disabled="loading"
 | 
				
			||||||
 | 
					      v-model="question"
 | 
				
			||||||
 | 
					      @keydown="ask"
 | 
				
			||||||
 | 
					      :placeholder="'Ctrl/Shift/Cmd + 回车换行 \n回车发送'"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					import Avatar from '@/components/avatar.vue'
 | 
				
			||||||
 | 
					import '@/lib/wc-code.js'
 | 
				
			||||||
 | 
					import { nextTick } from 'vue'
 | 
				
			||||||
 | 
					import markd from '@/lib/markd.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function ask() {
 | 
				
			||||||
 | 
					  //
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return Promise.resolve({
 | 
				
			||||||
 | 
					    data: {
 | 
				
			||||||
 | 
					      conversation: '',
 | 
				
			||||||
 | 
					      id: '',
 | 
				
			||||||
 | 
					      text: 'blabla...'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function get10Tokens(str = '') {
 | 
				
			||||||
 | 
					  return str.slice(0, 10)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  components: { Avatar },
 | 
				
			||||||
 | 
					  data() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      records: [],
 | 
				
			||||||
 | 
					      question: '',
 | 
				
			||||||
 | 
					      loading: false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  mounted() {
 | 
				
			||||||
 | 
					    // this.getConversations()
 | 
				
			||||||
 | 
					    // this.getRecords()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nextTick(_ => this.$refs.input.focus())
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    markd,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ask(ev) {
 | 
				
			||||||
 | 
					      let question = this.question.trim()
 | 
				
			||||||
 | 
					      let { id, lastMessageId } = this.$store.conversation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (ev.keyCode === 13) {
 | 
				
			||||||
 | 
					        if (ev.shiftKey) {
 | 
				
			||||||
 | 
					          return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (ev.ctrlKey || ev.metaKey) {
 | 
				
			||||||
 | 
					          this.question += '\n'
 | 
				
			||||||
 | 
					          return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ev.preventDefault()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (question === '') {
 | 
				
			||||||
 | 
					          return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.question = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.records.push({ id: Date.now(), role: 0, content: question })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        nextTick(_ => (this.$refs.records.scrollTop = Number.MAX_SAFE_INTEGER))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.loading = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.records.push({
 | 
				
			||||||
 | 
					          id: Date.now(),
 | 
				
			||||||
 | 
					          role: 1,
 | 
				
			||||||
 | 
					          content: '<div class="loading"><i></i><i></i><i></i></div>'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ask(question, lastMessageId, id)
 | 
				
			||||||
 | 
					          .then(r => {
 | 
				
			||||||
 | 
					            if (!id) {
 | 
				
			||||||
 | 
					              this.$store.conversations.unshift({
 | 
				
			||||||
 | 
					                id: r.data.conversation,
 | 
				
			||||||
 | 
					                name: get10Tokens(question)
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            this.$store.conversation.id = r.data.conversation
 | 
				
			||||||
 | 
					            this.$store.conversation.lastMessageId = r.data.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.records.at(-1).id = r.data.id
 | 
				
			||||||
 | 
					            this.records.at(-1).content = r.data.text
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					          .catch(r => {
 | 
				
			||||||
 | 
					            console.log(r)
 | 
				
			||||||
 | 
					            this.records.at(-1).content = r.msg || r.toString()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.$message.error(r.msg || r.toString())
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					          .finally(_ => {
 | 
				
			||||||
 | 
					            this.loading = false
 | 
				
			||||||
 | 
					            nextTick(_ => {
 | 
				
			||||||
 | 
					              this.$refs.records.scrollTop = Number.MAX_SAFE_INTEGER
 | 
				
			||||||
 | 
					              this.$refs.input.focus()
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.current-session {
 | 
				
			||||||
 | 
					  flex: 1;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					  max-height: 100%;
 | 
				
			||||||
 | 
					  background: var(--color-plain-1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .records {
 | 
				
			||||||
 | 
					    overflow: auto;
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					    padding: 16px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .item {
 | 
				
			||||||
 | 
					      display: flex;
 | 
				
			||||||
 | 
					      width: 100%;
 | 
				
			||||||
 | 
					      margin-top: 16px;
 | 
				
			||||||
 | 
					      font-size: 14px;
 | 
				
			||||||
 | 
					      color: var(--color-dark-1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .content {
 | 
				
			||||||
 | 
					        position: relative;
 | 
				
			||||||
 | 
					        margin-left: 16px;
 | 
				
			||||||
 | 
					        padding: 8px 16px;
 | 
				
			||||||
 | 
					        word-break: break-word;
 | 
				
			||||||
 | 
					        white-space: pre-wrap;
 | 
				
			||||||
 | 
					        border-radius: 6px;
 | 
				
			||||||
 | 
					        background: #fff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &::before {
 | 
				
			||||||
 | 
					          position: absolute;
 | 
				
			||||||
 | 
					          left: -6px;
 | 
				
			||||||
 | 
					          top: 10px;
 | 
				
			||||||
 | 
					          width: 12px;
 | 
				
			||||||
 | 
					          height: 12px;
 | 
				
			||||||
 | 
					          border-radius: 3px;
 | 
				
			||||||
 | 
					          background: #fff;
 | 
				
			||||||
 | 
					          transform: rotate(45deg);
 | 
				
			||||||
 | 
					          content: '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      &:nth-child(odd) {
 | 
				
			||||||
 | 
					        .content {
 | 
				
			||||||
 | 
					          background: #d9ecff;
 | 
				
			||||||
 | 
					          &::before {
 | 
				
			||||||
 | 
					            background: #d9ecff;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &::-webkit-scrollbar {
 | 
				
			||||||
 | 
					      width: 0;
 | 
				
			||||||
 | 
					      display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .question {
 | 
				
			||||||
 | 
					    flex-shrink: 0;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 128px;
 | 
				
			||||||
 | 
					    margin-top: 32px;
 | 
				
			||||||
 | 
					    padding: 8px;
 | 
				
			||||||
 | 
					    border: 0;
 | 
				
			||||||
 | 
					    border-top: 1px solid var(--color-plain-2);
 | 
				
			||||||
 | 
					    background: #fff;
 | 
				
			||||||
 | 
					    color: var(--color-dark-2);
 | 
				
			||||||
 | 
					    resize: none;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &:focus {
 | 
				
			||||||
 | 
					      box-shadow: 0 0 5px var(--color-blue-a);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &::placeholder {
 | 
				
			||||||
 | 
					      color: var(--color-grey-2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    &::-webkit-scrollbar {
 | 
				
			||||||
 | 
					      width: 0;
 | 
				
			||||||
 | 
					      display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
					<style lang="scss">
 | 
				
			||||||
 | 
					.current-session .content {
 | 
				
			||||||
 | 
					  line-height: 1.5;
 | 
				
			||||||
 | 
					  ol {
 | 
				
			||||||
 | 
					    margin-left: 1em;
 | 
				
			||||||
 | 
					    list-style: decimal outside none;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  ul {
 | 
				
			||||||
 | 
					    margin-left: 1em;
 | 
				
			||||||
 | 
					    list-style: disc outside none;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  li {
 | 
				
			||||||
 | 
					    margin: 0.5em 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  li ol {
 | 
				
			||||||
 | 
					    margin-left: 1em;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  li ul {
 | 
				
			||||||
 | 
					    margin-left: 1em;
 | 
				
			||||||
 | 
					    list-style-type: circle;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  li ol ul,
 | 
				
			||||||
 | 
					  li ul ul {
 | 
				
			||||||
 | 
					    list-style-type: square;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  code.inline {
 | 
				
			||||||
 | 
					    display: inline;
 | 
				
			||||||
 | 
					    margin: 0 2px;
 | 
				
			||||||
 | 
					    padding: 0 2px;
 | 
				
			||||||
 | 
					    color: var(--color-red-1);
 | 
				
			||||||
 | 
					    background: var(--color-plain-1);
 | 
				
			||||||
 | 
					    border-radius: 2px;
 | 
				
			||||||
 | 
					    font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.current-session .loading {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  height: 24px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  i {
 | 
				
			||||||
 | 
					    width: 12px;
 | 
				
			||||||
 | 
					    height: 12px;
 | 
				
			||||||
 | 
					    margin: 0 8px;
 | 
				
			||||||
 | 
					    border-radius: 50%;
 | 
				
			||||||
 | 
					    background: var(--color-blue-1);
 | 
				
			||||||
 | 
					    transform: scale(0.5);
 | 
				
			||||||
 | 
					    animation: loading 1s ease-in-out infinite;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &:nth-child(2) {
 | 
				
			||||||
 | 
					      animation-delay: 0.2s;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    &:nth-child(3) {
 | 
				
			||||||
 | 
					      animation-delay: 0.4s;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@keyframes loading {
 | 
				
			||||||
 | 
					  0%,
 | 
				
			||||||
 | 
					  100% {
 | 
				
			||||||
 | 
					    transform: scale(0.5);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  50% {
 | 
				
			||||||
 | 
					    transform: scale(1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -1,17 +0,0 @@
 | 
				
			||||||
 | 
					 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
export default {
 | 
					 | 
				
			||||||
  data(){
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      content: '欢迎访问~~ 这是首页'
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
  <main>
 | 
					 | 
				
			||||||
    <h1>{{content}}</h1>
 | 
					 | 
				
			||||||
  </main>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <main class="noselect topbar">
 | 
				
			||||||
 | 
					    <section class="btns"><i></i><i></i><i></i></section>
 | 
				
			||||||
 | 
					    <div class="option">•••</div>
 | 
				
			||||||
 | 
					  </main>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  data() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      content: '这是关于我们页面'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.topbar {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 48px;
 | 
				
			||||||
 | 
					  padding: 0 16px;
 | 
				
			||||||
 | 
					  border-bottom: 1px solid var(--color-plain-2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.btns {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  i {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    width: 12px;
 | 
				
			||||||
 | 
					    height: 12px;
 | 
				
			||||||
 | 
					    margin-right: 6px;
 | 
				
			||||||
 | 
					    border-radius: 50%;
 | 
				
			||||||
 | 
					    background: var(--color-red-1);
 | 
				
			||||||
 | 
					    &:nth-child(2) {
 | 
				
			||||||
 | 
					      background: var(--color-orange-1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    &:nth-child(3) {
 | 
				
			||||||
 | 
					      background: var(--color-green-1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.option {
 | 
				
			||||||
 | 
					  font-size: 22px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ export default {
 | 
				
			||||||
      // 这里的resolve可将相对路径转为绝对路径
 | 
					      // 这里的resolve可将相对路径转为绝对路径
 | 
				
			||||||
      // 如果传入的路径已经是绝对路径的, 可不需要resolve
 | 
					      // 如果传入的路径已经是绝对路径的, 可不需要resolve
 | 
				
			||||||
      entry: resolve('./src/main.js'),
 | 
					      entry: resolve('./src/main.js'),
 | 
				
			||||||
      title: 'vue-live 应用示例'
 | 
					      title: 'chatgpt网页客户端'
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  // 以下cdn地址, 可自行修改为适合的
 | 
					  // 以下cdn地址, 可自行修改为适合的
 | 
				
			||||||
| 
						 | 
					@ -26,11 +26,7 @@ export default {
 | 
				
			||||||
  // 也可以在页面中直接引入完整的路径, 而不必须在这里声明
 | 
					  // 也可以在页面中直接引入完整的路径, 而不必须在这里声明
 | 
				
			||||||
  imports: {
 | 
					  imports: {
 | 
				
			||||||
    vue: '//jscdn.ink/vue/3.2.47/vue.runtime.esm-browser.prod.js',
 | 
					    vue: '//jscdn.ink/vue/3.2.47/vue.runtime.esm-browser.prod.js',
 | 
				
			||||||
    // 这个vue-router库, 移除了 @vue/devtools-api 相关的代码。 以达到减少不必须的体积的效果
 | 
					    '@bd/core.js': '//jscdn.ink/@bd/core/1.6.0/index.js',
 | 
				
			||||||
    // 如需要支持devtools的, 请修改为原版vue-router地址即可。
 | 
					 | 
				
			||||||
    // 'vue-router': '//jscdn.ink/@bytedo/vue-router/4.1.6/vue-router.js',
 | 
					 | 
				
			||||||
    // 'vue-router': '//jscdn.ink/vue-router/4.1.6/vue-router.esm-browser.js',
 | 
					 | 
				
			||||||
    // '@vue/devtools-api': '//jscdn.ink/@vue/devtools-api/6.5.0/esm/index.js',
 | 
					 | 
				
			||||||
    fetch: '//jscdn.ink/@bytedo/fetch/2.1.5/next.js'
 | 
					    fetch: '//jscdn.ink/@bytedo/fetch/2.1.5/next.js'
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue