文章目录实现页面滚动时对应目录项高亮
This commit is contained in:
parent
fae9655cad
commit
32556a4c4a
|
|
@ -9,5 +9,6 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
tocWidth: number | "auto" | undefined;
|
||||
tocHeight: number | undefined;
|
||||
showtitle: boolean;
|
||||
activeColor: string | undefined;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export default OakComponent({
|
|||
tocWidth: undefined,
|
||||
tocHeight: undefined,
|
||||
showtitle: false, //大纲顶层显示文章名称
|
||||
activeColor: undefined,
|
||||
},
|
||||
data: {
|
||||
unsub: undefined,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
|
|||
name?: string;
|
||||
content?: string;
|
||||
tocPosition: 'none' | 'left' | 'right';
|
||||
highlightBgColor: string;
|
||||
highlightBgColor?: string;
|
||||
headerTop: number;
|
||||
className?: string;
|
||||
tocFixed: boolean;
|
||||
|
|
@ -14,4 +14,5 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
|
|||
tocWidth?: number;
|
||||
tocHeight?: number | string;
|
||||
showtitle: boolean;
|
||||
activeColor?: string;
|
||||
}, {}>): React.JSX.Element;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import classNames from 'classnames';
|
|||
import { TocView } from '../toc/tocView';
|
||||
import Styles from './web.module.less';
|
||||
export default function Render(props) {
|
||||
const { className, name, content, tocPosition = 'none', highlightBgColor = 'none', headerTop = 0, tocClosed = false, scrollId, tocWidth, tocHeight, showtitle } = props.data;
|
||||
const { className, name, content, tocPosition = 'none', highlightBgColor = 'none', activeColor, headerTop = 0, tocClosed = false, scrollId, tocWidth, tocHeight, showtitle } = props.data;
|
||||
const editorConfig = {
|
||||
readOnly: true,
|
||||
autoFocus: true,
|
||||
|
|
@ -40,7 +40,9 @@ export default function Render(props) {
|
|||
}, [name]);
|
||||
return (<div className={classNames(Styles.container, className)}>
|
||||
<div className={Styles.contentContainer}>
|
||||
{tocPosition === "left" ? (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc} highlightBgColor={highlightBgColor} headerTop={headerTop} closed={tocClosed} scrollId={scrollId} tocWidth={tocWidth} tocHeight={tocHeight ? `calc(${tocHeight} - 32px)` : 'calc(100vh - 32px)'} title={showtitle ? name : undefined}/>) : null}
|
||||
{tocPosition === "left" ? (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor} headerTop={headerTop} closed={tocClosed} scrollId={scrollId} tocWidth={tocWidth} tocHeight={tocHeight ? `calc(${tocHeight} - 32px)` : 'calc(100vh - 32px)'} title={showtitle ? name : undefined}/>) : null}
|
||||
|
||||
<div className={Styles.content}>
|
||||
<div className={Styles.editorContainer}>
|
||||
|
|
@ -64,7 +66,9 @@ export default function Render(props) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc} highlightBgColor={highlightBgColor} headerTop={headerTop} closed={tocClosed} scrollId={scrollId} tocWidth={tocWidth} tocHeight={tocHeight ? `calc(${tocHeight} - 32px)` : 'calc(100vh - 32px)'} title={showtitle ? name : undefined}/>) : null}
|
||||
{tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor} headerTop={headerTop} closed={tocClosed} scrollId={scrollId} tocWidth={tocWidth} tocHeight={tocHeight ? `calc(${tocHeight} - 32px)` : 'calc(100vh - 32px)'} title={showtitle ? name : undefined}/>) : null}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
origin: string;
|
||||
scrollId: string;
|
||||
height: number | "auto";
|
||||
activeColor: string | undefined;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export default OakComponent({
|
|||
origin: 'qiniu', // 默认为七牛云
|
||||
scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
|
||||
height: 600,
|
||||
activeColor: undefined,
|
||||
},
|
||||
listeners: {
|
||||
'editor,content'(prev, next) {
|
||||
|
|
|
|||
|
|
@ -12,13 +12,14 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
|
|||
articleMenuId: string;
|
||||
oakId: string;
|
||||
tocPosition: 'none' | 'left' | 'right';
|
||||
highlightBgColor: string;
|
||||
highlightBgColor?: string;
|
||||
scrollId?: string;
|
||||
tocWidth?: number;
|
||||
tocHeight?: number | string;
|
||||
height?: number | string;
|
||||
tocClosed: boolean;
|
||||
execuable: boolean;
|
||||
activeColor?: string;
|
||||
}, {
|
||||
setHtml: (content: string) => void;
|
||||
setEditor: (editor: any) => void;
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ function customCheckImageFn(src, alt, url) {
|
|||
export default function Render(props) {
|
||||
const { methods, data } = props;
|
||||
const { t, setEditor, check, uploadFile, update, setHtml, gotoPreview, clearContentTip, } = methods;
|
||||
const { oakId, oakFullpath, id, name, content, editor, origin = 'qiniu', tocPosition = 'none', highlightBgColor = 'none', scrollId, tocWidth, tocHeight, height = 600, tocClosed = false, execuable, } = data;
|
||||
const { oakId, oakFullpath, id, name, content, editor, origin = 'qiniu', tocPosition = 'none', highlightBgColor, scrollId, tocWidth, tocHeight, height = 600, tocClosed = false, execuable, activeColor, } = data;
|
||||
const [articleId, setArticleId] = useState('');
|
||||
const [toc, setToc] = useState([]);
|
||||
const [showToc, setShowToc] = useState(false);
|
||||
|
|
@ -70,7 +70,9 @@ export default function Render(props) {
|
|||
</div>
|
||||
</div>
|
||||
<div className={Style.contentContainer} id='article-upsert-editorContainer'>
|
||||
{tocPosition === "left" ? (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc} highlightBgColor={highlightBgColor} closed={tocClosed} scrollId={containerId} tocWidth={tocWidth} tocHeight={'calc(100% - 16px)'}/>) : null}
|
||||
{tocPosition === "left" ? (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor} closed={tocClosed} scrollId={containerId} tocWidth={tocWidth} tocHeight={'calc(100% - 16px)'}/>) : null}
|
||||
|
||||
<div className={Style.content} style={{ maxWidth: `calc(100% - ${tocWidth || 228}px)` }}>
|
||||
<div className={classNames(Style.editorContainer, {
|
||||
|
|
@ -178,7 +180,9 @@ export default function Render(props) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc} highlightBgColor={highlightBgColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
|
||||
{tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,5 +9,6 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
tocWidth: number | "auto" | undefined;
|
||||
tocHeight: number | undefined;
|
||||
showtitle: boolean;
|
||||
activeColor: string | undefined;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export default OakComponent({
|
|||
tocWidth: undefined,
|
||||
tocHeight: undefined,
|
||||
showtitle: false, //大纲顶层显示文章名称
|
||||
activeColor: undefined,
|
||||
},
|
||||
methods: {},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
|
|||
title: string;
|
||||
content?: string;
|
||||
tocPosition: 'none' | 'left' | 'right';
|
||||
highlightBgColor: string;
|
||||
highlightBgColor?: string;
|
||||
headerTop: number;
|
||||
className?: string;
|
||||
tocFixed: boolean;
|
||||
|
|
@ -14,4 +14,5 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
|
|||
tocWidth?: number;
|
||||
tocHeight?: number | string;
|
||||
showtitle: boolean;
|
||||
activeColor?: string;
|
||||
}, {}>): React.JSX.Element;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import classNames from 'classnames';
|
|||
import { TocView } from '../toc/tocView';
|
||||
import Styles from './web.module.less';
|
||||
export default function Render(props) {
|
||||
const { className, title, content, tocPosition = 'none', tocFixed, highlightBgColor = 'none', headerTop = 0, tocClosed = false, scrollId, tocWidth, tocHeight, showtitle } = props.data;
|
||||
const { className, title, content, tocPosition = 'none', tocFixed, highlightBgColor, activeColor, headerTop = 0, tocClosed = false, scrollId, tocWidth, tocHeight, showtitle } = props.data;
|
||||
const editorConfig = {
|
||||
readOnly: true,
|
||||
autoFocus: true,
|
||||
|
|
@ -35,7 +35,9 @@ export default function Render(props) {
|
|||
}, [tocPosition]);
|
||||
return (<div className={classNames(Styles.container, className)}>
|
||||
<div className={Styles.contentContainer}>
|
||||
{tocPosition === "left" && (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc} highlightBgColor={highlightBgColor} headerTop={headerTop} closed={tocClosed} scrollId={scrollId} tocWidth={tocWidth} tocHeight={tocHeight ? `calc(${tocHeight} - 32px)` : 'calc(100vh - 32px)'} title={showtitle ? title : undefined}/>)}
|
||||
{tocPosition === "left" && (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor} headerTop={headerTop} closed={tocClosed} scrollId={scrollId} tocWidth={tocWidth} tocHeight={tocHeight ? `calc(${tocHeight} - 32px)` : 'calc(100vh - 32px)'} title={showtitle ? title : undefined}/>)}
|
||||
|
||||
<div className={Styles.content}>
|
||||
<div className={Styles.editorContainer}>
|
||||
|
|
@ -59,7 +61,9 @@ export default function Render(props) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{tocPosition === "right" && (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc} highlightBgColor={highlightBgColor} headerTop={headerTop} closed={tocClosed} scrollId={scrollId} tocWidth={tocWidth} tocHeight={tocHeight} title={showtitle ? title : undefined}/>)}
|
||||
{tocPosition === "right" && (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor} headerTop={headerTop} closed={tocClosed} scrollId={scrollId} tocWidth={tocWidth} tocHeight={tocHeight} title={showtitle ? title : undefined}/>)}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ export declare function TocView(props: {
|
|||
showToc: boolean;
|
||||
tocPosition: 'left' | 'right';
|
||||
setShowToc: (showToc: boolean) => void;
|
||||
highlightBgColor: string;
|
||||
highlightBgColor?: string;
|
||||
activeColor?: string;
|
||||
headerTop?: number;
|
||||
scrollId?: string;
|
||||
closed?: boolean;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { Button, Tooltip } from "antd";
|
||||
import { CaretDownOutlined, CloseOutlined, MenuOutlined } from "@ant-design/icons";
|
||||
import classNames from "classnames";
|
||||
import Style from './tocView.module.less';
|
||||
export function TocView(props) {
|
||||
const { toc, showToc, tocPosition, setShowToc, highlightBgColor, headerTop = 0, scrollId, closed = false, tocWidth, tocHeight = '100vh', title } = props;
|
||||
const { toc, showToc, tocPosition, setShowToc, highlightBgColor, activeColor = 'var(--oak-color-primary)', headerTop = 0, scrollId, closed = false, tocWidth, tocHeight = '100vh', title } = props;
|
||||
// useEffect(() => {
|
||||
// document.documentElement.style.setProperty('--highlight-bg-color', highlightBgColor);
|
||||
// }, [highlightBgColor]);
|
||||
useEffect(() => {
|
||||
document.documentElement.style.setProperty('--highlight-bg-color', highlightBgColor);
|
||||
}, [highlightBgColor]);
|
||||
const [selectedId, setSelectedId] = useState('');
|
||||
useEffect(() => {
|
||||
if (selectedId && selectedId !== '') {
|
||||
const e = document.getElementById(`li-${selectedId}`);
|
||||
}
|
||||
}, [selectedId]);
|
||||
document.documentElement.style.setProperty('--active-color', activeColor);
|
||||
}, [activeColor]);
|
||||
const generateTocList = (items, currentLevel = 1, parentId = null) => {
|
||||
//递归生成嵌套列表
|
||||
const result = [];
|
||||
|
|
@ -51,35 +48,7 @@ export function TocView(props) {
|
|||
ulElem?.classList.add(Style.fold);
|
||||
}
|
||||
}}/>
|
||||
<div style={{ fontSize: '1em', fontWeight: item.level === 1 ? 'bold' : 'normal' }} className={Style.tocItem} onClick={(event) => {
|
||||
setSelectedId(item.id);
|
||||
//页面滚动到对应元素
|
||||
const elem = document.getElementById(item.id);
|
||||
const elemTop = elem?.getBoundingClientRect().top;
|
||||
if (scrollId) {
|
||||
const scrollContainer = document.getElementById(scrollId);
|
||||
const containerTop = scrollContainer?.getBoundingClientRect().top;
|
||||
scrollContainer?.scrollBy({
|
||||
top: elemTop - containerTop,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
else {
|
||||
// const containerTop = document.body.getBoundingClientRect().top;
|
||||
window.scrollBy({
|
||||
top: elemTop - headerTop,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
//添加背景色
|
||||
elem?.classList.add(Style.highlight);
|
||||
//移除背景色类名
|
||||
setTimeout(function () {
|
||||
elem?.classList.remove(Style.highlight);
|
||||
}, 1000);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}}>
|
||||
<div style={{ fontSize: '1em', fontWeight: item.level === 1 ? 'bold' : 'normal' }} className={Style.tocItem}>
|
||||
{item.text}
|
||||
</div>
|
||||
</li>);
|
||||
|
|
@ -89,6 +58,129 @@ export function TocView(props) {
|
|||
}
|
||||
return result;
|
||||
};
|
||||
useEffect(() => {
|
||||
const tocContainer = document.getElementById('tocContainer');
|
||||
const tocItems = tocContainer?.querySelectorAll('li');
|
||||
let sections = [];
|
||||
for (const t of toc) {
|
||||
const item = document.getElementById(t.id);
|
||||
sections.push(item);
|
||||
}
|
||||
let currentHighlighted = '';
|
||||
let isClick = false;
|
||||
let clickTimeout = undefined;
|
||||
// 滚动目录以确保指定目录项可见
|
||||
function scrollToTocItem(item) {
|
||||
if (tocContainer) {
|
||||
const tocRect = tocContainer.getBoundingClientRect();
|
||||
const itemRect = item.getBoundingClientRect();
|
||||
// 检查目录项是否在可视区域内
|
||||
if (itemRect.top < tocRect.top || itemRect.bottom > tocRect.bottom) {
|
||||
const itemOffsetTop = item.offsetTop;
|
||||
const containerHeight = tocContainer.clientHeight;
|
||||
const itemHeight = item.offsetHeight;
|
||||
let targetScrollTop;
|
||||
if (itemRect.top < tocRect.top) {
|
||||
// 目录项在可视区域上方
|
||||
targetScrollTop = itemOffsetTop - 80;
|
||||
}
|
||||
else {
|
||||
// 目录项在可视区域下方
|
||||
targetScrollTop = itemOffsetTop - containerHeight + itemHeight + 10;
|
||||
}
|
||||
tocContainer.scrollTo({
|
||||
top: targetScrollTop,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// 高亮指定的目录项
|
||||
function highlightTocItem(targetId, source) {
|
||||
// 移除所有目录项的active样式
|
||||
tocItems?.forEach(item => {
|
||||
item.classList.remove(Style.active);
|
||||
});
|
||||
// 找到对应的目录项并添加active样式
|
||||
const correspondingTocItem = document.querySelector(`#${targetId}`);
|
||||
if (correspondingTocItem) {
|
||||
correspondingTocItem.classList.add(Style.active);
|
||||
// 滚动目录
|
||||
scrollToTocItem(correspondingTocItem);
|
||||
// 如果是点击触发的,设置超时
|
||||
if (source === 'click') {
|
||||
isClick = true;
|
||||
// 设置超时,1秒后恢复滚动的高亮功能
|
||||
clearTimeout(clickTimeout);
|
||||
clickTimeout = setTimeout(() => {
|
||||
isClick = false;
|
||||
}, 1000);
|
||||
}
|
||||
currentHighlighted = targetId;
|
||||
}
|
||||
}
|
||||
// 点击目录项平滑滚动到对应区块并高亮
|
||||
const handleClick = function (event) {
|
||||
const targetSection = event.target.closest('li');
|
||||
const targetId = targetSection.getAttribute('id');
|
||||
if (targetSection) {
|
||||
// 高亮点击的目录项
|
||||
highlightTocItem(targetId, 'click');
|
||||
// 平滑滚动到目标位置
|
||||
const itemId = targetId.substring(3);
|
||||
const elem = document.getElementById(itemId);
|
||||
const elemTop = elem?.getBoundingClientRect().top;
|
||||
if (scrollId) {
|
||||
const scrollContainer = document.getElementById(scrollId);
|
||||
const containerTop = scrollContainer?.getBoundingClientRect().top;
|
||||
scrollContainer?.scrollBy({
|
||||
top: elemTop - containerTop,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
else {
|
||||
window.scrollBy({
|
||||
top: elemTop - headerTop,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
tocItems?.forEach(item => {
|
||||
item.addEventListener('click', handleClick);
|
||||
});
|
||||
// 创建Intersection Observer实例
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
// 如果处于点击状态,跳过滚动检测
|
||||
if (isClick)
|
||||
return;
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
// 获取当前可见区块的ID
|
||||
const id = `li-${entry.target.getAttribute('id')}`;
|
||||
if (id && id !== currentHighlighted) {
|
||||
highlightTocItem(id, 'scroll');
|
||||
}
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.5,
|
||||
rootMargin: '0px 0px -75% 0px',
|
||||
});
|
||||
// 开始观察所有内容区块
|
||||
sections.forEach(section => {
|
||||
observer.observe(section);
|
||||
});
|
||||
return () => {
|
||||
if (tocContainer) {
|
||||
tocContainer.removeEventListener('click', handleClick);
|
||||
observer.disconnect();
|
||||
if (clickTimeout) {
|
||||
clearTimeout(clickTimeout);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [toc, showToc]);
|
||||
return (<div className={Style.tocContainer} style={Object.assign({}, tocWidth ? { width: tocWidth } : {}, tocHeight ? { height: tocHeight } : {})}>
|
||||
{showToc ? (<>
|
||||
<div className={Style.catalogTitle}>
|
||||
|
|
@ -96,7 +188,7 @@ export function TocView(props) {
|
|||
{closed ? (<CloseOutlined style={{ color: '#A5A5A5' }} onClick={() => setShowToc(false)}/>) : null}
|
||||
</div>
|
||||
|
||||
{(toc && toc.length > 0) ? (<ul style={{ listStyleType: 'none', paddingInlineStart: '0px', overflowX: 'hidden', overflowY: 'auto', height: '100%', borderLeft: tocPosition === 'right' ? '1px solid var(--oak-border-color)' : '' }}>{generateTocList([...toc])}</ul>) : (<div style={{ display: 'flex', alignItems: 'center', color: '#B1B1B1', height: '200px' }}>
|
||||
{(toc && toc.length > 0) ? (<ul id="tocContainer" style={{ listStyleType: 'none', paddingInlineStart: '0px', overflowX: 'hidden', overflowY: 'auto', height: '100%', borderLeft: tocPosition === 'right' ? '1px solid var(--oak-border-color)' : '' }}>{generateTocList([...toc])}</ul>) : (<div style={{ display: 'flex', alignItems: 'center', color: '#B1B1B1', height: '200px' }}>
|
||||
<div>
|
||||
<div>
|
||||
对文档内容应用“标题”样式
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@
|
|||
transition: all 1s ease;
|
||||
}
|
||||
|
||||
.highlightBorder {
|
||||
border-left: 1px solid var(--oak-color-primary);
|
||||
.active {
|
||||
color: var(--active-color);
|
||||
}
|
||||
|
||||
.catalogTitle {
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
origin: string;
|
||||
scrollId: string;
|
||||
height: number | "auto";
|
||||
activeColor: string | undefined;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export default OakComponent({
|
|||
origin: 'qiniu', // 默认为七牛云
|
||||
scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
|
||||
height: 600,
|
||||
activeColor: undefined,
|
||||
},
|
||||
listeners: {
|
||||
'editor,content'(prev, next) {
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
|
|||
articleMenuId: string;
|
||||
oakId: string;
|
||||
tocPosition: 'none' | 'left' | 'right';
|
||||
highlightBgColor: string;
|
||||
highlightBgColor?: string;
|
||||
scrollId?: string;
|
||||
tocWidth?: number;
|
||||
tocHeight?: number | string;
|
||||
height?: number | string;
|
||||
activeColor?: string;
|
||||
}, {
|
||||
setHtml: (content: string) => void;
|
||||
setEditor: (editor: any) => void;
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ function customCheckImageFn(src, alt, url) {
|
|||
export default function Render(props) {
|
||||
const { methods, data } = props;
|
||||
const { t, setEditor, check, uploadFile, update, setHtml, gotoPreview, clearContentTip, } = methods;
|
||||
const { oakId, oakFullpath, id, content, editor, origin = 'qiniu', tocPosition = 'none', highlightBgColor = 'none', scrollId, tocWidth, tocHeight, height = 600 } = data;
|
||||
const { oakId, oakFullpath, id, content, editor, origin = 'qiniu', tocPosition = 'none', highlightBgColor, activeColor, scrollId, tocWidth, tocHeight, height = 600 } = data;
|
||||
const [articleId, setArticleId] = useState('');
|
||||
const [toc, setToc] = useState([]);
|
||||
const [showToc, setShowToc] = useState(false);
|
||||
|
|
@ -52,7 +52,9 @@ export default function Render(props) {
|
|||
<Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default"/>
|
||||
</div>
|
||||
<div className={Style.contentContainer}>
|
||||
{tocPosition === "left" ? (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc} highlightBgColor={highlightBgColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight || (height === 'auto' ? '100vh' : height)}/>) : null}
|
||||
{tocPosition === "left" ? (<TocView toc={toc} showToc={showToc} tocPosition="left" setShowToc={setShowToc}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight || (height === 'auto' ? '100vh' : height)}/>) : null}
|
||||
|
||||
<div className={Style.content} style={{ maxWidth: `calc(100% - ${tocWidth || 228}px)` }}>
|
||||
<div id={containerId} className={classNames(Style.editorContainer, {
|
||||
|
|
@ -166,7 +168,9 @@ export default function Render(props) {
|
|||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
{tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc} highlightBgColor={highlightBgColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
|
||||
{tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,4 +10,5 @@ export default function Render(props: WebComponentProps<EntityDict, 'articleMenu
|
|||
}[];
|
||||
highlightBgColor: string;
|
||||
allowHiddenMenu: boolean;
|
||||
activeColor?: string;
|
||||
}, {}>): React.JSX.Element;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import ArticleDetail from '../../article/detail';
|
|||
import classNames from 'classnames';
|
||||
export default function Render(props) {
|
||||
const { data, methods } = props;
|
||||
const { oakFullpath, menu, menuName, articles, highlightBgColor, allowHiddenMenu } = data;
|
||||
const { oakFullpath, menu, menuName, articles, highlightBgColor, activeColor, allowHiddenMenu } = data;
|
||||
const { t } = methods;
|
||||
const [selectedId, setSelectedId] = useState('');
|
||||
const [menuHidden, setMenuHidden] = useState(false);
|
||||
|
|
@ -43,7 +43,9 @@ export default function Render(props) {
|
|||
</div>))}
|
||||
</div>)}
|
||||
<div className={Styles.docContainer} id="articleBox">
|
||||
{selectedId && (<ArticleDetail oakPath={`$articleMenu/detail-articleDetail-${selectedId}`} oakId={selectedId} tocPosition='right' tocFixed={true} highlightBgColor={highlightBgColor} style={{ width: '100%' }} scrollId={"articleBox"}/>)}
|
||||
{selectedId && (<ArticleDetail oakPath={`$articleMenu/detail-articleDetail-${selectedId}`} oakId={selectedId} tocPosition='right' tocFixed={true}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor} style={{ width: '100%' }} scrollId={"articleBox"}/>)}
|
||||
</div>
|
||||
|
||||
</div>);
|
||||
|
|
|
|||
|
|
@ -14,5 +14,6 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
setCopyArticleUrl: (articleId: string) => string;
|
||||
origin: string;
|
||||
scrollId: string;
|
||||
activeColor: string | undefined;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -27,5 +27,6 @@ export default OakComponent({
|
|||
setCopyArticleUrl: (articleId) => '',
|
||||
origin: 'qiniu', // cos origin默认七牛云
|
||||
scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
|
||||
activeColor: undefined, //目录高亮颜色
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'articleMenu
|
|||
onArticleEdit: (oakId: string) => void;
|
||||
setCopyArticleUrl: (id: string) => string;
|
||||
scrollId?: string;
|
||||
activeColor?: string;
|
||||
}, {
|
||||
gotoDoc: () => void;
|
||||
gotoArticleDetail: (oakId: string) => void;
|
||||
|
|
|
|||
|
|
@ -9,18 +9,18 @@ import Styles from './web.pc.module.less';
|
|||
function BreadcrumbView(props) {
|
||||
const { breadcrumbItems } = props;
|
||||
return (<div style={{ fontSize: 14, display: 'flex', flexDirection: 'row', margin: 10 }}>
|
||||
{breadcrumbItems?.map((breadcrumbItem, index) => {
|
||||
{breadcrumbItems?.map((breadcrumbItem, index) => {
|
||||
return index !== breadcrumbItems.length - 1 ? (<div style={{ color: '#B2B2B2' }} key={index}>
|
||||
{breadcrumbItem}
|
||||
<span style={{ margin: '0 6px' }}>/</span>
|
||||
</div>) : (<div className={Styles.breadcrumbItem} key={index}>
|
||||
{breadcrumbItem}
|
||||
</div>);
|
||||
{breadcrumbItem}
|
||||
<span style={{ margin: '0 6px' }}>/</span>
|
||||
</div>) : (<div className={Styles.breadcrumbItem} key={index}>
|
||||
{breadcrumbItem}
|
||||
</div>);
|
||||
})}
|
||||
</div>);
|
||||
</div>);
|
||||
}
|
||||
export default function Render(props) {
|
||||
const { entity, entityId, oakFullpath, show, articleMenuId, articleId, tocPosition, highlightBgColor, onMenuViewById, onArticlePreview, onArticleEdit, origin, setCopyArticleUrl, scrollId } = props.data;
|
||||
const { entity, entityId, oakFullpath, show, articleMenuId, articleId, tocPosition, highlightBgColor, activeColor, onMenuViewById, onArticlePreview, onArticleEdit, origin, setCopyArticleUrl, scrollId } = props.data;
|
||||
const { gotoDoc, setMessage, gotoArticleDetail } = props.methods;
|
||||
const [editArticleId, setEditArticleId] = useState('');
|
||||
const [breadcrumbItems, setBreadcrumbItems] = useState([]);
|
||||
|
|
@ -117,7 +117,7 @@ export default function Render(props) {
|
|||
</Space>
|
||||
</div>
|
||||
<BreadcrumbView breadcrumbItems={breadcrumbItems}/>
|
||||
{isEdit ? (<ArticleUpsert key={editArticleId} oakId={editArticleId} oakAutoUnmount={true} oakPath={`$articleMenu/treeManager-ArticleUpsert-${editArticleId}`} changeIsEdit={changeIsEdit} tocPosition={tocPosition} highlightBgColor={highlightBgColor} origin={origin} scrollId={scrollId}/>) : (<ArticleCell key={editArticleId} oakId={editArticleId} oakAutoUnmount={true} oakPath={`$articleMenu/treeManager-ArticleCell-${editArticleId}`}/>)}
|
||||
{isEdit ? (<ArticleUpsert key={editArticleId} oakId={editArticleId} oakAutoUnmount={true} oakPath={`$articleMenu/treeManager-ArticleUpsert-${editArticleId}`} changeIsEdit={changeIsEdit} tocPosition={tocPosition} highlightBgColor={highlightBgColor} activeColor={activeColor} origin={origin} scrollId={scrollId}/>) : (<ArticleCell key={editArticleId} oakId={editArticleId} oakAutoUnmount={true} oakPath={`$articleMenu/treeManager-ArticleCell-${editArticleId}`}/>)}
|
||||
</div>) : null}
|
||||
</div>
|
||||
</div>);
|
||||
|
|
@ -125,33 +125,35 @@ export default function Render(props) {
|
|||
return (<div className={Styles.container}>
|
||||
{menuHidden ? (<div className={Styles.menu_hidden}>
|
||||
<div className={Styles.menuHeader}>
|
||||
<Tooltip title={'显示菜单'}>
|
||||
<Button type="text" icon={<MenuUnfoldOutlined />} size="small" onClick={() => setMenuHidden(false)}/>
|
||||
</Tooltip>
|
||||
<Tooltip title={'显示菜单'}>
|
||||
<Button type="text" icon={<MenuUnfoldOutlined />} size="small" onClick={() => setMenuHidden(false)}/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>) : (<div className={Styles.menu}>
|
||||
<div className={Styles.menuHeader}>
|
||||
<div className={Styles.menuTitle}>菜单</div>
|
||||
<div className={Styles.menuActions}>
|
||||
<Space size={4}>
|
||||
<Tooltip title={'查看文档'}>
|
||||
<Button type="text" icon={<EyeOutlined />} size="small" onClick={() => gotoDoc()}/>
|
||||
</Tooltip>
|
||||
<Tooltip title={'添加分类'}>
|
||||
<Button type="text" icon={<PlusOutlined />} size="small" onClick={() => setAddOpen(true)}/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={'隐藏菜单'}>
|
||||
<Button type="text" icon={<MenuFoldOutlined />} size="small" onClick={() => setMenuHidden(true)}/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
<div className={Styles.menuTitle}>菜单</div>
|
||||
<div className={Styles.menuActions}>
|
||||
<Space size={4}>
|
||||
<Tooltip title={'查看文档'}>
|
||||
<Button type="text" icon={<EyeOutlined />} size="small" onClick={() => gotoDoc()}/>
|
||||
</Tooltip>
|
||||
<Tooltip title={'添加分类'}>
|
||||
<Button type="text" icon={<PlusOutlined />} size="small" onClick={() => setAddOpen(true)}/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={'隐藏菜单'}>
|
||||
<Button type="text" icon={<MenuFoldOutlined />} size="small" onClick={() => setMenuHidden(true)}/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
<TreeList oakPath={`$articleMenu/treeManager-TreeList`} entity={entity} entityId={entityId} onGrandChildEditArticleChange={checkEditArticle} show={show} changeAddOpen={changeAddOpen} addOpen={addOpen} onMenuViewById={onMenuViewById} setCopyArticleUrl={setCopyArticleUrl}/>
|
||||
</div>)}
|
||||
|
||||
|
||||
<div className={Styles.editor}>
|
||||
{editArticleId ? (<ArticleUpsert key={editArticleId} oakId={editArticleId} oakAutoUnmount={true} oakPath={`$articleMenu/treeManager-ArticleUpsert-${editArticleId}`} tocPosition={tocPosition} highlightBgColor={highlightBgColor} onArticlePreview={onArticlePreview} origin={origin} scrollId={scrollId}/>) : null}
|
||||
{editArticleId ? (<ArticleUpsert key={editArticleId} oakId={editArticleId} oakAutoUnmount={true} oakPath={`$articleMenu/treeManager-ArticleUpsert-${editArticleId}`} tocPosition={tocPosition}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
onArticlePreview={onArticlePreview} origin={origin} scrollId={scrollId}/>) : null}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export default OakComponent({
|
|||
tocWidth: undefined as number | 'auto' | undefined,
|
||||
tocHeight: undefined as number | undefined,
|
||||
showtitle: false, //大纲顶层显示文章名称
|
||||
activeColor: undefined as string | undefined,
|
||||
},
|
||||
data: {
|
||||
unsub: undefined as undefined | (() => void),
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export default function Render(
|
|||
name?: string;
|
||||
content?: string;
|
||||
tocPosition: 'none' | 'left' | 'right';
|
||||
highlightBgColor: string;
|
||||
highlightBgColor?: string;
|
||||
headerTop: number;
|
||||
className?: string;
|
||||
tocFixed: boolean;
|
||||
|
|
@ -28,11 +28,12 @@ export default function Render(
|
|||
tocWidth?: number;
|
||||
tocHeight?: number | string;
|
||||
showtitle: boolean;
|
||||
activeColor?: string;
|
||||
},
|
||||
{}
|
||||
>
|
||||
) {
|
||||
const { className, name, content, tocPosition = 'none', highlightBgColor = 'none', headerTop = 0, tocClosed = false, scrollId, tocWidth, tocHeight, showtitle } = props.data;
|
||||
const { className, name, content, tocPosition = 'none', highlightBgColor = 'none', activeColor, headerTop = 0, tocClosed = false, scrollId, tocWidth, tocHeight, showtitle } = props.data;
|
||||
const editorConfig: Partial<IEditorConfig> = {
|
||||
readOnly: true,
|
||||
autoFocus: true,
|
||||
|
|
@ -80,7 +81,8 @@ export default function Render(
|
|||
showToc={showToc}
|
||||
tocPosition="left"
|
||||
setShowToc={setShowToc}
|
||||
highlightBgColor={highlightBgColor}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor}
|
||||
headerTop={headerTop}
|
||||
closed={tocClosed}
|
||||
scrollId={scrollId}
|
||||
|
|
@ -131,7 +133,8 @@ export default function Render(
|
|||
showToc={showToc}
|
||||
tocPosition="right"
|
||||
setShowToc={setShowToc}
|
||||
highlightBgColor={highlightBgColor}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor}
|
||||
headerTop={headerTop}
|
||||
closed={tocClosed}
|
||||
scrollId={scrollId}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export default OakComponent({
|
|||
origin: 'qiniu', // 默认为七牛云
|
||||
scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
|
||||
height: 600 as number | 'auto',
|
||||
activeColor: undefined as string | undefined,
|
||||
},
|
||||
listeners: {
|
||||
'editor,content'(prev, next) {
|
||||
|
|
|
|||
|
|
@ -60,13 +60,14 @@ export default function Render(
|
|||
articleMenuId: string;
|
||||
oakId: string;
|
||||
tocPosition: 'none' | 'left' | 'right';
|
||||
highlightBgColor: string;
|
||||
highlightBgColor?: string;
|
||||
scrollId?: string;
|
||||
tocWidth?: number;
|
||||
tocHeight?: number | string;
|
||||
height?: number | string;
|
||||
tocClosed: boolean;
|
||||
execuable: boolean;
|
||||
activeColor?: string;
|
||||
},
|
||||
{
|
||||
setHtml: (content: string) => void;
|
||||
|
|
@ -101,13 +102,14 @@ export default function Render(
|
|||
editor,
|
||||
origin = 'qiniu',
|
||||
tocPosition = 'none',
|
||||
highlightBgColor = 'none',
|
||||
highlightBgColor,
|
||||
scrollId,
|
||||
tocWidth,
|
||||
tocHeight,
|
||||
height = 600,
|
||||
tocClosed = false,
|
||||
execuable,
|
||||
activeColor,
|
||||
} = data;
|
||||
const [articleId, setArticleId] = useState('');
|
||||
const [toc, setToc] = useState<TocItem[]>([]);
|
||||
|
|
@ -173,7 +175,8 @@ export default function Render(
|
|||
showToc={showToc}
|
||||
tocPosition="left"
|
||||
setShowToc={setShowToc}
|
||||
highlightBgColor={highlightBgColor}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor}
|
||||
closed={tocClosed}
|
||||
scrollId={containerId}
|
||||
tocWidth={tocWidth}
|
||||
|
|
@ -364,7 +367,8 @@ export default function Render(
|
|||
showToc={showToc}
|
||||
tocPosition="right"
|
||||
setShowToc={setShowToc}
|
||||
highlightBgColor={highlightBgColor}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor}
|
||||
scrollId={containerId}
|
||||
tocWidth={tocWidth}
|
||||
tocHeight={tocHeight}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export default OakComponent({
|
|||
tocWidth: undefined as number | 'auto' | undefined,
|
||||
tocHeight: undefined as number | undefined,
|
||||
showtitle: false, //大纲顶层显示文章名称
|
||||
activeColor: undefined as string | undefined,
|
||||
},
|
||||
methods: {},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export default function Render(
|
|||
title: string;
|
||||
content?: string;
|
||||
tocPosition: 'none' | 'left' | 'right';
|
||||
highlightBgColor: string;
|
||||
highlightBgColor?: string;
|
||||
headerTop: number;
|
||||
className?: string;
|
||||
tocFixed: boolean;
|
||||
|
|
@ -26,11 +26,12 @@ export default function Render(
|
|||
tocWidth?: number;
|
||||
tocHeight?: number | string;
|
||||
showtitle: boolean;
|
||||
activeColor?: string;
|
||||
},
|
||||
{}
|
||||
>
|
||||
) {
|
||||
const { className, title, content, tocPosition = 'none', tocFixed, highlightBgColor = 'none', headerTop = 0, tocClosed = false, scrollId, tocWidth, tocHeight, showtitle } = props.data;
|
||||
const { className, title, content, tocPosition = 'none', tocFixed, highlightBgColor, activeColor, headerTop = 0, tocClosed = false, scrollId, tocWidth, tocHeight, showtitle } = props.data;
|
||||
const editorConfig: Partial<IEditorConfig> = {
|
||||
readOnly: true,
|
||||
autoFocus: true,
|
||||
|
|
@ -71,7 +72,8 @@ export default function Render(
|
|||
showToc={showToc}
|
||||
tocPosition="left"
|
||||
setShowToc={setShowToc}
|
||||
highlightBgColor={highlightBgColor}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor}
|
||||
headerTop={headerTop}
|
||||
closed={tocClosed}
|
||||
scrollId={scrollId}
|
||||
|
|
@ -122,7 +124,8 @@ export default function Render(
|
|||
showToc={showToc}
|
||||
tocPosition="right"
|
||||
setShowToc={setShowToc}
|
||||
highlightBgColor={highlightBgColor}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor}
|
||||
headerTop={headerTop}
|
||||
closed={tocClosed}
|
||||
scrollId={scrollId}
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@
|
|||
transition: all 1s ease;
|
||||
}
|
||||
|
||||
.highlightBorder {
|
||||
border-left: 1px solid var(--oak-color-primary);
|
||||
.active {
|
||||
color: var(--active-color);
|
||||
}
|
||||
|
||||
.catalogTitle {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ export function TocView(
|
|||
showToc: boolean;
|
||||
tocPosition: 'left' | 'right';
|
||||
setShowToc: (showToc: boolean) => void;
|
||||
highlightBgColor: string;
|
||||
highlightBgColor?: string; //暂时废弃背景色改为高亮文字
|
||||
activeColor?: string;
|
||||
headerTop?: number;
|
||||
scrollId?: string;
|
||||
closed?: boolean,
|
||||
|
|
@ -25,19 +26,14 @@ export function TocView(
|
|||
title?: string,
|
||||
}
|
||||
) {
|
||||
const { toc, showToc, tocPosition, setShowToc, highlightBgColor, headerTop = 0, scrollId, closed = false, tocWidth, tocHeight = '100vh', title } = props;
|
||||
const { toc, showToc, tocPosition, setShowToc, highlightBgColor, activeColor = 'var(--oak-color-primary)', headerTop = 0, scrollId, closed = false, tocWidth, tocHeight = '100vh', title } = props;
|
||||
|
||||
// useEffect(() => {
|
||||
// document.documentElement.style.setProperty('--highlight-bg-color', highlightBgColor);
|
||||
// }, [highlightBgColor]);
|
||||
useEffect(() => {
|
||||
document.documentElement.style.setProperty('--highlight-bg-color', highlightBgColor);
|
||||
}, [highlightBgColor]);
|
||||
|
||||
const [selectedId, setSelectedId] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedId && selectedId !== '') {
|
||||
const e = document.getElementById(`li-${selectedId}`);
|
||||
}
|
||||
}, [selectedId]);
|
||||
document.documentElement.style.setProperty('--active-color', activeColor);
|
||||
}, [activeColor]);
|
||||
|
||||
const generateTocList = (items: TocItem[], currentLevel: number = 1, parentId: string | null = null): React.ReactNode => {
|
||||
//递归生成嵌套列表
|
||||
|
|
@ -82,39 +78,38 @@ export function TocView(
|
|||
<div
|
||||
style={{ fontSize: '1em', fontWeight: item.level === 1 ? 'bold' : 'normal' }}
|
||||
className={Style.tocItem}
|
||||
onClick={(event: any) => {
|
||||
setSelectedId(item.id);
|
||||
//页面滚动到对应元素
|
||||
const elem = document.getElementById(item.id);
|
||||
const elemTop = elem?.getBoundingClientRect().top;
|
||||
// onClick={(event: any) => {
|
||||
// //页面滚动到对应元素
|
||||
// const elem = document.getElementById(item.id);
|
||||
// const elemTop = elem?.getBoundingClientRect().top;
|
||||
|
||||
if (scrollId) {
|
||||
const scrollContainer = document.getElementById(scrollId);
|
||||
const containerTop = scrollContainer?.getBoundingClientRect().top;
|
||||
// if (scrollId) {
|
||||
// const scrollContainer = document.getElementById(scrollId);
|
||||
// const containerTop = scrollContainer?.getBoundingClientRect().top;
|
||||
|
||||
scrollContainer?.scrollBy({
|
||||
top: elemTop! - containerTop!,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
else {
|
||||
// const containerTop = document.body.getBoundingClientRect().top;
|
||||
window.scrollBy({
|
||||
top: elemTop! - headerTop,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
// scrollContainer?.scrollBy({
|
||||
// top: elemTop! - containerTop!,
|
||||
// behavior: 'smooth',
|
||||
// });
|
||||
// }
|
||||
// else {
|
||||
// // const containerTop = document.body.getBoundingClientRect().top;
|
||||
// window.scrollBy({
|
||||
// top: elemTop! - headerTop,
|
||||
// behavior: 'smooth',
|
||||
// });
|
||||
// }
|
||||
|
||||
//添加背景色
|
||||
elem?.classList.add(Style.highlight);
|
||||
//移除背景色类名
|
||||
setTimeout(function () {
|
||||
elem?.classList.remove(Style.highlight);
|
||||
}, 1000);
|
||||
// //添加背景色
|
||||
// elem?.classList.add(Style.highlight);
|
||||
// //移除背景色类名
|
||||
// setTimeout(function () {
|
||||
// elem?.classList.remove(Style.highlight);
|
||||
// }, 1000);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}}
|
||||
// event.preventDefault();
|
||||
// event.stopPropagation();
|
||||
// }}
|
||||
>
|
||||
{item.text}
|
||||
</div>
|
||||
|
|
@ -127,6 +122,147 @@ export function TocView(
|
|||
return result;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const tocContainer = document.getElementById('tocContainer');
|
||||
const tocItems = tocContainer?.querySelectorAll('li');
|
||||
let sections = [];
|
||||
for (const t of toc) {
|
||||
const item = document.getElementById(t.id);
|
||||
sections.push(item!);
|
||||
}
|
||||
|
||||
let currentHighlighted = '';
|
||||
let isClick = false;
|
||||
let clickTimeout: NodeJS.Timeout | undefined = undefined;
|
||||
|
||||
// 滚动目录以确保指定目录项可见
|
||||
function scrollToTocItem(item: any) {
|
||||
if (tocContainer) {
|
||||
const tocRect = tocContainer.getBoundingClientRect();
|
||||
const itemRect = item.getBoundingClientRect();
|
||||
|
||||
// 检查目录项是否在可视区域内
|
||||
if (itemRect.top < tocRect.top || itemRect.bottom > tocRect.bottom) {
|
||||
const itemOffsetTop = item.offsetTop;
|
||||
const containerHeight = tocContainer.clientHeight;
|
||||
const itemHeight = item.offsetHeight;
|
||||
|
||||
let targetScrollTop;
|
||||
if (itemRect.top < tocRect.top) {
|
||||
// 目录项在可视区域上方
|
||||
targetScrollTop = itemOffsetTop - 80;
|
||||
} else {
|
||||
// 目录项在可视区域下方
|
||||
targetScrollTop = itemOffsetTop - containerHeight + itemHeight + 10;
|
||||
}
|
||||
|
||||
tocContainer.scrollTo({
|
||||
top: targetScrollTop,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 高亮指定的目录项
|
||||
function highlightTocItem(targetId: string, source: 'click' | 'scroll') {
|
||||
// 移除所有目录项的active样式
|
||||
tocItems?.forEach(item => {
|
||||
item.classList.remove(Style.active);
|
||||
});
|
||||
|
||||
// 找到对应的目录项并添加active样式
|
||||
const correspondingTocItem = document.querySelector(`#${targetId}`);
|
||||
if (correspondingTocItem) {
|
||||
correspondingTocItem.classList.add(Style.active);
|
||||
|
||||
// 滚动目录
|
||||
scrollToTocItem(correspondingTocItem);
|
||||
|
||||
// 如果是点击触发的,设置超时
|
||||
if (source === 'click') {
|
||||
isClick = true;
|
||||
|
||||
// 设置超时,1秒后恢复滚动的高亮功能
|
||||
clearTimeout(clickTimeout);
|
||||
clickTimeout = setTimeout(() => {
|
||||
isClick = false;
|
||||
}, 1000);
|
||||
}
|
||||
currentHighlighted = targetId;
|
||||
}
|
||||
}
|
||||
|
||||
// 点击目录项平滑滚动到对应区块并高亮
|
||||
const handleClick = function (event: any) {
|
||||
const targetSection = event.target.closest('li');
|
||||
const targetId = targetSection.getAttribute('id');
|
||||
if (targetSection) {
|
||||
// 高亮点击的目录项
|
||||
highlightTocItem(targetId, 'click');
|
||||
|
||||
// 平滑滚动到目标位置
|
||||
const itemId = targetId.substring(3);
|
||||
const elem = document.getElementById(itemId);
|
||||
const elemTop = elem?.getBoundingClientRect().top;
|
||||
if (scrollId) {
|
||||
const scrollContainer = document.getElementById(scrollId);
|
||||
const containerTop = scrollContainer?.getBoundingClientRect().top;
|
||||
scrollContainer?.scrollBy({
|
||||
top: elemTop! - containerTop!,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
window.scrollBy({
|
||||
top: elemTop! - headerTop,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
tocItems?.forEach(item => {
|
||||
item.addEventListener('click', handleClick);
|
||||
});
|
||||
|
||||
// 创建Intersection Observer实例
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
// 如果处于点击状态,跳过滚动检测
|
||||
if (isClick) return;
|
||||
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
// 获取当前可见区块的ID
|
||||
const id = `li-${entry.target.getAttribute('id')}`;
|
||||
|
||||
if (id && id !== currentHighlighted) {
|
||||
highlightTocItem(id, 'scroll');
|
||||
}
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.5,
|
||||
rootMargin: '0px 0px -75% 0px',
|
||||
});
|
||||
|
||||
// 开始观察所有内容区块
|
||||
sections.forEach(section => {
|
||||
observer.observe(section);
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (tocContainer) {
|
||||
tocContainer.removeEventListener('click', handleClick);
|
||||
observer.disconnect();
|
||||
if (clickTimeout) {
|
||||
clearTimeout(clickTimeout);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [toc, showToc]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={Style.tocContainer}
|
||||
|
|
@ -140,7 +276,7 @@ export function TocView(
|
|||
</div>
|
||||
|
||||
{(toc && toc.length > 0) ? (
|
||||
<ul style={{ listStyleType: 'none', paddingInlineStart: '0px', overflowX: 'hidden', overflowY: 'auto', height: '100%', borderLeft: tocPosition === 'right' ? '1px solid var(--oak-border-color)' : '' }}>{generateTocList([...toc])}</ul>
|
||||
<ul id="tocContainer" style={{ listStyleType: 'none', paddingInlineStart: '0px', overflowX: 'hidden', overflowY: 'auto', height: '100%', borderLeft: tocPosition === 'right' ? '1px solid var(--oak-border-color)' : '' }}>{generateTocList([...toc])}</ul>
|
||||
) : (
|
||||
<div style={{ display: 'flex', alignItems: 'center', color: '#B1B1B1', height: '200px' }}>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export default OakComponent({
|
|||
origin: 'qiniu', // 默认为七牛云
|
||||
scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
|
||||
height: 600 as number | 'auto',
|
||||
activeColor: undefined as string | undefined,
|
||||
},
|
||||
listeners: {
|
||||
'editor,content'(prev, next) {
|
||||
|
|
|
|||
|
|
@ -60,11 +60,12 @@ export default function Render(
|
|||
articleMenuId: string;
|
||||
oakId: string;
|
||||
tocPosition: 'none' | 'left' | 'right';
|
||||
highlightBgColor: string;
|
||||
highlightBgColor?: string;
|
||||
scrollId?: string;
|
||||
tocWidth?: number;
|
||||
tocHeight?: number | string;
|
||||
height?: number | string;
|
||||
activeColor?: string;
|
||||
},
|
||||
{
|
||||
setHtml: (content: string) => void;
|
||||
|
|
@ -98,7 +99,8 @@ export default function Render(
|
|||
editor,
|
||||
origin = 'qiniu',
|
||||
tocPosition = 'none',
|
||||
highlightBgColor = 'none',
|
||||
highlightBgColor,
|
||||
activeColor,
|
||||
scrollId,
|
||||
tocWidth,
|
||||
tocHeight,
|
||||
|
|
@ -141,7 +143,8 @@ export default function Render(
|
|||
showToc={showToc}
|
||||
tocPosition="left"
|
||||
setShowToc={setShowToc}
|
||||
highlightBgColor={highlightBgColor}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor}
|
||||
scrollId={containerId}
|
||||
tocWidth={tocWidth}
|
||||
tocHeight={tocHeight || (height === 'auto' ? '100vh' : height)}
|
||||
|
|
@ -351,7 +354,8 @@ export default function Render(
|
|||
showToc={showToc}
|
||||
tocPosition="right"
|
||||
setShowToc={setShowToc}
|
||||
highlightBgColor={highlightBgColor}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor}
|
||||
scrollId={containerId}
|
||||
tocWidth={tocWidth}
|
||||
tocHeight={tocHeight}
|
||||
|
|
|
|||
|
|
@ -22,13 +22,14 @@ export default function Render(
|
|||
}[]
|
||||
highlightBgColor: string;
|
||||
allowHiddenMenu: boolean;
|
||||
activeColor?: string;
|
||||
},
|
||||
{
|
||||
}
|
||||
>
|
||||
) {
|
||||
const { data, methods } = props;
|
||||
const { oakFullpath, menu, menuName, articles, highlightBgColor, allowHiddenMenu } = data;
|
||||
const { oakFullpath, menu, menuName, articles, highlightBgColor, activeColor, allowHiddenMenu } = data;
|
||||
const { t } = methods;
|
||||
const [selectedId, setSelectedId] = useState('');
|
||||
const [menuHidden, setMenuHidden] = useState(false);
|
||||
|
|
@ -78,7 +79,7 @@ export default function Render(
|
|||
behavior: 'instant',
|
||||
});
|
||||
}, 100);
|
||||
|
||||
|
||||
}
|
||||
}}
|
||||
className={ele.id === selectedId ? classNames(Styles.article, Styles['article-selected']) : Styles.article}
|
||||
|
|
@ -96,7 +97,8 @@ export default function Render(
|
|||
oakId={selectedId}
|
||||
tocPosition='right'
|
||||
tocFixed={true}
|
||||
highlightBgColor={highlightBgColor}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor}
|
||||
style={{ width: '100%' }}
|
||||
scrollId={"articleBox"}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -28,5 +28,6 @@ export default OakComponent({
|
|||
setCopyArticleUrl: (articleId: string) => '' as string,
|
||||
origin: 'qiniu', // cos origin默认七牛云
|
||||
scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
|
||||
activeColor: undefined as string | undefined, //目录高亮颜色
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,21 +13,21 @@ function BreadcrumbView(props: { breadcrumbItems: string[] }) {
|
|||
const { breadcrumbItems } = props;
|
||||
return (
|
||||
<div style={{ fontSize: 14, display: 'flex', flexDirection: 'row', margin: 10 }}>
|
||||
{
|
||||
breadcrumbItems?.map((breadcrumbItem: string, index: number) => {
|
||||
return index !== breadcrumbItems.length - 1 ? (
|
||||
<div style={{ color: '#B2B2B2' }} key={index}>
|
||||
{breadcrumbItem}
|
||||
<span style={{ margin: '0 6px' }}>/</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className={Styles.breadcrumbItem} key={index}>
|
||||
{breadcrumbItem}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
{
|
||||
breadcrumbItems?.map((breadcrumbItem: string, index: number) => {
|
||||
return index !== breadcrumbItems.length - 1 ? (
|
||||
<div style={{ color: '#B2B2B2' }} key={index}>
|
||||
{breadcrumbItem}
|
||||
<span style={{ margin: '0 6px' }}>/</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className={Styles.breadcrumbItem} key={index}>
|
||||
{breadcrumbItem}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +49,8 @@ export default function Render(
|
|||
onArticlePreview: (content?: string, title?: string) => void;
|
||||
onArticleEdit: (oakId: string) => void;
|
||||
setCopyArticleUrl: (id: string) => string;
|
||||
scrollId?: string
|
||||
scrollId?: string;
|
||||
activeColor?: string;
|
||||
},
|
||||
{
|
||||
gotoDoc: () => void;
|
||||
|
|
@ -57,7 +58,7 @@ export default function Render(
|
|||
}
|
||||
>
|
||||
) {
|
||||
const { entity, entityId, oakFullpath, show, articleMenuId, articleId, tocPosition, highlightBgColor, onMenuViewById, onArticlePreview, onArticleEdit, origin, setCopyArticleUrl, scrollId } = props.data;
|
||||
const { entity, entityId, oakFullpath, show, articleMenuId, articleId, tocPosition, highlightBgColor, activeColor, onMenuViewById, onArticlePreview, onArticleEdit, origin, setCopyArticleUrl, scrollId } = props.data;
|
||||
const { gotoDoc, setMessage, gotoArticleDetail } = props.methods;
|
||||
const [editArticleId, setEditArticleId] = useState('');
|
||||
const [breadcrumbItems, setBreadcrumbItems] = useState([] as string[]);
|
||||
|
|
@ -201,13 +202,13 @@ export default function Render(
|
|||
{isEdit ? (
|
||||
<Button
|
||||
onClick={() => setIsEdit(false)}
|
||||
>
|
||||
>
|
||||
<FileOutlined />返回
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => setIsEdit(true)}
|
||||
>
|
||||
>
|
||||
<EditOutlined />更新
|
||||
</Button>
|
||||
)}
|
||||
|
|
@ -224,6 +225,7 @@ export default function Render(
|
|||
changeIsEdit={changeIsEdit}
|
||||
tocPosition={tocPosition}
|
||||
highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor}
|
||||
origin={origin}
|
||||
scrollId={scrollId}
|
||||
/>
|
||||
|
|
@ -250,49 +252,49 @@ export default function Render(
|
|||
menuHidden ? (
|
||||
<div className={Styles.menu_hidden}>
|
||||
<div className={Styles.menuHeader}>
|
||||
<Tooltip title={'显示菜单'}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuUnfoldOutlined />}
|
||||
size="small"
|
||||
onClick={() => setMenuHidden(false)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title={'显示菜单'}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuUnfoldOutlined />}
|
||||
size="small"
|
||||
onClick={() => setMenuHidden(false)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={Styles.menu}>
|
||||
<div className={Styles.menuHeader}>
|
||||
<div className={Styles.menuTitle}>菜单</div>
|
||||
<div className={Styles.menuActions}>
|
||||
<Space size={4}>
|
||||
<Tooltip title={'查看文档'}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EyeOutlined />}
|
||||
size="small"
|
||||
onClick={() => gotoDoc()}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title={'添加分类'}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<PlusOutlined />}
|
||||
size="small"
|
||||
onClick={() => setAddOpen(true)}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={'隐藏菜单'}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuFoldOutlined />}
|
||||
size="small"
|
||||
onClick={() => setMenuHidden(true)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
<div className={Styles.menuTitle}>菜单</div>
|
||||
<div className={Styles.menuActions}>
|
||||
<Space size={4}>
|
||||
<Tooltip title={'查看文档'}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EyeOutlined />}
|
||||
size="small"
|
||||
onClick={() => gotoDoc()}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title={'添加分类'}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<PlusOutlined />}
|
||||
size="small"
|
||||
onClick={() => setAddOpen(true)}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={'隐藏菜单'}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuFoldOutlined />}
|
||||
size="small"
|
||||
onClick={() => setMenuHidden(true)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
<TreeList
|
||||
oakPath={`$articleMenu/treeManager-TreeList`}
|
||||
|
|
@ -308,7 +310,7 @@ export default function Render(
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
<div className={Styles.editor}>
|
||||
{
|
||||
editArticleId ? (
|
||||
|
|
@ -318,7 +320,7 @@ export default function Render(
|
|||
oakAutoUnmount={true}
|
||||
oakPath={`$articleMenu/treeManager-ArticleUpsert-${editArticleId}`}
|
||||
tocPosition={tocPosition}
|
||||
highlightBgColor={highlightBgColor}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
onArticlePreview={onArticlePreview}
|
||||
origin={origin}
|
||||
scrollId={scrollId}
|
||||
|
|
|
|||
Loading…
Reference in New Issue