mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-29 06:15:29 -04:00
Restructure repository (#426)
organized repository Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> Co-authored-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> Co-authored-by: Philip Molares <git@molar.es>
This commit is contained in:
parent
66258ca615
commit
0fadc09f2b
254 changed files with 384 additions and 403 deletions
|
@ -1,28 +0,0 @@
|
|||
import React, { Fragment } from 'react'
|
||||
import { ShowIf } from '../show-if/show-if'
|
||||
|
||||
export interface ElementSeparatorProps {
|
||||
separator: React.ReactElement
|
||||
}
|
||||
|
||||
export const ElementSeparator: React.FC<ElementSeparatorProps> = ({ children, separator }) => {
|
||||
return (
|
||||
<Fragment>
|
||||
{
|
||||
React.Children
|
||||
.toArray(children)
|
||||
.filter(child => child !== null)
|
||||
.map((child, index) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<ShowIf condition={index > 0}>
|
||||
{separator}
|
||||
</ShowIf>
|
||||
{child}
|
||||
</Fragment>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
.markdown-body {
|
||||
@import '../../../../node_modules/highlight.js/styles/github-gist';
|
||||
}
|
||||
|
||||
.markdown-body pre code.hljs {
|
||||
|
||||
padding: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
|
||||
&.showGutter {
|
||||
.linenumber {
|
||||
position: relative;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
padding: 0 8px 0 0;
|
||||
min-width: 20px;
|
||||
box-sizing: content-box;
|
||||
color: #afafaf;
|
||||
border-right: 3px solid #6ce26c;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
||||
&:before {
|
||||
content: attr(data-line-number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.showGutter .codeline {
|
||||
margin: 0 0 0 16px;
|
||||
}
|
||||
|
||||
&.wrapLines .codeline {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
import hljs from 'highlight.js'
|
||||
import React, { Fragment, useMemo } from 'react'
|
||||
import ReactHtmlParser from 'react-html-parser'
|
||||
import './highlighted-code.scss'
|
||||
|
||||
export interface HighlightedCodeProps {
|
||||
code: string,
|
||||
language?: string,
|
||||
startLineNumber?: number
|
||||
wrapLines: boolean
|
||||
}
|
||||
|
||||
export const escapeHtml = (unsafe: string): string => {
|
||||
return unsafe
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
const checkIfLanguageIsSupported = (language: string): boolean => {
|
||||
return hljs.listLanguages().indexOf(language) > -1
|
||||
}
|
||||
|
||||
const correctLanguage = (language: string | undefined): string | undefined => {
|
||||
switch (language) {
|
||||
case 'html':
|
||||
return 'xml'
|
||||
default:
|
||||
return language
|
||||
}
|
||||
}
|
||||
|
||||
export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language, startLineNumber, wrapLines }) => {
|
||||
const highlightedCode = useMemo(() => {
|
||||
const replacedLanguage = correctLanguage(language)
|
||||
return ((!!replacedLanguage && checkIfLanguageIsSupported(replacedLanguage)) ? hljs.highlight(replacedLanguage, code).value : escapeHtml(code))
|
||||
.split('\n')
|
||||
.filter(line => !!line)
|
||||
.map(line => ReactHtmlParser(line))
|
||||
}, [code, language])
|
||||
|
||||
return (
|
||||
<code className={`hljs ${startLineNumber !== undefined ? 'showGutter' : ''} ${wrapLines ? 'wrapLines' : ''}`}>
|
||||
{
|
||||
highlightedCode
|
||||
.map((line, index) => {
|
||||
return <Fragment key={index}>
|
||||
<span className={'linenumber'} data-line-number={(startLineNumber || 1) + index}/>
|
||||
<div className={'codeline'}>
|
||||
{line}
|
||||
</div>
|
||||
</Fragment>
|
||||
})
|
||||
}
|
||||
|
||||
</code>)
|
||||
}
|
|
@ -1,11 +1,17 @@
|
|||
.btn-icon {
|
||||
padding: 0.375rem 0.375rem;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
|
||||
&.with-border {
|
||||
.icon-part {
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.text-part {
|
||||
padding: 0.375rem 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-part {
|
||||
padding: 0.375rem 0.375rem;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
|
||||
.social-icon {
|
||||
|
@ -14,7 +20,7 @@
|
|||
}
|
||||
|
||||
.text-part {
|
||||
padding: 0.375rem 0.75rem;
|
||||
padding: 0.375rem 0.75rem 0.375rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,15 +3,15 @@ import { Button, ButtonProps } from 'react-bootstrap'
|
|||
import { ForkAwesomeIcon, IconName } from '../fork-awesome/fork-awesome-icon'
|
||||
import './icon-button.scss'
|
||||
|
||||
export interface SocialButtonProps extends ButtonProps {
|
||||
export interface IconButtonProps extends ButtonProps {
|
||||
icon: IconName
|
||||
onClick?: () => void
|
||||
border?: boolean
|
||||
}
|
||||
|
||||
export const IconButton: React.FC<SocialButtonProps> = ({ icon, children, variant, onClick }) => {
|
||||
export const IconButton: React.FC<IconButtonProps> = ({ icon, children, border = false, ...props }) => {
|
||||
return (
|
||||
<Button variant={variant} className={'btn-icon p-0 d-inline-flex align-items-stretch'}
|
||||
onClick={() => onClick?.()}>
|
||||
<Button {...props} className={`btn-icon p-0 d-inline-flex align-items-stretch ${border ? 'with-border' : ''}`}>
|
||||
<span className="icon-part d-flex align-items-center">
|
||||
<ForkAwesomeIcon icon={icon} className={'icon'}/>
|
||||
</span>
|
||||
|
|
16
src/components/common/icon-button/translated-icon-button.tsx
Normal file
16
src/components/common/icon-button/translated-icon-button.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import './icon-button.scss'
|
||||
import { IconButton, IconButtonProps } from './icon-button'
|
||||
|
||||
export interface TranslatedIconButton extends IconButtonProps {
|
||||
i18nKey: string
|
||||
}
|
||||
|
||||
export const TranslatedIconButton: React.FC<TranslatedIconButton> = ({ i18nKey, ...props }) => {
|
||||
return (
|
||||
<IconButton {...props}>
|
||||
<Trans i18nKey={i18nKey}/>
|
||||
</IconButton>
|
||||
)
|
||||
}
|
34
src/components/common/motd-banner/motd-banner.tsx
Normal file
34
src/components/common/motd-banner/motd-banner.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { ApplicationState } from '../../../redux'
|
||||
import { Alert, Button } from 'react-bootstrap'
|
||||
import { setBanner } from '../../../redux/banner/methods'
|
||||
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
|
||||
import { ShowIf } from '../show-if/show-if'
|
||||
|
||||
export const MotdBanner: React.FC = () => {
|
||||
const bannerState = useSelector((state: ApplicationState) => state.banner)
|
||||
|
||||
const dismissBanner = () => {
|
||||
setBanner({ ...bannerState, show: false })
|
||||
window.localStorage.setItem('bannerTimeStamp', bannerState.timestamp)
|
||||
}
|
||||
|
||||
return (
|
||||
<ShowIf condition={bannerState.show}>
|
||||
<Alert variant='primary' dir='auto' className='mb-0 text-center d-flex flex-row justify-content-center'>
|
||||
<Link to='/s/banner' className='flex-grow-1 align-self-center'>
|
||||
{bannerState.text}
|
||||
</Link>
|
||||
<Button
|
||||
variant='outline-primary'
|
||||
size='sm'
|
||||
className='mx-2'
|
||||
onClick={dismissBanner}>
|
||||
<ForkAwesomeIcon icon='times'/>
|
||||
</Button>
|
||||
</Alert>
|
||||
</ShowIf>
|
||||
)
|
||||
}
|
12
src/components/common/routing/not-found-error-screen.tsx
Normal file
12
src/components/common/routing/not-found-error-screen.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import React from 'react'
|
||||
import { LandingLayout } from '../../landing-layout/landing-layout'
|
||||
|
||||
export const NotFoundErrorScreen: React.FC = () => {
|
||||
return (
|
||||
<LandingLayout>
|
||||
<div className='text-white d-flex align-items-center justify-content-center my-5'>
|
||||
<h1>404 Not Found <small>oops.</small></h1>
|
||||
</div>
|
||||
</LandingLayout>
|
||||
)
|
||||
}
|
28
src/components/common/routing/redirector.tsx
Normal file
28
src/components/common/routing/redirector.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { Redirect } from 'react-router'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { getNote } from '../../../api/notes'
|
||||
import { NotFoundErrorScreen } from './not-found-error-screen'
|
||||
|
||||
interface RouteParameters {
|
||||
id: string
|
||||
}
|
||||
|
||||
export const Redirector: React.FC = () => {
|
||||
const { id } = useParams<RouteParameters>()
|
||||
const [error, setError] = useState<boolean | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
getNote(id)
|
||||
.then((noteFromAPI) => setError(!noteFromAPI.preVersionTwoNote))
|
||||
.catch(() => setError(true))
|
||||
}, [id])
|
||||
|
||||
if (error) {
|
||||
return (<NotFoundErrorScreen/>)
|
||||
} else if (!error && error != null) {
|
||||
return (<Redirect to={`/n/${id}`}/>)
|
||||
} else {
|
||||
return (<span>Loading</span>)
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import React from 'react'
|
||||
import { ButtonProps } from 'react-bootstrap'
|
||||
import { IconName } from '../fork-awesome/fork-awesome-icon'
|
||||
import { IconButton } from '../icon-button/icon-button'
|
||||
|
||||
export enum SortModeEnum {
|
||||
up = 1,
|
||||
down = -1,
|
||||
no = 0
|
||||
}
|
||||
|
||||
const getIcon = (direction: SortModeEnum): IconName => {
|
||||
switch (direction) {
|
||||
default:
|
||||
case SortModeEnum.no:
|
||||
return 'sort'
|
||||
case SortModeEnum.up:
|
||||
return 'sort-asc'
|
||||
case SortModeEnum.down:
|
||||
return 'sort-desc'
|
||||
}
|
||||
}
|
||||
|
||||
export interface SortButtonProps extends ButtonProps {
|
||||
onDirectionChange: (direction: SortModeEnum) => void
|
||||
direction: SortModeEnum
|
||||
}
|
||||
|
||||
const toggleDirection = (direction: SortModeEnum) => {
|
||||
switch (direction) {
|
||||
case SortModeEnum.no:
|
||||
return SortModeEnum.up
|
||||
case SortModeEnum.up:
|
||||
return SortModeEnum.down
|
||||
default:
|
||||
case SortModeEnum.down:
|
||||
return SortModeEnum.no
|
||||
}
|
||||
}
|
||||
|
||||
export const SortButton: React.FC<SortButtonProps> = ({ children, variant, onDirectionChange, direction }) => {
|
||||
const toggleSort = () => {
|
||||
onDirectionChange(toggleDirection(direction))
|
||||
}
|
||||
|
||||
return <IconButton onClick={toggleSort} variant={variant} icon={getIcon(direction)}>{children}</IconButton>
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
.split-divider {
|
||||
width: 10px;
|
||||
background: white;
|
||||
z-index: 1;
|
||||
cursor: col-resize;
|
||||
box-shadow: 3px 0 6px #e7e7e7;
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import React from 'react'
|
||||
import './split-divider.scss'
|
||||
|
||||
export interface SplitDividerProps {
|
||||
onGrab: () => void
|
||||
}
|
||||
|
||||
export const SplitDivider: React.FC<SplitDividerProps> = ({ onGrab }) => {
|
||||
return (
|
||||
<div
|
||||
onMouseDown={() => onGrab()}
|
||||
onTouchStart={() => onGrab()}
|
||||
className={'split-divider'}/>
|
||||
)
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
.splitter {
|
||||
&.left {
|
||||
flex: 0 1 100%;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
&.right {
|
||||
flex: 1 0 200px;
|
||||
}
|
||||
|
||||
&.separator {
|
||||
display: flex;
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
import React, { ReactElement, useRef, useState } from 'react'
|
||||
import { ShowIf } from '../show-if/show-if'
|
||||
import { SplitDivider } from '../split-divider/split-divider'
|
||||
import './splitter.scss'
|
||||
|
||||
export interface SplitterProps {
|
||||
left: ReactElement
|
||||
right: ReactElement
|
||||
containerClassName?: string
|
||||
showLeft: boolean
|
||||
showRight: boolean
|
||||
}
|
||||
|
||||
export const Splitter: React.FC<SplitterProps> = ({ containerClassName, left, right, showLeft, showRight }) => {
|
||||
const [split, setSplit] = useState(50)
|
||||
const realSplit = Math.max(0, Math.min(100, (showRight ? split : 100)))
|
||||
const [doResizing, setDoResizing] = useState(false)
|
||||
const splitContainer = useRef<HTMLDivElement>(null)
|
||||
|
||||
const recalculateSize = (mouseXPosition: number): void => {
|
||||
if (!splitContainer.current) {
|
||||
return
|
||||
}
|
||||
const x = mouseXPosition - splitContainer.current.offsetLeft
|
||||
|
||||
const newSize = x / splitContainer.current.clientWidth
|
||||
setSplit(newSize * 100)
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={splitContainer} className={`flex-fill flex-row d-flex ${containerClassName || ''}`}
|
||||
onMouseUp={() => setDoResizing(false)}
|
||||
onTouchEnd={() => setDoResizing(false)}
|
||||
onMouseMove={(mouseEvent) => {
|
||||
if (doResizing) {
|
||||
recalculateSize(mouseEvent.pageX)
|
||||
mouseEvent.preventDefault()
|
||||
}
|
||||
}}
|
||||
onTouchMove={(touchEvent) => {
|
||||
if (doResizing) {
|
||||
recalculateSize(touchEvent.touches[0].pageX)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ShowIf condition={showLeft}>
|
||||
<div className={'splitter left'} style={{ flexBasis: `calc(${realSplit}% - 5px)` }}>
|
||||
{left}
|
||||
</div>
|
||||
</ShowIf>
|
||||
<ShowIf condition={showLeft && showRight}>
|
||||
<div className='splitter separator'>
|
||||
<SplitDivider onGrab={() => setDoResizing(true)}/>
|
||||
</div>
|
||||
</ShowIf>
|
||||
<ShowIf condition={showRight}>
|
||||
<div className='splitter right'>
|
||||
{right}
|
||||
</div>
|
||||
</ShowIf>
|
||||
</div>
|
||||
)
|
||||
}
|
8
src/components/common/user-avatar/user-avatar.scss
Normal file
8
src/components/common/user-avatar/user-avatar.scss
Normal file
|
@ -0,0 +1,8 @@
|
|||
.user-avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 1rem;
|
||||
}
|
30
src/components/common/user-avatar/user-avatar.tsx
Normal file
30
src/components/common/user-avatar/user-avatar.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ShowIf } from '../show-if/show-if'
|
||||
import './user-avatar.scss'
|
||||
|
||||
export interface UserAvatarProps {
|
||||
name: string;
|
||||
photo: string;
|
||||
additionalClasses?: string;
|
||||
showName?: boolean
|
||||
}
|
||||
|
||||
const UserAvatar: React.FC<UserAvatarProps> = ({ name, photo, additionalClasses = '', showName = true }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<span className={'d-inline-flex align-items-center ' + additionalClasses}>
|
||||
<img
|
||||
src={photo}
|
||||
className="user-avatar rounded"
|
||||
alt={t('common.avatarOf', { name })}
|
||||
/>
|
||||
<ShowIf condition={showName}>
|
||||
<span className="mx-1 user-name">{name}</span>
|
||||
</ShowIf>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export { UserAvatar }
|
Loading…
Add table
Add a link
Reference in a new issue