diff --git a/package-lock.json b/package-lock.json index 2dfe86e..ba2af5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@seafile/seafile-editor", - "version": "1.0.118", + "version": "1.0.121", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@seafile/seafile-editor", - "version": "1.0.118", + "version": "1.0.121", "license": "Apache-2.0", "dependencies": { "@seafile/react-image-lightbox": "2.0.5", diff --git a/package.json b/package.json index 29f7e6c..1fc96c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@seafile/seafile-editor", - "version": "1.0.118", + "version": "1.0.121", "description": "", "main": "dist/index.js", "scripts": { diff --git a/src/constants/event-types.js b/src/constants/event-types.js index 07542d7..ec74138 100644 --- a/src/constants/event-types.js +++ b/src/constants/event-types.js @@ -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 = { diff --git a/src/containers/article-info/index.js b/src/containers/article-info/index.js index ba5b0b9..889951c 100644 --- a/src/containers/article-info/index.js +++ b/src/containers/article-info/index.js @@ -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'; @@ -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); }, []); @@ -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 (
diff --git a/src/containers/outline/index.js b/src/containers/outline/index.js index 810d34c..c6aea3b 100644 --- a/src/containers/outline/index.js +++ b/src/containers/outline/index.js @@ -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'; @@ -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) => { @@ -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); @@ -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 = () => { @@ -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 ( -
- {isShown && ( - <> -
-

{t('Outline')}

- -
- {headerList.length === 0 ? ( -
{t('No_outline')}
- ) : ( -
- {headerList.map((node, index) => ( - - ))} +
+
+ {isShown && ( + <> +
+

{t('Outline')}

+
- )} - - )} + {headerList.length === 0 ? ( +
{t('No_outline')}
+ ) : ( +
+ {headerList.map((node, index) => ( + + ))} +
+ )} + + )} +
{!isShown && ( - <> - - {t('Outline')} - - + + {t('Outline')} + )}
); diff --git a/src/containers/outline/outline-item.js b/src/containers/outline/outline-item.js index 09937e8..9b8520d 100644 --- a/src/containers/outline/outline-item.js +++ b/src/containers/outline/outline-item.js @@ -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 ( -
+
{node.children.map(child => child.text).join('')}
); diff --git a/src/containers/outline/style.css b/src/containers/outline/style.css index 2564407..9b86fd1 100644 --- a/src/containers/outline/style.css +++ b/src/containers/outline/style.css @@ -1,16 +1,26 @@ -.sf-editor-outline { - width: 220px; +.sf-editor-outline-wrapper { display: flex; + margin: 20px 30px 20px 16px; min-height: 0; - flex-direction: column; - font-size: 14px; + pointer-events: none; position: fixed; - margin: 20px 30px 20px 16px; + bottom: 0; top: 100px; + z-index: 101; +} + +.sf-editor-outline-wrapper.active { + pointer-events: all; } -.sf-editor-outline.active { - pointer-events: all; +.sf-editor-outline { + display: flex; + flex: 1 1; + flex-direction: column; + font-size: 14px; + min-height: 0; + position: relative; + width: 220px; } .sf-editor-outline-header { @@ -38,23 +48,27 @@ } .sf-editor-outline-list-container { - padding: 0.5rem 0; - flex: 1 1; display: flex; + flex: 1 1; flex-direction: column; + list-style: none; overflow-x: hidden; overflow-y: auto; - max-height: 400px; + padding: .5rem 0; + word-break: break-word; } .sf-editor-outline-item { - padding: 4px 0; - padding-right: 6px; - max-width: 100%; + padding: 4px 6px 4px 0; + width: 100%; overflow-wrap: anywhere; cursor: pointer; } +.sf-editor-outline-item.active { + color: #eb8205; +} + .sf-editor-outline-menu { line-height: 1; font-size: 14px; @@ -70,7 +84,7 @@ justify-content: center; position: absolute; top: 20px; - left: -15px; + left: -15px;; pointer-events: all; } @@ -104,43 +118,7 @@ color: #333; } -.sf-editor-outline .outline-h2, -.sf-editor-outline .outline-h3 { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.sf-editor-outline .outline-h2 { - margin-left: 20px; - line-height: 2.5; - color:#364149; - white-space: nowrap; - cursor:pointer; -} - -.sf-editor-outline .outline-h2:hover { - color: #eb8205; -} - -.sf-editor-outline .outline-h3 { - margin-left: 40px; - line-height: 2.5; - color:#364149; - white-space: nowrap; - cursor:pointer; -} - -.sf-editor-outline .outline-h3:hover { - color: #eb8205; -} - .sf-editor-outline .empty-container { margin-top: 10px; text-align: center; } - -.sf-editor-outline .outline-h2.active, -.sf-editor-outline .outline-h3.active { - color: #eb8205; -} diff --git a/src/editors/slate-editor/editor-help/index.js b/src/editors/slate-editor/editor-help/index.js index 0dfd858..f3e7497 100644 --- a/src/editors/slate-editor/editor-help/index.js +++ b/src/editors/slate-editor/editor-help/index.js @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import classNames from 'classnames'; import EventBus from '../../../utils/event-bus'; -import { EXTERNAL_EVENTS } from '../../../constants/event-types'; +import { EXTERNAL_EVENTS, INTERNAL_EVENTS } from '../../../constants/event-types'; import HotKeysHelper from '../../../containers/hotkeys-helper'; import ArticleInfo from '../../../containers/article-info'; @@ -20,6 +20,8 @@ export default function EditorHelp({ children }) { const updateHelpInfoState = useCallback((state) => { setIsShowHelpInfo(state); setIsShowArticleInfo(false); + const eventBus = EventBus.getInstance(); + eventBus.dispatch(INTERNAL_EVENTS.RESIZE_ARTICLE); }, []); useEffect(() => { diff --git a/src/editors/slate-editor/index.js b/src/editors/slate-editor/index.js index dd29112..8421848 100644 --- a/src/editors/slate-editor/index.js +++ b/src/editors/slate-editor/index.js @@ -10,7 +10,7 @@ import { focusEditor } from '../../extension/core'; import { ScrollContext } from '../../hooks/use-scroll-context'; import useSeafileUtils from '../../hooks/use-insert-image'; import { isDocumentEmpty, isMac } from '../../utils/common'; -import Outline from "../../containers/outline"; +import Outline, { getOutlineSetting } from '../../containers/outline'; import { INTERNAL_EVENTS } from '../../constants/event-types'; import './style.css'; @@ -30,23 +30,32 @@ export default function SlateEditor({ value, editorApi, onSave, onContentChanged const decorate = useHighlight(editor); - //Adjust article container margin-left value according to isShown of the outline and width of window - const handleWindowResize = (newIsShown) => { + // Adjust article container margin-left value according to isShown of the outline and width of window + const handleWindowResize = useCallback(() => { const rect = scrollRef.current.getBoundingClientRect(); const articleElement = document.querySelector('.article'); const articleRect = articleElement ? articleElement.getBoundingClientRect() : null; - if (newIsShown && articleRect && (rect.width - articleRect.width) / 2 < 280) { - setContainerStyle({ marginLeft: '280px' }); + const isOutlineShow = getOutlineSetting(); + if (isOutlineShow && articleRect && (rect.width - articleRect.width) / 2 < 280) { + setContainerStyle({ marginLeft: 280 }); } else { setContainerStyle({}); } - } + }, []); useEffect(() => { + handleWindowResize(); + window.addEventListener('resize', handleWindowResize); const eventBus = EventBus.getInstance(); const unsubscribeOutline = eventBus.subscribe(INTERNAL_EVENTS.OUTLINE_STATE_CHANGED, handleWindowResize); - return unsubscribeOutline; - }, [handleWindowResize]); + const unsubscribeResizeArticle = eventBus.subscribe(INTERNAL_EVENTS.RESIZE_ARTICLE, handleWindowResize); + return () => { + window.removeEventListener('resize', handleWindowResize); + unsubscribeOutline(); + unsubscribeResizeArticle(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const onChange = useCallback((value) => { setSlateValue(value);