feat: add comment demo #238

pr675
Hufe921 3 years ago committed by Hufe
parent 3e183aefa5
commit 86cdcf3481

@ -313,6 +313,7 @@
<div class="catalog__main"></div>
</div>
<div class="editor"></div>
<div class="comment" editor-component="comment"></div>
<div class="footer" editor-component="footer">
<div>
<div class="catalog-mode" title="目录">

@ -1,4 +1,4 @@
import { data, options } from './mock'
import { commentList, data, options } from './mock'
import './style.css'
import prism from 'prismjs'
import Editor, {
@ -6,6 +6,7 @@ import Editor, {
Command,
ControlType,
EditorMode,
EditorZone,
ElementType,
IBlock,
ICatalogItem,
@ -21,7 +22,7 @@ import Editor, {
import { Dialog } from './components/dialog/Dialog'
import { formatPrismToken } from './utils/prism'
import { Signature } from './components/signature/Signature'
import { debounce } from './utils'
import { debounce, nextTick, scrollIntoView } from './utils'
window.onload = function () {
const isApple =
@ -1233,6 +1234,61 @@ window.onload = function () {
})
}
// 模拟批注
const commentDom = document.querySelector<HTMLDivElement>('.comment')!
async function updateComment() {
const groupIds = await instance.command.getGroupIds()
for (const comment of commentList) {
const activeCommentDom = commentDom.querySelector<HTMLDivElement>(
`.comment-item[data-id='${comment.id}']`
)
// 编辑器是否存在对应成组id
if (groupIds.includes(comment.id)) {
// 当前dom是否存在-不存在则追加
if (!activeCommentDom) {
const commentItem = document.createElement('div')
commentItem.classList.add('comment-item')
commentItem.setAttribute('data-id', comment.id)
commentItem.onclick = () => {
instance.command.executeLocationGroup(comment.id)
}
commentDom.append(commentItem)
// 选区信息
const commentItemTitle = document.createElement('div')
commentItemTitle.classList.add('comment-item__title')
commentItemTitle.append(document.createElement('span'))
const commentItemTitleContent = document.createElement('span')
commentItemTitleContent.innerText = comment.rangeText
commentItemTitle.append(commentItemTitleContent)
const closeDom = document.createElement('i')
closeDom.onclick = () => {
instance.command.executeDeleteGroup(comment.id)
}
commentItemTitle.append(closeDom)
commentItem.append(commentItemTitle)
// 基础信息
const commentItemInfo = document.createElement('div')
commentItemInfo.classList.add('comment-item__info')
const commentItemInfoName = document.createElement('span')
commentItemInfoName.innerText = comment.userName
const commentItemInfoDate = document.createElement('span')
commentItemInfoDate.innerText = comment.createdDate
commentItemInfo.append(commentItemInfoName)
commentItemInfo.append(commentItemInfoDate)
commentItem.append(commentItemInfo)
// 详细评论
const commentItemContent = document.createElement('div')
commentItemContent.classList.add('comment-item__content')
commentItemContent.innerText = comment.content
commentItem.append(commentItemContent)
commentDom.append(commentItem)
}
} else {
// 编辑器内不存在对应成组id则dom则移除
activeCommentDom?.remove()
}
}
}
// 8. 内部事件监听
instance.listener.rangeStyleChange = function (payload) {
// 控件类型
@ -1381,6 +1437,23 @@ window.onload = function () {
} else {
listDom.classList.remove('active')
}
// 批注
commentDom
.querySelectorAll<HTMLDivElement>('.comment-item')
.forEach(commentItemDom => {
commentItemDom.classList.remove('active')
})
if (payload.groupIds) {
const [id] = payload.groupIds
const activeCommentDom = commentDom.querySelector<HTMLDivElement>(
`.comment-item[data-id='${id}']`
)
if (activeCommentDom) {
activeCommentDom.classList.add('active')
scrollIntoView(commentDom, activeCommentDom)
}
}
}
instance.listener.visiblePageNoListChange = function (payload) {
@ -1448,8 +1521,14 @@ window.onload = function () {
}`
// 目录
if (isCatalogShow) {
updateCatalog()
nextTick(() => {
updateCatalog()
})
}
// 批注
nextTick(() => {
updateComment()
})
}
instance.listener.contentChange = debounce(handleContentChange, 200)
handleContentChange()
@ -1460,6 +1539,44 @@ window.onload = function () {
// 9. 右键菜单注册
instance.register.contextMenuList([
{
name: '批注',
when: payload => {
return (
!payload.isReadonly &&
payload.editorHasSelection &&
payload.zone === EditorZone.MAIN
)
},
callback: (command: Command) => {
new Dialog({
title: '批注',
data: [
{
type: 'textarea',
label: '批注',
height: 100,
name: 'value',
required: true,
placeholder: '请输入批注'
}
],
onConfirm: payload => {
const value = payload.find(p => p.name === 'value')?.value
if (!value) return
const groupId = command.executeSetGroup()
if (!groupId) return
commentList.push({
id: groupId,
content: value,
userName: 'Hufe',
rangeText: command.getRangeText(),
createdDate: new Date().toLocaleString()
})
}
})
}
},
{
name: '签名',
icon: 'signature',

@ -86,7 +86,8 @@ while (index < textList.length) {
} else if (highlightIndex.includes(index)) {
elementList.push({
value,
highlight: '#F2F27F'
highlight: '#F2F27F',
groupIds: ['1'] // 模拟批注
})
} else {
elementList.push({
@ -419,6 +420,24 @@ elementList.push(
export const data: IElement[] = elementList
interface IComment {
id: string
content: string
userName: string
rangeText: string
createdDate: string
}
export const commentList: IComment[] = [
{
id: '1',
content:
'红细胞比容HCT是指每单位容积中红细胞所占全血容积的比值用于反映红细胞和血浆的比例。',
userName: 'Hufe',
rangeText: '血细胞比容',
createdDate: '2023-08-20 23:10:55'
}
]
export const options: IEditorOption = {
margins: [100, 120, 100, 120],
watermark: {

@ -732,6 +732,95 @@ ul {
box-shadow: rgb(158 161 165 / 40%) 0px 2px 12px 0px;
}
.comment {
width: 250px;
height: 650px;
position: fixed;
transform: translateX(420px);
top: 200px;
left: 50%;
overflow-y: auto;
}
.comment-item {
background: #ffffff;
border: 1px solid #e2e6ed;
position: relative;
border-radius: 8px;
padding: 15px;
font-size: 14px;
margin-bottom: 20px;
cursor: pointer;
transition: all .5s;
}
.comment-item:hover {
border-color: #c0c6cf;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
}
.comment-item.active {
border-color: #E99D00;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
}
.comment-item__title {
height: 22px;
position: relative;
display: flex;
align-items: center;
color: #c1c6ce;
}
.comment-item__title span:first-child {
background-color: #dbdbdb;
width: 4px;
height: 16px;
margin-right: 5px;
display: inline-block;
border-radius: 999px;
}
.comment-item__title span:nth-child(2) {
width: 200px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.comment-item__title i {
width: 16px;
height: 16px;
cursor: pointer;
position: absolute;
right: -8px;
top: -8px;
background: url(./assets/images/close.svg) no-repeat;
}
.comment-item__title i:hover {
opacity: 0.6;
}
.comment-item__info {
height: 28px;
display: flex;
align-items: center;
justify-content: space-between;
}
.comment-item__info>span:first-child {
font-weight: 600;
}
.comment-item__info>span:last-child {
color: #c1c6ce;
}
.comment-item__content {
line-height: 22px;
}
.footer {
width: 100%;
height: 30px;

@ -9,3 +9,34 @@ export function debounce(func: Function, delay: number) {
}, delay)
}
}
export function scrollIntoView(container: HTMLElement, selected: HTMLElement) {
if (!selected) {
container.scrollTop = 0
return
}
const offsetParents: HTMLElement[] = []
let pointer = <HTMLElement>selected.offsetParent
while (pointer && container !== pointer && container.contains(pointer)) {
offsetParents.push(pointer)
pointer = <HTMLElement>pointer.offsetParent
}
const top =
selected.offsetTop +
offsetParents.reduce((prev, curr) => prev + curr.offsetTop, 0)
const bottom = top + selected.offsetHeight
const viewRectTop = container.scrollTop
const viewRectBottom = viewRectTop + container.clientHeight
if (top < viewRectTop) {
container.scrollTop = top
} else if (bottom > viewRectBottom) {
container.scrollTop = bottom - container.clientHeight
}
}
export function nextTick(fn: Function) {
const callback = window.requestIdleCallback || window.setTimeout
callback(() => {
fn()
})
}

Loading…
Cancel
Save