feat: add comment demo #238
This commit is contained in:
@@ -313,6 +313,7 @@
|
|||||||
<div class="catalog__main"></div>
|
<div class="catalog__main"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor"></div>
|
<div class="editor"></div>
|
||||||
|
<div class="comment" editor-component="comment"></div>
|
||||||
<div class="footer" editor-component="footer">
|
<div class="footer" editor-component="footer">
|
||||||
<div>
|
<div>
|
||||||
<div class="catalog-mode" title="目录">
|
<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 './style.css'
|
||||||
import prism from 'prismjs'
|
import prism from 'prismjs'
|
||||||
import Editor, {
|
import Editor, {
|
||||||
@@ -6,6 +6,7 @@ import Editor, {
|
|||||||
Command,
|
Command,
|
||||||
ControlType,
|
ControlType,
|
||||||
EditorMode,
|
EditorMode,
|
||||||
|
EditorZone,
|
||||||
ElementType,
|
ElementType,
|
||||||
IBlock,
|
IBlock,
|
||||||
ICatalogItem,
|
ICatalogItem,
|
||||||
@@ -21,7 +22,7 @@ import Editor, {
|
|||||||
import { Dialog } from './components/dialog/Dialog'
|
import { Dialog } from './components/dialog/Dialog'
|
||||||
import { formatPrismToken } from './utils/prism'
|
import { formatPrismToken } from './utils/prism'
|
||||||
import { Signature } from './components/signature/Signature'
|
import { Signature } from './components/signature/Signature'
|
||||||
import { debounce } from './utils'
|
import { debounce, nextTick, scrollIntoView } from './utils'
|
||||||
|
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
const isApple =
|
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. 内部事件监听
|
// 8. 内部事件监听
|
||||||
instance.listener.rangeStyleChange = function (payload) {
|
instance.listener.rangeStyleChange = function (payload) {
|
||||||
// 控件类型
|
// 控件类型
|
||||||
@@ -1381,6 +1437,23 @@ window.onload = function () {
|
|||||||
} else {
|
} else {
|
||||||
listDom.classList.remove('active')
|
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) {
|
instance.listener.visiblePageNoListChange = function (payload) {
|
||||||
@@ -1448,8 +1521,14 @@ window.onload = function () {
|
|||||||
}`
|
}`
|
||||||
// 目录
|
// 目录
|
||||||
if (isCatalogShow) {
|
if (isCatalogShow) {
|
||||||
|
nextTick(() => {
|
||||||
updateCatalog()
|
updateCatalog()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
// 批注
|
||||||
|
nextTick(() => {
|
||||||
|
updateComment()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
instance.listener.contentChange = debounce(handleContentChange, 200)
|
instance.listener.contentChange = debounce(handleContentChange, 200)
|
||||||
handleContentChange()
|
handleContentChange()
|
||||||
@@ -1460,6 +1539,44 @@ window.onload = function () {
|
|||||||
|
|
||||||
// 9. 右键菜单注册
|
// 9. 右键菜单注册
|
||||||
instance.register.contextMenuList([
|
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: '签名',
|
name: '签名',
|
||||||
icon: 'signature',
|
icon: 'signature',
|
||||||
|
|||||||
+20
-1
@@ -86,7 +86,8 @@ while (index < textList.length) {
|
|||||||
} else if (highlightIndex.includes(index)) {
|
} else if (highlightIndex.includes(index)) {
|
||||||
elementList.push({
|
elementList.push({
|
||||||
value,
|
value,
|
||||||
highlight: '#F2F27F'
|
highlight: '#F2F27F',
|
||||||
|
groupIds: ['1'] // 模拟批注
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
elementList.push({
|
elementList.push({
|
||||||
@@ -419,6 +420,24 @@ elementList.push(
|
|||||||
|
|
||||||
export const data: IElement[] = elementList
|
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 = {
|
export const options: IEditorOption = {
|
||||||
margins: [100, 120, 100, 120],
|
margins: [100, 120, 100, 120],
|
||||||
watermark: {
|
watermark: {
|
||||||
|
|||||||
@@ -732,6 +732,95 @@ ul {
|
|||||||
box-shadow: rgb(158 161 165 / 40%) 0px 2px 12px 0px;
|
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 {
|
.footer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
|||||||
@@ -9,3 +9,34 @@ export function debounce(func: Function, delay: number) {
|
|||||||
}, delay)
|
}, 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