mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-06-04 00:48:51 -04:00
Move and rename files (2/4) (#987)
Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
This commit is contained in:
parent
1b7abf9f27
commit
123f959fb3
145 changed files with 586 additions and 301 deletions
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { DeletionModal } from '../../common/modals/deletion-modal'
|
||||
import { SidebarButton } from './sidebar-button'
|
||||
import { SpecificSidebarEntryProps } from './types'
|
||||
|
||||
export const DeleteNoteSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ hide, className }) => {
|
||||
useTranslation()
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<SidebarButton icon={"trash"} className={className} hide={hide} onClick={() => setShowDialog(true)}>
|
||||
<Trans i18nKey={'landing.history.menu.deleteNote'}/>
|
||||
</SidebarButton>
|
||||
<DeletionModal
|
||||
onConfirm={() => setShowDialog(false)}
|
||||
deletionButtonI18nKey={'editor.modal.deleteNote.button'}
|
||||
show={showDialog}
|
||||
onHide={() => setShowDialog(false)}
|
||||
titleI18nKey={'editor.modal.deleteNote.title'}>
|
||||
<h5><Trans i18nKey={'editor.modal.deleteNote.question'}/></h5>
|
||||
<ul>
|
||||
<li> noteTitle</li>
|
||||
</ul>
|
||||
<h6>
|
||||
<Trans i18nKey={'editor.modal.deleteNote.warning'}/>
|
||||
</h6>
|
||||
</DeletionModal>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { DocumentInfoModal } from '../document-bar/document-info/document-info-modal'
|
||||
import { SidebarButton } from './sidebar-button'
|
||||
import { SpecificSidebarEntryProps } from './types'
|
||||
|
||||
export const DocumentInfoSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({className, hide}) => {
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
useTranslation()
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<SidebarButton hide={hide} className={className} icon={"line-chart"} onClick={() => setShowModal(true)}>
|
||||
<Trans i18nKey={'editor.modal.documentInfo.title'} />
|
||||
</SidebarButton>
|
||||
<DocumentInfoModal show={showModal} onHide={() => setShowModal(false)}/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useNoteMarkdownContent } from '../../../hooks/common/use-note-markdown-content'
|
||||
import { download } from '../../common/download/download'
|
||||
import { SidebarButton } from './sidebar-button'
|
||||
|
||||
export const ExportMarkdownSidebarEntry: React.FC = () => {
|
||||
useTranslation()
|
||||
|
||||
const markdownContent = useNoteMarkdownContent()
|
||||
const onClick = useCallback(() => {
|
||||
download(markdownContent, `title.md`, 'text/markdown') //todo: replace hard coded title with redux
|
||||
}, [markdownContent])
|
||||
|
||||
return (
|
||||
<SidebarButton data-cy={"menu-export-markdown"} onClick={onClick} icon={'file-text'}>
|
||||
<Trans i18nKey={'editor.export.markdown-file'}/>
|
||||
</SidebarButton>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useCallback } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import links from '../../../links.json'
|
||||
import { ExportMarkdownSidebarEntry } from './export-markdown-sidebar-entry'
|
||||
import { SidebarButton } from './sidebar-button'
|
||||
import { SidebarMenu } from './sidebar-menu'
|
||||
import { DocumentSidebarMenuSelection, SpecificSidebarMenuProps } from './types'
|
||||
|
||||
export const ExportMenuSidebarMenu: React.FC<SpecificSidebarMenuProps> = (
|
||||
{
|
||||
className,
|
||||
menuId,
|
||||
onClick,
|
||||
selectedMenuId
|
||||
}) => {
|
||||
useTranslation()
|
||||
|
||||
const hide = selectedMenuId !== DocumentSidebarMenuSelection.NONE && selectedMenuId !== menuId
|
||||
const expand = selectedMenuId === menuId
|
||||
const onClickHandler = useCallback(() => {
|
||||
onClick(menuId)
|
||||
}, [menuId, onClick])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<SidebarButton data-cy={"menu-export"} hide={hide} icon={expand ? "arrow-left" : "cloud-download"}
|
||||
className={className} onClick={onClickHandler}>
|
||||
<Trans i18nKey={'editor.documentBar.export'}/>
|
||||
</SidebarButton>
|
||||
<SidebarMenu expand={expand}>
|
||||
<SidebarButton icon={"github"}>
|
||||
Gist
|
||||
</SidebarButton>
|
||||
<SidebarButton icon={"gitlab"}>
|
||||
Gitlab Snippet
|
||||
</SidebarButton>
|
||||
|
||||
<ExportMarkdownSidebarEntry/>
|
||||
|
||||
<SidebarButton icon={"file-code-o"}>
|
||||
HTML
|
||||
</SidebarButton>
|
||||
<SidebarButton icon={"file-code-o"}>
|
||||
<Trans i18nKey='editor.export.rawHtml'/>
|
||||
</SidebarButton>
|
||||
<SidebarButton icon={"file-pdf-o"}>
|
||||
<a className='small text-muted' dir={'auto'} href={links.faq} target={'_blank'} rel='noopener noreferrer'>
|
||||
<Trans i18nKey={'editor.export.pdf'}/>
|
||||
|
||||
<span className={'text-primary'}>
|
||||
<Trans i18nKey={'common.why'}/>
|
||||
</span>
|
||||
</a>
|
||||
</SidebarButton>
|
||||
</SidebarMenu>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useCallback, useRef } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useNoteMarkdownContent } from '../../../hooks/common/use-note-markdown-content'
|
||||
import { setNoteMarkdownContent } from '../../../redux/note-details/methods'
|
||||
import { SidebarButton } from './sidebar-button'
|
||||
import { UploadInput } from './upload-input'
|
||||
|
||||
export const ImportMarkdownSidebarEntry: React.FC = () => {
|
||||
const markdownContent = useNoteMarkdownContent()
|
||||
useTranslation()
|
||||
|
||||
const onImportMarkdown = useCallback((file: File) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const fileReader = new FileReader()
|
||||
fileReader.addEventListener('load', () => {
|
||||
const newContent = fileReader.result as string
|
||||
setNoteMarkdownContent(markdownContent.length === 0 ? newContent : `${markdownContent}\n${newContent}`)
|
||||
})
|
||||
fileReader.addEventListener('loadend', () => {
|
||||
resolve()
|
||||
})
|
||||
fileReader.addEventListener('error', (error) => {
|
||||
reject(error)
|
||||
})
|
||||
fileReader.readAsText(file)
|
||||
})
|
||||
}, [markdownContent])
|
||||
|
||||
const clickRef = useRef<(() => void)>()
|
||||
const buttonClick = useCallback(() => {
|
||||
clickRef.current?.()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<SidebarButton data-cy={"menu-import-markdown"} icon={"file-text-o"} onClick={buttonClick}>
|
||||
<Trans i18nKey={'editor.import.file'}/>
|
||||
</SidebarButton>
|
||||
<UploadInput onLoad={onImportMarkdown} data-cy={"menu-import-markdown-input"}
|
||||
acceptedFiles={'.md, text/markdown, text/plain'} onClickRef={clickRef}/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useCallback } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { ImportMarkdownSidebarEntry } from './import-markdown-sidebar-entry'
|
||||
import { SidebarButton } from './sidebar-button'
|
||||
import { SidebarMenu } from './sidebar-menu'
|
||||
import { DocumentSidebarMenuSelection, SpecificSidebarMenuProps } from './types'
|
||||
|
||||
export const ImportMenuSidebarMenu: React.FC<SpecificSidebarMenuProps> = (
|
||||
{
|
||||
className,
|
||||
menuId,
|
||||
onClick,
|
||||
selectedMenuId
|
||||
}) => {
|
||||
|
||||
useTranslation()
|
||||
|
||||
const hide = selectedMenuId !== DocumentSidebarMenuSelection.NONE && selectedMenuId !== menuId
|
||||
const expand = selectedMenuId === menuId
|
||||
const onClickHandler = useCallback(() => {
|
||||
onClick(menuId)
|
||||
}, [menuId, onClick])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<SidebarButton data-cy={"menu-import"} hide={hide} icon={expand ? "arrow-left" : "cloud-upload"}
|
||||
className={className} onClick={onClickHandler}>
|
||||
<Trans i18nKey={'editor.documentBar.import'}/>
|
||||
</SidebarButton>
|
||||
<SidebarMenu expand={expand}>
|
||||
<SidebarButton icon={"github"}>
|
||||
Gist
|
||||
</SidebarButton>
|
||||
<SidebarButton icon={"gitlab"}>
|
||||
Gitlab Snippet
|
||||
</SidebarButton>
|
||||
<SidebarButton icon={"clipboard"}>
|
||||
<Trans i18nKey={'editor.import.clipboard'}/>
|
||||
</SidebarButton>
|
||||
<ImportMarkdownSidebarEntry/>
|
||||
</SidebarMenu>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { PermissionModal } from '../document-bar/permissions/permission-modal'
|
||||
import { SidebarButton } from './sidebar-button'
|
||||
import { SpecificSidebarEntryProps } from './types'
|
||||
|
||||
export const PermissionsSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({className, hide}) => {
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
useTranslation()
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<SidebarButton hide={hide} className={className} icon={"lock"} onClick={() => setShowModal(true)}>
|
||||
<Trans i18nKey={'editor.modal.permissions.title'}/>
|
||||
</SidebarButton>
|
||||
<PermissionModal show={showModal} onHide={() => setShowModal(false)}/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { SidebarButton } from './sidebar-button'
|
||||
import { SpecificSidebarEntryProps } from './types'
|
||||
|
||||
export const PinNoteSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({ className, hide }) => {
|
||||
useTranslation()
|
||||
|
||||
const isPinned = true
|
||||
const i18nKey = isPinned ? 'editor.documentBar.pinNoteToHistory' : 'editor.documentBar.pinnedToHistory'
|
||||
|
||||
return (
|
||||
<SidebarButton icon={'thumb-tack'} className={className} hide={hide}>
|
||||
<Trans i18nKey={i18nKey}/>
|
||||
</SidebarButton>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { RevisionModal } from '../document-bar/revisions/revision-modal'
|
||||
import { SidebarButton } from './sidebar-button'
|
||||
import { SpecificSidebarEntryProps } from './types'
|
||||
|
||||
export const RevisionSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({className, hide}) => {
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<SidebarButton hide={hide} className={className} icon={"history"} onClick={() => setShowModal(true)}>
|
||||
<Trans i18nKey={'editor.modal.revision.title'}/>
|
||||
</SidebarButton>
|
||||
<RevisionModal show={showModal} onHide={() => setShowModal(false)}/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
25
src/components/editor-page/sidebar/share-sidebar-entry.tsx
Normal file
25
src/components/editor-page/sidebar/share-sidebar-entry.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { ShareModal } from '../document-bar/share/share-modal'
|
||||
import { SidebarButton } from './sidebar-button'
|
||||
import { SpecificSidebarEntryProps } from './types'
|
||||
|
||||
export const ShareSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({className, hide}) => {
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
useTranslation()
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<SidebarButton hide={hide} className={className} icon={"share"} onClick={() => setShowModal(true)}>
|
||||
<Trans i18nKey={'editor.modal.shareLink.title'}/>
|
||||
</SidebarButton>
|
||||
<ShareModal show={showModal} onHide={() => setShowModal(false)}/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
29
src/components/editor-page/sidebar/sidebar-button.tsx
Normal file
29
src/components/editor-page/sidebar/sidebar-button.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||
import { IconName } from '../../common/fork-awesome/types'
|
||||
import { ShowIf } from '../../common/show-if/show-if'
|
||||
import { SidebarEntryProps } from './types'
|
||||
|
||||
export type SidebarEntryVariant = "primary"
|
||||
|
||||
export const SidebarButton: React.FC<SidebarEntryProps> = ({children, icon, className, variant,buttonRef, hide, ...props }) => {
|
||||
return (
|
||||
<button ref={buttonRef}
|
||||
className={`sidebar-entry ${hide ? 'hide' : ''} ${variant ? `sidebar-entry-${variant}` : ''} ${className ?? ''}`} {...props} >
|
||||
<ShowIf condition={!!icon}>
|
||||
<span className={'sidebar-icon'}>
|
||||
<ForkAwesomeIcon icon={icon as IconName}/>
|
||||
</span>
|
||||
</ShowIf>
|
||||
<span className={'sidebar-text'}>
|
||||
{children}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
18
src/components/editor-page/sidebar/sidebar-menu.tsx
Normal file
18
src/components/editor-page/sidebar/sidebar-menu.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { SidebarMenuProps } from './types'
|
||||
|
||||
export const SidebarMenu: React.FC<SidebarMenuProps> = ({children, expand}) => {
|
||||
return (
|
||||
<div className={`sidebar-menu ${expand ? 'show' : ''}`}>
|
||||
<div className={`d-flex flex-column`}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
55
src/components/editor-page/sidebar/sidebar.tsx
Normal file
55
src/components/editor-page/sidebar/sidebar.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { useClickAway } from 'react-use'
|
||||
import { DeleteNoteSidebarEntry } from './delete-note-sidebar-entry'
|
||||
import { DocumentInfoSidebarEntry } from './document-info-sidebar-entry'
|
||||
import { ExportMenuSidebarMenu } from './export-menu-sidebar-menu'
|
||||
import { ImportMenuSidebarMenu } from './import-menu-sidebar-menu'
|
||||
import { PermissionsSidebarEntry } from './permissions-sidebar-entry'
|
||||
import { PinNoteSidebarEntry } from './pin-note-sidebar-entry'
|
||||
import { RevisionSidebarEntry } from './revision-sidebar-entry'
|
||||
import { ShareSidebarEntry } from './share-sidebar-entry'
|
||||
import "./style/theme.scss"
|
||||
import { DocumentSidebarMenuSelection } from './types'
|
||||
import { UsersOnlineSidebarMenu } from './users-online-sidebar-menu/users-online-sidebar-menu'
|
||||
|
||||
export const Sidebar: React.FC = () => {
|
||||
|
||||
const sideBarRef = useRef<HTMLDivElement>(null)
|
||||
const [selectedMenu, setSelectedMenu] = useState<DocumentSidebarMenuSelection>(DocumentSidebarMenuSelection.NONE)
|
||||
|
||||
useClickAway(sideBarRef, () => {
|
||||
setSelectedMenu(DocumentSidebarMenuSelection.NONE)
|
||||
})
|
||||
|
||||
const toggleValue = useCallback((toggleValue: DocumentSidebarMenuSelection): void => {
|
||||
const newValue = selectedMenu === toggleValue ? DocumentSidebarMenuSelection.NONE : toggleValue
|
||||
setSelectedMenu(newValue)
|
||||
}, [selectedMenu])
|
||||
|
||||
const selectionIsNotNone = selectedMenu !== DocumentSidebarMenuSelection.NONE
|
||||
|
||||
return (
|
||||
<div className="slide-sidebar">
|
||||
<div ref={sideBarRef} className={`sidebar-inner ${selectionIsNotNone ? 'show' : ''}`}>
|
||||
<UsersOnlineSidebarMenu menuId={DocumentSidebarMenuSelection.USERS_ONLINE}
|
||||
selectedMenuId={selectedMenu} onClick={toggleValue}/>
|
||||
<DocumentInfoSidebarEntry hide={selectionIsNotNone}/>
|
||||
<RevisionSidebarEntry hide={selectionIsNotNone}/>
|
||||
<PermissionsSidebarEntry hide={selectionIsNotNone}/>
|
||||
<ImportMenuSidebarMenu menuId={DocumentSidebarMenuSelection.IMPORT}
|
||||
selectedMenuId={selectedMenu} onClick={toggleValue}/>
|
||||
<ExportMenuSidebarMenu menuId={DocumentSidebarMenuSelection.EXPORT}
|
||||
selectedMenuId={selectedMenu} onClick={toggleValue}/>
|
||||
<ShareSidebarEntry hide={selectionIsNotNone}/>
|
||||
<DeleteNoteSidebarEntry hide={selectionIsNotNone}/>
|
||||
<PinNoteSidebarEntry hide={selectionIsNotNone}/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
49
src/components/editor-page/sidebar/style/colors.scss
Normal file
49
src/components/editor-page/sidebar/style/colors.scss
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
.slide-sidebar {
|
||||
background: $body-bg;
|
||||
|
||||
.sidebar-menu>div {
|
||||
background: $sidebar-menu-bg;
|
||||
box-shadow: inset 0 7px 7px -6px $sidebar-menu-shadow;
|
||||
}
|
||||
|
||||
.sidebar-inner {
|
||||
background: $body-bg;
|
||||
}
|
||||
|
||||
.sidebar-entry {
|
||||
color: $dark;
|
||||
|
||||
&:hover {
|
||||
background: $entry-hover-bg;
|
||||
color: $dark;
|
||||
}
|
||||
|
||||
&.sidebar-entry-primary {
|
||||
background: $primary;
|
||||
color: $white;
|
||||
|
||||
&:hover {
|
||||
color: $primary;
|
||||
background: $entry-hover-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin text-stroke($shadow-color) {
|
||||
text-shadow: 0px 0px 1px $shadow-color,
|
||||
1px 0px 1px $shadow-color,
|
||||
0px 1px 1px $shadow-color,
|
||||
-1px 0px 1px $shadow-color,
|
||||
0px -1px 1px $shadow-color,
|
||||
1px 1px 1px $shadow-color,
|
||||
-1px -1px 1px $shadow-color,
|
||||
-1px 1px 1px $shadow-color,
|
||||
1px -1px 1px $shadow-color;
|
||||
}
|
100
src/components/editor-page/sidebar/style/layout.scss
Normal file
100
src/components/editor-page/sidebar/style/layout.scss
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
||||
$height: 40px;
|
||||
$menu-width: 175px;
|
||||
|
||||
.slide-sidebar {
|
||||
flex: 0 0 $height;
|
||||
position: relative;
|
||||
|
||||
.sidebar-inner {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow-y: auto;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
width: $menu-width;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transition: left 0.3s;
|
||||
box-shadow: 0 0 0px rgba(0, 0, 0, 0.15);
|
||||
|
||||
&:hover, &.show {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
|
||||
left: (-$menu-width + $height);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
transition: height 0.2s, flex-basis 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
flex: 0 1 0;
|
||||
height: 0;
|
||||
|
||||
&.show {
|
||||
height: 100%;
|
||||
flex-basis: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-entry {
|
||||
height: $height;
|
||||
flex: 0 0 $height;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: solid 1px rgba(0, 0, 0, 0.15);
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
transition: height 0.2s, flex-basis 0.2s;
|
||||
overflow: hidden;
|
||||
|
||||
&.hide {
|
||||
flex-basis: 0;
|
||||
height: 0px;
|
||||
border-width: 0px;
|
||||
|
||||
.sidebar-icon {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sidebar-text {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-icon {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
height: $height;
|
||||
width: $height;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex: 0 0 40px;
|
||||
}
|
||||
|
||||
.sidebar-text {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s;
|
||||
text-align: left;
|
||||
flex: 1 1 0;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
}
|
21
src/components/editor-page/sidebar/style/theme.scss
Normal file
21
src/components/editor-page/sidebar/style/theme.scss
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
body.dark {
|
||||
@import "../../../../style/variables.dark";
|
||||
$entry-hover-bg: lighten($body-bg, 10%);
|
||||
$sidebar-menu-bg: $body-bg;
|
||||
$sidebar-menu-shadow: #1d1d1d;
|
||||
@import "colors";
|
||||
}
|
||||
|
||||
@import "../../../../style/variables.light";
|
||||
$entry-hover-bg: darken($body-bg, 10%);
|
||||
$sidebar-menu-bg: $body-bg;
|
||||
$sidebar-menu-shadow: #bbbbbb;
|
||||
@import "colors";
|
||||
|
||||
@import "layout";
|
43
src/components/editor-page/sidebar/types.ts
Normal file
43
src/components/editor-page/sidebar/types.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { RefObject } from 'react'
|
||||
import { IconName } from '../../common/fork-awesome/types'
|
||||
import { SidebarEntryVariant } from './sidebar-button'
|
||||
|
||||
export interface SpecificSidebarEntryProps {
|
||||
className?: string
|
||||
hide?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export interface SidebarEntryProps {
|
||||
icon?: IconName
|
||||
variant?: SidebarEntryVariant
|
||||
buttonRef?: RefObject<HTMLButtonElement>
|
||||
hide?: boolean
|
||||
className?: string
|
||||
onClick?: () => void
|
||||
"data-cy"?: string
|
||||
}
|
||||
|
||||
export interface SidebarMenuProps {
|
||||
expand?: boolean
|
||||
}
|
||||
|
||||
export enum DocumentSidebarMenuSelection {
|
||||
NONE,
|
||||
USERS_ONLINE,
|
||||
IMPORT,
|
||||
EXPORT
|
||||
}
|
||||
|
||||
export interface SpecificSidebarMenuProps {
|
||||
className?: string
|
||||
onClick: (menuId: DocumentSidebarMenuSelection) => void
|
||||
selectedMenuId: DocumentSidebarMenuSelection
|
||||
menuId: DocumentSidebarMenuSelection
|
||||
}
|
44
src/components/editor-page/sidebar/upload-input.tsx
Normal file
44
src/components/editor-page/sidebar/upload-input.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { MutableRefObject, useCallback, useEffect, useRef } from 'react'
|
||||
|
||||
export interface UploadInputProps {
|
||||
onLoad: (file: File) => Promise<void>
|
||||
acceptedFiles: string
|
||||
onClickRef: MutableRefObject<(() => void) | undefined>
|
||||
"data-cy"?: string
|
||||
}
|
||||
|
||||
export const UploadInput: React.FC<UploadInputProps> = ({ onLoad, acceptedFiles, onClickRef, ...props }) => {
|
||||
const fileInputReference = useRef<HTMLInputElement>(null)
|
||||
const onClick = useCallback(() => {
|
||||
const fileInput = fileInputReference.current
|
||||
if (!fileInput) {
|
||||
return
|
||||
}
|
||||
fileInput.addEventListener('change', () => {
|
||||
if (!fileInput.files || fileInput.files.length < 1) {
|
||||
return
|
||||
}
|
||||
const file = fileInput.files[0]
|
||||
onLoad(file).then(() => {
|
||||
fileInput.value = ''
|
||||
}).catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
})
|
||||
fileInput.click()
|
||||
}, [onLoad])
|
||||
|
||||
useEffect(() => {
|
||||
onClickRef.current = onClick
|
||||
})
|
||||
|
||||
return (
|
||||
<input data-cy={props["data-cy"]} type='file' ref={fileInputReference} className='d-none' accept={acceptedFiles}/>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
.activeIndicator {
|
||||
$indicator-size: 12px;
|
||||
border-radius: $indicator-size;
|
||||
height: $indicator-size;
|
||||
width: $indicator-size;
|
||||
|
||||
&.active {
|
||||
background-color: #5cb85c;
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
background-color: #d20000;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import './active-indicator.scss'
|
||||
|
||||
export enum ActiveIndicatorStatus {
|
||||
ACTIVE = 'active',
|
||||
INACTIVE = 'inactive'
|
||||
}
|
||||
|
||||
export interface ActiveIndicatorProps {
|
||||
status: ActiveIndicatorStatus;
|
||||
}
|
||||
|
||||
export const ActiveIndicator: React.FC<ActiveIndicatorProps> = ({ status }) => {
|
||||
return (
|
||||
<span className={`activeIndicator ${status}`}/>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
body.dark {
|
||||
@import "../../../../style/variables.dark";
|
||||
$entry-hover-bg: lighten($body-bg, 10%);
|
||||
$sidebar-menu-bg: $body-bg;
|
||||
$sidebar-menu-shadow: #1d1d1d;
|
||||
@import "../style/colors";
|
||||
}
|
||||
|
||||
@import "../../../../style/variables.light";
|
||||
$entry-hover-bg: darken($body-bg, 10%);
|
||||
$sidebar-menu-bg: $body-bg;
|
||||
$sidebar-menu-shadow: #bbbbbb;
|
||||
@import "../style/colors";
|
||||
|
||||
.slide-sidebar .sidebar-entry.online-entry {
|
||||
&:hover {
|
||||
.sidebar-icon:after {
|
||||
color: $primary;
|
||||
$shadow-color: #ffffff;
|
||||
@include text-stroke(#ffffff);
|
||||
}
|
||||
}
|
||||
|
||||
--users-online: '0';
|
||||
|
||||
.sidebar-icon:after {
|
||||
@include text-stroke($primary);
|
||||
content: var(--users-online);
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
bottom: 3px;
|
||||
font-size: 0.9rem;
|
||||
color: #ffffff;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
.user-line-color-indicator {
|
||||
border-left: 3px solid;
|
||||
min-height: 30px;
|
||||
height: 100%;
|
||||
flex: 0 0 3px;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
flex: 0 0 20px;
|
||||
}
|
||||
|
||||
.user-line-name {
|
||||
text-overflow: ellipsis;
|
||||
flex: 1 1 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.active-indicator-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex: 0 0 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { UserAvatar } from '../../../common/user-avatar/user-avatar'
|
||||
import { ActiveIndicator, ActiveIndicatorStatus } from './active-indicator'
|
||||
import './user-line.scss'
|
||||
|
||||
export interface UserLineProps {
|
||||
name: string;
|
||||
photo: string;
|
||||
color: string;
|
||||
status: ActiveIndicatorStatus;
|
||||
}
|
||||
|
||||
export const UserLine: React.FC<UserLineProps> = ({ name, photo, color, status }) => {
|
||||
return (
|
||||
<div className={'d-flex align-items-center h-100 w-100'}>
|
||||
<div className='d-inline-flex align-items-bottom user-line-color-indicator' style={{ borderLeftColor: color }}/>
|
||||
<UserAvatar photo={photo} name={name} additionalClasses={'flex-fill overflow-hidden px-2 text-nowrap w-100'}/>
|
||||
<div className={"active-indicator-container"}>
|
||||
<ActiveIndicator status={status} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { SidebarButton } from '../sidebar-button'
|
||||
import { SidebarMenu } from '../sidebar-menu'
|
||||
import { DocumentSidebarMenuSelection, SpecificSidebarMenuProps } from '../types'
|
||||
import { ActiveIndicatorStatus } from './active-indicator'
|
||||
import './online-counter.scss'
|
||||
import { UserLine } from './user-line'
|
||||
|
||||
export const UsersOnlineSidebarMenu: React.FC<SpecificSidebarMenuProps> = ({
|
||||
className,
|
||||
menuId,
|
||||
onClick,
|
||||
selectedMenuId
|
||||
}) => {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const [counter] = useState(2)
|
||||
useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
const value = `${counter}`
|
||||
buttonRef.current?.style.setProperty('--users-online', `"${value}"`)
|
||||
}, [counter])
|
||||
|
||||
const hide = selectedMenuId !== DocumentSidebarMenuSelection.NONE && selectedMenuId !== menuId
|
||||
const expand = selectedMenuId === menuId
|
||||
const onClickHandler = useCallback(() => {
|
||||
onClick(menuId)
|
||||
}, [menuId, onClick])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<SidebarButton hide={hide} buttonRef={buttonRef} onClick={onClickHandler} icon={expand ? "arrow-left" : "users"}
|
||||
variant={'primary'} className={`online-entry ${className ?? ''}`}>
|
||||
<Trans i18nKey={'editor.onlineStatus.online'}/>
|
||||
</SidebarButton>
|
||||
<SidebarMenu expand={expand}>
|
||||
<SidebarButton>
|
||||
<UserLine name="Philip Molares" photo="/img/avatar.png" color="red" status={ActiveIndicatorStatus.INACTIVE}/>
|
||||
</SidebarButton>
|
||||
<SidebarButton>
|
||||
<UserLine name="Tilman Vatteroth" photo="/img/avatar.png" color="blue" status={ActiveIndicatorStatus.ACTIVE}/>
|
||||
</SidebarButton>
|
||||
</SidebarMenu>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue