feat: add comment demo #238
This commit is contained in:
@@ -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="目录">
|
||||
|
||||
+119
-2
@@ -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) {
|
||||
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',
|
||||
|
||||
+20
-1
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user