mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-26 03:33:58 -05:00
Extract toc button (#1302)
* Extract toc button Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
4e9b059d2e
commit
4720f2d36b
10 changed files with 99 additions and 148 deletions
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { RefObject, useCallback } from 'react'
|
||||
import { LineMarkerPosition } from '../../../markdown-renderer/types'
|
||||
|
||||
export const useAdaptedLineMarkerCallback = (
|
||||
documentRenderPaneRef: RefObject<HTMLDivElement> | undefined,
|
||||
rendererRef: RefObject<HTMLDivElement>,
|
||||
onLineMarkerPositionChanged: ((lineMarkerPosition: LineMarkerPosition[]) => void) | undefined
|
||||
): ((lineMarkerPosition: LineMarkerPosition[]) => void) => {
|
||||
return useCallback(
|
||||
(linkMarkerPositions) => {
|
||||
if (
|
||||
!onLineMarkerPositionChanged ||
|
||||
!documentRenderPaneRef ||
|
||||
!documentRenderPaneRef.current ||
|
||||
!rendererRef.current
|
||||
) {
|
||||
return
|
||||
}
|
||||
const documentRenderPaneTop = documentRenderPaneRef.current.offsetTop ?? 0
|
||||
const rendererTop = rendererRef.current.offsetTop ?? 0
|
||||
const offset = rendererTop - documentRenderPaneTop
|
||||
onLineMarkerPositionChanged(
|
||||
linkMarkerPositions.map((oldMarker) => ({
|
||||
line: oldMarker.line,
|
||||
position: oldMarker.position + offset
|
||||
}))
|
||||
)
|
||||
},
|
||||
[documentRenderPaneRef, onLineMarkerPositionChanged, rendererRef]
|
||||
)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { RefObject, useCallback, useRef } from 'react'
|
||||
import { IframeEditorToRendererCommunicator } from '../../../render-page/iframe-editor-to-renderer-communicator'
|
||||
|
||||
export const useOnIframeLoad = (
|
||||
frameReference: RefObject<HTMLIFrameElement>,
|
||||
iframeCommunicator: IframeEditorToRendererCommunicator,
|
||||
rendererOrigin: string,
|
||||
renderPageUrl: string,
|
||||
onNavigateAway: () => void
|
||||
): (() => void) => {
|
||||
const sendToRenderPage = useRef<boolean>(true)
|
||||
|
||||
return useCallback(() => {
|
||||
const frame = frameReference.current
|
||||
if (!frame || !frame.contentWindow) {
|
||||
iframeCommunicator.unsetOtherSide()
|
||||
return
|
||||
}
|
||||
|
||||
if (sendToRenderPage.current) {
|
||||
iframeCommunicator.setOtherSide(frame.contentWindow, rendererOrigin)
|
||||
sendToRenderPage.current = false
|
||||
return
|
||||
} else {
|
||||
onNavigateAway()
|
||||
console.error('Navigated away from unknown URL')
|
||||
frame.src = renderPageUrl
|
||||
sendToRenderPage.current = true
|
||||
}
|
||||
}, [frameReference, iframeCommunicator, onNavigateAway, renderPageUrl, rendererOrigin])
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { ImageLightboxModal } from '../../markdown-renderer/replace-components/image/image-lightbox-modal'
|
||||
import { ImageDetails } from '../../render-page/rendering-message'
|
||||
|
||||
export interface ShowOnPropChangeImageLightboxProps {
|
||||
details?: ImageDetails
|
||||
}
|
||||
|
||||
export const ShowOnPropChangeImageLightbox: React.FC<ShowOnPropChangeImageLightboxProps> = ({ details }) => {
|
||||
const [show, setShow] = useState<boolean>(false)
|
||||
|
||||
const hideLightbox = useCallback(() => {
|
||||
setShow(false)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (details) {
|
||||
setShow(true)
|
||||
}
|
||||
}, [details])
|
||||
|
||||
return (
|
||||
<ImageLightboxModal
|
||||
show={show}
|
||||
onHide={hideLightbox}
|
||||
src={details?.src}
|
||||
alt={details?.alt}
|
||||
title={details?.title}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -164,7 +164,7 @@ export const EditorPane: React.FC<EditorPaneProps & ScrollProps> = ({
|
|||
)
|
||||
|
||||
const onCursorActivity = useCallback(
|
||||
(editorWithActivity) => {
|
||||
(editorWithActivity: Editor) => {
|
||||
setStatusBarInfo(createStatusInfo(editorWithActivity, maxLength))
|
||||
},
|
||||
[maxLength]
|
||||
|
|
|
@ -62,15 +62,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-toc-sidebar-button {
|
||||
position: fixed;
|
||||
right: 70px;
|
||||
bottom: 30px;
|
||||
|
||||
& > .dropup {
|
||||
position: sticky;
|
||||
bottom: 20px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ export const IntroPage: React.FC = () => {
|
|||
<RenderIframe
|
||||
frameClasses={'w-100 overflow-y-hidden'}
|
||||
markdownContent={introPageContent as string}
|
||||
disableToc={true}
|
||||
onRendererReadyChange={setRendererReady}
|
||||
rendererType={RendererType.INTRO}
|
||||
forcedDarkMode={true}
|
||||
|
|
|
@ -6,20 +6,18 @@
|
|||
|
||||
import { TocAst } from 'markdown-it-toc-done-right'
|
||||
import React, { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import useResizeObserver from 'use-resize-observer'
|
||||
import { ForkAwesomeIcon } from '../common/fork-awesome/fork-awesome-icon'
|
||||
import { ShowIf } from '../common/show-if/show-if'
|
||||
import { NoteFrontmatter } from '../editor-page/note-frontmatter/note-frontmatter'
|
||||
import { YamlArrayDeprecationAlert } from '../editor-page/renderer-pane/yaml-array-deprecation-alert'
|
||||
import { useSyncedScrolling } from '../editor-page/synced-scroll/hooks/use-synced-scrolling'
|
||||
import { ScrollProps } from '../editor-page/synced-scroll/scroll-props'
|
||||
import { TableOfContents } from '../editor-page/table-of-contents/table-of-contents'
|
||||
import { BasicMarkdownRenderer } from '../markdown-renderer/basic-markdown-renderer'
|
||||
import { ImageClickHandler } from '../markdown-renderer/replace-components/image/image-replacer'
|
||||
import './markdown-document.scss'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../redux'
|
||||
import { WidthBasedTableOfContents } from './width-based-table-of-contents'
|
||||
import { ShowIf } from '../common/show-if/show-if'
|
||||
|
||||
export interface RendererProps extends ScrollProps {
|
||||
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
||||
|
@ -27,15 +25,15 @@ export interface RendererProps extends ScrollProps {
|
|||
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void
|
||||
documentRenderPaneRef?: MutableRefObject<HTMLDivElement | null>
|
||||
markdownContent: string
|
||||
baseUrl?: string
|
||||
onImageClick?: ImageClickHandler
|
||||
onHeightChange?: (height: number) => void
|
||||
disableToc?: boolean
|
||||
}
|
||||
|
||||
export interface MarkdownDocumentProps extends RendererProps {
|
||||
additionalOuterContainerClasses?: string
|
||||
additionalRendererClasses?: string
|
||||
disableToc?: boolean
|
||||
baseUrl: string
|
||||
}
|
||||
|
||||
export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
|
||||
|
@ -105,23 +103,7 @@ export const MarkdownDocument: React.FC<MarkdownDocumentProps> = ({
|
|||
</div>
|
||||
<div className={'markdown-document-side pt-4'}>
|
||||
<ShowIf condition={!!tocAst && !disableToc}>
|
||||
<ShowIf condition={containerWidth >= 1100}>
|
||||
<TableOfContents ast={tocAst as TocAst} className={'sticky'} baseUrl={baseUrl} />
|
||||
</ShowIf>
|
||||
<ShowIf condition={containerWidth < 1100}>
|
||||
<div className={'markdown-toc-sidebar-button'}>
|
||||
<Dropdown drop={'up'}>
|
||||
<Dropdown.Toggle id='toc-overlay-button' variant={'secondary'} className={'no-arrow'}>
|
||||
<ForkAwesomeIcon icon={'list-ol'} />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<div className={'p-2'}>
|
||||
<TableOfContents ast={tocAst as TocAst} baseUrl={baseUrl} />
|
||||
</div>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</ShowIf>
|
||||
<WidthBasedTableOfContents tocAst={tocAst as TocAst} baseUrl={baseUrl} width={containerWidth} />
|
||||
</ShowIf>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
.markdown-toc-sidebar-button {
|
||||
position: fixed;
|
||||
right: 70px;
|
||||
bottom: 30px;
|
||||
|
||||
& > .dropup {
|
||||
position: sticky;
|
||||
bottom: 20px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import { TocAst } from 'markdown-it-toc-done-right'
|
||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||
import { TableOfContents } from '../../editor-page/table-of-contents/table-of-contents'
|
||||
import './markdown-toc-button.scss'
|
||||
|
||||
export interface MarkdownTocButtonProps {
|
||||
tocAst: TocAst
|
||||
baseUrl: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a button that is hovering over the parent and shows a {@link TableOfContents table of contents list} as overlay if clicked.
|
||||
*
|
||||
* @param tocAst the {@link TocAst AST} that should be rendered.
|
||||
* @param baseUrl the base url that will be used to generate the links
|
||||
* @return the created component
|
||||
*/
|
||||
export const TableOfContentsHoveringButton: React.FC<MarkdownTocButtonProps> = ({ tocAst, baseUrl }) => {
|
||||
return (
|
||||
<div className={'markdown-toc-sidebar-button'}>
|
||||
<Dropdown drop={'up'}>
|
||||
<Dropdown.Toggle id='toc-overlay-button' variant={'secondary'} className={'no-arrow'}>
|
||||
<ForkAwesomeIcon icon={'list-ol'} />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<div className={'p-2'}>
|
||||
<TableOfContents ast={tocAst} baseUrl={baseUrl} />
|
||||
</div>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
}
|
35
src/components/render-page/width-based-table-of-contents.tsx
Normal file
35
src/components/render-page/width-based-table-of-contents.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { TocAst } from 'markdown-it-toc-done-right'
|
||||
import { TableOfContents } from '../editor-page/table-of-contents/table-of-contents'
|
||||
import { TableOfContentsHoveringButton } from './markdown-toc-button/table-of-contents-hovering-button'
|
||||
|
||||
export interface DocumentExternalTocProps {
|
||||
tocAst: TocAst
|
||||
width: number
|
||||
baseUrl: string
|
||||
}
|
||||
|
||||
const MAX_WIDTH_FOR_BUTTON_VISIBILITY = 1100
|
||||
|
||||
/**
|
||||
* Renders the {@link TableOfContents table of contents list} for the given {@link TocAst AST}.
|
||||
* If the given width is below {@link MAX_WIDTH_FOR_BUTTON_VISIBILITY the width limit} then a {@link TableOfContentsHoveringButton button} with an overlay will be shown instead.
|
||||
*
|
||||
* @param tocAst the {@link TocAst AST} that should be rendered.
|
||||
* @param width the width that should be used to determine if the button should be shown.
|
||||
* @param baseUrl the base url that will be used to generate the links //TODO: replace with consumer/provider
|
||||
* @return the created component
|
||||
*/
|
||||
export const WidthBasedTableOfContents: React.FC<DocumentExternalTocProps> = ({ tocAst, width, baseUrl }) => {
|
||||
if (width >= MAX_WIDTH_FOR_BUTTON_VISIBILITY) {
|
||||
return <TableOfContents ast={tocAst} className={'sticky'} baseUrl={baseUrl} />
|
||||
} else {
|
||||
return <TableOfContentsHoveringButton tocAst={tocAst} baseUrl={baseUrl} />
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue