Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: outline style #215

Merged
merged 4 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@seafile/seafile-editor",
"version": "1.0.118",
"version": "1.0.121",
"description": "",
"main": "dist/index.js",
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions src/constants/event-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const INTERNAL_EVENTS = {
ON_SELECT_ALL_CELL: 'on_select_all_cell',
ON_TOGGLE_IMAGE_POPOVER: 'on_toggle_image_popover',
OUTLINE_STATE_CHANGED: 'outline_state_changed',
RESIZE_ARTICLE: 'resize_article',
};

export const EXTERNAL_EVENTS = {
Expand Down
18 changes: 13 additions & 5 deletions src/containers/article-info/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useCallback, useState, useMemo, useEffect } from 'react';
import PropTypes from 'prop-types';
import ResizeWidth from './resize-width';
import EventBus from '../../utils/event-bus';
import { EXTERNAL_EVENTS } from '../../constants/event-types';
import { EXTERNAL_EVENTS, INTERNAL_EVENTS } from '../../constants/event-types';

import './style.css';

Expand Down Expand Up @@ -34,13 +34,16 @@ const ArticleInfo = ({ isVisible }) => {
}, []);

const resizeWidthEnd = useCallback((width) => {
const panelWidth = JSON.parse(window.localStorage.getItem('sf-editor-panel-width') || '{}');
window.localStorage.setItem('sf-editor-panel-width', JSON.stringify({ ...panelWidth, width }));
const settings = JSON.parse(window.localStorage.getItem('sf-editor') || '{}');
window.localStorage.setItem('sf-editor', JSON.stringify({ ...settings, panelWidth: width }));
const eventBus = EventBus.getInstance();
eventBus.dispatch(INTERNAL_EVENTS.RESIZE_ARTICLE);
}, []);

useEffect(() => {
const panelWidth = JSON.parse(window.localStorage.getItem('sf-editor-panel-width', '{}')) || {};
const width = Math.max(MIN_PANEL_WIDTH, Math.min(parseInt(panelWidth.width, 10) || MIN_PANEL_WIDTH, MAX_PANEL_WIDTH));
const settings = JSON.parse(window.localStorage.getItem('sf-editor', '{}')) || {};
const { panelWidth } = settings;
const width = Math.max(MIN_PANEL_WIDTH, Math.min(parseInt(panelWidth, 10) || MIN_PANEL_WIDTH, MAX_PANEL_WIDTH));
setWidth(width);
}, []);

Expand All @@ -57,6 +60,11 @@ const ArticleInfo = ({ isVisible }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
const eventBus = EventBus.getInstance();
eventBus.dispatch(INTERNAL_EVENTS.RESIZE_ARTICLE);
}, [isVisible, fileDetails]);

const { component: fileDetailsComponent, props: fileDetailsProps } = fileDetails || {};
return (
<div className="sf-article-info-container-wrapper" style={containerWrapperStyle}>
Expand Down
123 changes: 58 additions & 65 deletions src/containers/outline/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import classnames from 'classnames';
import OutlineItem from './outline-item';
import { useScrollContext } from '../../hooks/use-scroll-context';
import { TRANSLATE_NAMESPACE } from '../../constants';
Expand All @@ -9,6 +10,20 @@ import { INTERNAL_EVENTS } from '../../constants/event-types';

import './style.css';

export const getOutlineSetting = () => {
const currentValue = localStorage.getItem('sf-editor');
const config = currentValue ? JSON.parse(currentValue) : {};
const { outlineOpen = false } = config;
return outlineOpen;
};

export const setOutlineSetting = (isShown) => {
const currentValue = localStorage.getItem('sf-editor');
const config = currentValue ? JSON.parse(currentValue) : {};
config['outlineOpen'] = isShown;
localStorage.setItem('sf-editor', JSON.stringify(config));
};

const getHeaderList = (children) => {
const headerList = [];
children.forEach((node) => {
Expand All @@ -23,7 +38,6 @@ const Outline = ({ editor }) => {
const { t } = useTranslation(TRANSLATE_NAMESPACE);
const scrollRef = useScrollContext();
const [headerList, setHeaderList] = useState([]);
const [activeId, setActiveId] = useState('');
const [isShown, setIsShown] = useState(false);
const [scrollLeft, setScrollLeft] = useState(0);

Expand All @@ -32,47 +46,19 @@ const Outline = ({ editor }) => {
setHeaderList(headerList);
}, [editor.children]);

const handleScroll = useCallback((e) => {
const scrollTop = scrollRef.current.scrollTop;
const styles = getComputedStyle(scrollRef?.current);
const paddingTop = parseInt(styles.paddingTop);
for (let i = 0; i < headerList.length; i++) {
const headerItem = headerList[i];
const dom = document.getElementById(headerItem.id);
const { offsetTop, offsetHeight } = dom;
const styles = getComputedStyle(dom);
const marginTop = parseInt(styles.marginTop);
if (offsetTop + offsetHeight + marginTop > scrollTop - paddingTop) {
setActiveId(headerItem.id);
break;
}
}
}, [headerList, scrollRef]);

useEffect(() => {
let observerRefValue = null;
if (scrollRef.current) {
scrollRef.current.addEventListener('scroll', handleScroll);
observerRefValue = scrollRef.current;
}

return () => {
observerRefValue.removeEventListener('scroll', handleScroll);
};
}, [handleScroll, scrollRef]);
const updateOutlineState = useCallback((nextState) => {
setOutlineSetting(nextState);
setIsShown(nextState);
const eventBus = EventBus.getInstance();
eventBus.dispatch(INTERNAL_EVENTS.OUTLINE_STATE_CHANGED);
}, []);

const toggleShow = useCallback(() => {
setIsShown(prevIsShown => {
const newIsShown = !prevIsShown;
setTimeout(() => {
const eventBus = EventBus.getInstance();
eventBus.dispatch(INTERNAL_EVENTS.OUTLINE_STATE_CHANGED, newIsShown);
}, 0);
return newIsShown;
});
}, []);
const nextState = !isShown;
updateOutlineState(nextState);
}, [isShown, updateOutlineState]);

useEffect(() => {
useEffect(() => {
if (!scrollRef.current) return;

const updateScrollLeft = () => {
Expand All @@ -86,35 +72,42 @@ const Outline = ({ editor }) => {
};
}, [scrollRef]);

useEffect(() => {
const outlineState = getOutlineSetting();
console.log(outlineState);
updateOutlineState(outlineState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<div className="sf-editor-outline" style={{ left: -scrollLeft }}>
{isShown && (
<>
<div className="sf-editor-outline-header">
<h2 className="sf-editor-outline-header_title">{t('Outline')}</h2>
<span className="sf-editor-outline-header_close iconfont icon-x" onClick={toggleShow}></span>
</div>
{headerList.length === 0 ? (
<div className="empty-container">{t('No_outline')}</div>
) : (
<div className="sf-editor-outline-list-container">
{headerList.map((node, index) => (
<OutlineItem key={index} node={node} activeId={activeId} />
))}
<div className={classnames('sf-editor-outline-wrapper', { 'active': isShown })} style={{ left: -scrollLeft }}>
<div className="sf-editor-outline" >
{isShown && (
<>
<div className="sf-editor-outline-header">
<h2 className="sf-editor-outline-header_title">{t('Outline')}</h2>
<span className="sf-editor-outline-header_close iconfont icon-x" onClick={toggleShow}></span>
</div>
)}
</>
)}
{headerList.length === 0 ? (
<div className="empty-container">{t('No_outline')}</div>
) : (
<div className="sf-editor-outline-list-container">
{headerList.map((node, index) => (
<OutlineItem key={index} node={node} />
))}
</div>
)}
</>
)}
</div>
{!isShown && (
<>
<span
id="sf-editor-outline-menu"
className="sf-editor-outline-menu sf-edito-tooltip iconfont icon-outline"
onClick={toggleShow}
>
<span className="custom-tooltip">{t('Outline')}</span>
</span>
</>
<span
id="sf-editor-outline-menu"
className="sf-editor-outline-menu sf-editor-tooltip iconfont icon-outline"
onClick={toggleShow}
>
<span className="custom-tooltip">{t('Outline')}</span>
</span>
)}
</div>
);
Expand Down
29 changes: 22 additions & 7 deletions src/containers/outline/outline-item.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

const OutlineItem = ({ node, activeId }) => {
const OutlineItem = ({ node }) => {
const [isHighlighted, setIsHighlighted] = useState(false);

const onMouseOver = useCallback(() => {
setIsHighlighted(true);
}, []);

const onMouseOut = useCallback(() => {
setIsHighlighted(false);
}, []);

const onItemClick = useCallback(() => {
const { id } = node;
document.getElementById(id).scrollIntoView();
}, [node]);

const className = classNames({
'outline-h2': node.type === 'header2',
'outline-h3': node.type === 'header3',
'active': node.id === activeId,
const className = classNames('sf-editor-outline-item', {
'pl-5': node.type === 'header2',
'pl-7': node.type === 'header3',
'active': isHighlighted,
});

return (
<div className={className} onClick={onItemClick}>
<div
className={className}
onClick={onItemClick}
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
>
{node.children.map(child => child.text).join('')}
</div>
);
Expand Down
Loading
Loading