mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-06-05 17:14:40 -04:00
Add dark mode (#554)
This commit is contained in:
parent
be2428f22c
commit
44637c753e
80 changed files with 2474 additions and 178 deletions
|
@ -9,7 +9,7 @@ import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
|||
import { ShowIf } from '../../common/show-if/show-if'
|
||||
import { SignInButton } from '../../landing-layout/navigation/sign-in-button'
|
||||
import { UserDropdown } from '../../landing-layout/navigation/user-dropdown'
|
||||
import { SyncScrollButton } from './sync-scroll-button/sync-scroll-button'
|
||||
import { SyncScrollButtons } from './sync-scroll-buttons/sync-scroll-buttons'
|
||||
import { EditorPathParams } from '../editor'
|
||||
import { DarkModeButton } from './dark-mode-button'
|
||||
import { EditorViewMode } from './editor-view-mode'
|
||||
|
@ -26,7 +26,7 @@ export const AppBar: React.FC = () => {
|
|||
<Nav className="mr-auto d-flex align-items-center">
|
||||
<NavbarBranding/>
|
||||
<EditorViewMode/>
|
||||
<SyncScrollButton/>
|
||||
<SyncScrollButtons/>
|
||||
<DarkModeButton/>
|
||||
<Link to={`/p/${id}`} target='_blank'>
|
||||
<Button title={t('editor.documentBar.slideMode')} className="ml-2 text-secondary" size="sm" variant="outline-light">
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../redux'
|
||||
import { setDarkMode } from '../../../redux/dark-mode/methods'
|
||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||
|
||||
const DarkModeButton: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [buttonState, setButtonState] = useState(false)
|
||||
const buttonToggle = () => {
|
||||
setButtonState(prevState => !prevState)
|
||||
}
|
||||
const darkModeEnabled = useSelector((state: ApplicationState) => state.darkMode.darkMode)
|
||||
|
||||
return (
|
||||
<ToggleButtonGroup type="checkbox" defaultValue={[]} name="dark-mode" className="ml-2" value={buttonState ? ['dark'] : ['']}>
|
||||
<ToggleButton
|
||||
title={ buttonState ? t('editor.darkMode.switchToLight') : t('editor.darkMode.switchToDark')}
|
||||
variant={ buttonState ? 'secondary' : 'light' }
|
||||
className={ buttonState ? 'text-white' : 'text-secondary' }
|
||||
onChange={buttonToggle} value={'dark'}
|
||||
>
|
||||
{buttonState
|
||||
? <ForkAwesomeIcon icon="sun"/>
|
||||
: <ForkAwesomeIcon icon="moon"/>
|
||||
}
|
||||
<ToggleButtonGroup
|
||||
type="radio"
|
||||
name="dark-mode"
|
||||
value={darkModeEnabled}
|
||||
className="ml-2"
|
||||
onChange={(value: boolean) => {
|
||||
setDarkMode(value)
|
||||
}}>
|
||||
<ToggleButton value={true} variant="outline-secondary" title={t('editor.darkMode.switchToDark')}>
|
||||
<ForkAwesomeIcon icon="moon"/>
|
||||
</ToggleButton>
|
||||
<ToggleButton value={false} variant="outline-secondary" title={t('editor.darkMode.switchToLight')}>
|
||||
<ForkAwesomeIcon icon="sun-o"/>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
)
|
||||
|
|
|
@ -40,30 +40,24 @@ export const HelpButton: React.FC = () => {
|
|||
<ForkAwesomeIcon icon="question-circle"/> <Trans i18nKey={'editor.documentBar.help'}/> – <Trans i18nKey={`editor.help.${tab}`}/>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body className="text-dark">
|
||||
<ul className='nav nav-tabs'>
|
||||
<li className='nav-item'>
|
||||
<button className={`nav-link ${tab === HelpTabStatus.Cheatsheet ? 'active' : ''}`}
|
||||
onClick={() => setTab(HelpTabStatus.Cheatsheet)}
|
||||
>
|
||||
<Trans i18nKey={'editor.help.cheatsheet.title'}/>
|
||||
</button>
|
||||
</li>
|
||||
<li className='nav-item'>
|
||||
<button className={`nav-link ${tab === HelpTabStatus.Shortcuts ? 'active' : ''}`}
|
||||
onClick={() => setTab(HelpTabStatus.Shortcuts)}
|
||||
>
|
||||
<Trans i18nKey={'editor.help.shortcuts.title'}/>
|
||||
</button>
|
||||
</li>
|
||||
<li className='nav-item'>
|
||||
<button className={`nav-link ${tab === HelpTabStatus.Links ? 'active' : ''}`}
|
||||
onClick={() => setTab(HelpTabStatus.Links)}
|
||||
>
|
||||
<Trans i18nKey={'editor.help.links.title'}/>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<Modal.Body>
|
||||
<nav className='nav nav-tabs'>
|
||||
<Button variant={'light'} className={`nav-link nav-item ${tab === HelpTabStatus.Cheatsheet ? 'active' : ''}`}
|
||||
onClick={() => setTab(HelpTabStatus.Cheatsheet)}
|
||||
>
|
||||
<Trans i18nKey={'editor.help.cheatsheet.title'}/>
|
||||
</Button>
|
||||
<Button variant={'light'} className={`nav-link nav-item ${tab === HelpTabStatus.Shortcuts ? 'active' : ''}`}
|
||||
onClick={() => setTab(HelpTabStatus.Shortcuts)}
|
||||
>
|
||||
<Trans i18nKey={'editor.help.shortcuts.title'}/>
|
||||
</Button>
|
||||
<Button variant={'light'} className={`nav-link nav-item ${tab === HelpTabStatus.Links ? 'active' : ''}`}
|
||||
onClick={() => setTab(HelpTabStatus.Links)}
|
||||
>
|
||||
<Trans i18nKey={'editor.help.links.title'}/>
|
||||
</Button>
|
||||
</nav>
|
||||
{tabContent()}
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Card, ListGroup, Row } from 'react-bootstrap'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { isMac } from '../../utils'
|
||||
|
@ -25,14 +25,19 @@ export const Shortcut: React.FC = () => {
|
|||
<Row className={'justify-content-center pt-4'}>
|
||||
{Object.keys(shortcutMap).map(category => {
|
||||
return (
|
||||
<Card className={'m-2 w-50'}>
|
||||
<Card key={category} className={'m-2 w-50'}>
|
||||
<Card.Header>{category}</Card.Header>
|
||||
<ListGroup variant="flush">
|
||||
{Object.entries(shortcutMap[category]).map(([functionName, shortcut]) => {
|
||||
{Object.entries(shortcutMap[category]).map(([functionName, shortcuts]) => {
|
||||
return (
|
||||
<ListGroup.Item key={functionName} className={'d-flex justify-content-between'}>
|
||||
<span><Trans i18nKey={functionName}/></span>
|
||||
<span>{shortcut}</span>
|
||||
<span>
|
||||
{
|
||||
shortcuts.map((shortcut, shortcutIndex) =>
|
||||
<Fragment key={shortcutIndex}>{shortcut}</Fragment>)
|
||||
}
|
||||
</span>
|
||||
</ListGroup.Item>
|
||||
)
|
||||
})}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import React, { useCallback } from 'react'
|
||||
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../redux'
|
||||
import { setEditorSyncScroll } from '../../../../redux/editor/methods'
|
||||
import disabledScroll from './disabledScroll.svg'
|
||||
import enabledScroll from './enabledScroll.svg'
|
||||
|
||||
export const SyncScrollButton: React.FC = () => {
|
||||
const syncScroll: boolean = useSelector((state: ApplicationState) => state.editorConfig.syncScroll)
|
||||
const translation = syncScroll ? 'editor.appBar.syncScroll.enable' : 'editor.appBar.syncScroll.disable'
|
||||
const onClick = useCallback(() => {
|
||||
setEditorSyncScroll(!syncScroll)
|
||||
}, [syncScroll])
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<ToggleButtonGroup type="checkbox" defaultValue={[]} name="sync-scroll" className="ml-2" value={[syncScroll]}>
|
||||
<ToggleButton
|
||||
title={ t(translation) }
|
||||
variant={syncScroll ? 'secondary' : 'light'}
|
||||
onChange={onClick} value={true}
|
||||
>
|
||||
<img src={syncScroll ? disabledScroll : enabledScroll} width={'20px'} alt={t(translation)}/>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
)
|
||||
}
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
|
@ -0,0 +1,33 @@
|
|||
.sync-scroll-buttons {
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.btn {
|
||||
svg g {
|
||||
@import "../../../../style/light.scss";
|
||||
fill: $secondary;
|
||||
}
|
||||
|
||||
&.active, &:hover {
|
||||
svg g {
|
||||
@import "../../../../style/light.scss";
|
||||
fill: $light;
|
||||
}
|
||||
}
|
||||
|
||||
body.dark & {
|
||||
svg g {
|
||||
@import "../../../../style/dark.scss";
|
||||
fill: $secondary;
|
||||
}
|
||||
|
||||
&.active, &:hover {
|
||||
svg g {
|
||||
@import "../../../../style/dark.scss";
|
||||
fill: $light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react'
|
||||
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../redux'
|
||||
import { setEditorSyncScroll } from '../../../../redux/editor/methods'
|
||||
import { ReactComponent as DisabledScrollIcon } from './disabledScroll.svg'
|
||||
import { ReactComponent as EnabledScrollIcon } from './enabledScroll.svg'
|
||||
import './sync-scroll-buttons.scss'
|
||||
|
||||
export const SyncScrollButtons: React.FC = () => {
|
||||
const syncScroll: boolean = useSelector((state: ApplicationState) => state.editorConfig.syncScroll)
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<ToggleButtonGroup type="radio" defaultValue={[]} name="sync-scroll" className={'ml-2 sync-scroll-buttons'}
|
||||
value={[syncScroll]}>
|
||||
<ToggleButton
|
||||
variant={'outline-secondary'}
|
||||
title={t('editor.appBar.syncScroll.enable')}
|
||||
onChange={() => setEditorSyncScroll(true)} value={true}
|
||||
>
|
||||
<EnabledScrollIcon/>
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
variant={'outline-secondary'}
|
||||
title={t('editor.appBar.syncScroll.disable')}
|
||||
onChange={() => setEditorSyncScroll(false)} value={false}
|
||||
>
|
||||
<DisabledScrollIcon/>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
.upper-case {
|
||||
text-transform: uppercase;
|
||||
}
|
|
@ -2,14 +2,13 @@ import React from 'react'
|
|||
import { Dropdown } from 'react-bootstrap'
|
||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { ActiveIndicatorStatus } from './active-indicator'
|
||||
import './connection-indicator.scss'
|
||||
import { UserLine } from './user-line'
|
||||
|
||||
const ConnectionIndicator: React.FC = () => {
|
||||
const userOnline = 2
|
||||
return (
|
||||
<Dropdown className="small mx-2" alignRight>
|
||||
<Dropdown.Toggle id="connection-indicator" size="sm" variant="primary" className="upper-case">
|
||||
<Dropdown.Toggle id="connection-indicator" size="sm" variant="primary" className="text-uppercase">
|
||||
<ForkAwesomeIcon icon="users" className={'mr-1'}/> {userOnline} Online
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
|
|
|
@ -2,9 +2,11 @@ import React, { useEffect, useRef, useState } from 'react'
|
|||
import { Alert, Col, ListGroup, Modal, Row, Button } from 'react-bootstrap'
|
||||
import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useParams } from 'react-router'
|
||||
import { getAllRevisions, getRevision, Revision, RevisionListEntry } from '../../../../api/revisions'
|
||||
import { UserResponse } from '../../../../api/users/types'
|
||||
import { ApplicationState } from '../../../../redux'
|
||||
import { CommonModal, CommonModalProps } from '../../../common/modals/common-modal'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
import { RevisionButtonProps } from './revision-button'
|
||||
|
@ -20,6 +22,7 @@ export const RevisionModal: React.FC<CommonModalProps & RevisionButtonProps> = (
|
|||
const [error, setError] = useState(false)
|
||||
const revisionAuthorListMap = useRef(new Map<number, UserResponse[]>())
|
||||
const revisionCacheMap = useRef(new Map<number, Revision>())
|
||||
const darkModeEnabled = useSelector((state: ApplicationState) => state.darkMode.darkMode)
|
||||
const { id } = useParams<{ id: string }>()
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -81,7 +84,7 @@ export const RevisionModal: React.FC<CommonModalProps & RevisionButtonProps> = (
|
|||
newValue={noteContent}
|
||||
splitView={false}
|
||||
compareMethod={DiffMethod.WORDS}
|
||||
useDarkTheme={false}
|
||||
useDarkTheme={darkModeEnabled}
|
||||
/>
|
||||
</ShowIf>
|
||||
</Col>
|
||||
|
|
|
@ -12,8 +12,3 @@
|
|||
font-size: 18px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.btn-toolbar .btn {
|
||||
padding: 0.1875rem 0.5rem;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
.btn-toolbar {
|
||||
border: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
border-top: 1px solid #ededed;
|
||||
|
||||
.btn {
|
||||
padding: 0.1875rem 0.5rem;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.btn-group:not(:last-of-type)::after {
|
||||
background-color: #e2e6ea;
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useSelector } from 'react-redux'
|
|||
import useMedia from 'use-media'
|
||||
import { ApplicationState } from '../../redux'
|
||||
import { setEditorMode } from '../../redux/editor/methods'
|
||||
import { ApplyDarkMode } from '../common/apply-dark-mode/apply-dark-mode'
|
||||
import { DocumentTitle } from '../common/document-title/document-title'
|
||||
import { MotdBanner } from '../common/motd-banner/motd-banner'
|
||||
import { AppBar } from './app-bar/app-bar'
|
||||
|
@ -109,6 +110,7 @@ export const Editor: React.FC = () => {
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
<ApplyDarkMode/>
|
||||
<MotdBanner/>
|
||||
<DocumentTitle title={documentTitle}/>
|
||||
<div className={'d-flex flex-column vh-100'}>
|
||||
|
|
|
@ -119,8 +119,21 @@ https://asciinema.org/a/117928
|
|||
|
||||
## Code highlighting
|
||||
\`\`\`javascript=
|
||||
|
||||
let a = 1
|
||||
var s = "JavaScript syntax highlighting";
|
||||
alert(s);
|
||||
function $initHighlight(block, cls) {
|
||||
try {
|
||||
if (cls.search(/\\bno\\-highlight\\b/) != -1)
|
||||
return process(block, true, 0x0F) +
|
||||
' class=""';
|
||||
} catch (e) {
|
||||
/* handle exception */
|
||||
}
|
||||
for (var i = 0 / 2; i < classes.length; i++) {
|
||||
if (checkCondition(classes[i]) === undefined)
|
||||
return /\\d+[\\s/]/g;
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
## PlantUML
|
||||
|
|
|
@ -4,4 +4,9 @@
|
|||
z-index: 1;
|
||||
cursor: col-resize;
|
||||
box-shadow: 3px 0 6px #e7e7e7;
|
||||
|
||||
body.dark & {
|
||||
box-shadow: 3px 0 6px #7b7b7b;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,19 +3,22 @@
|
|||
max-width: 200px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
&.sticky {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
>ul>li {
|
||||
>a {
|
||||
> ul > li {
|
||||
> a {
|
||||
padding: 4px 20px;
|
||||
}
|
||||
>ul>li {
|
||||
|
||||
> ul > li {
|
||||
> a {
|
||||
padding: 1px 0 1px 30px;
|
||||
}
|
||||
>ul>li {
|
||||
|
||||
> ul > li {
|
||||
> a {
|
||||
padding: 1px 0 1px 38px;
|
||||
}
|
||||
|
@ -49,14 +52,15 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.markdown-toc-sidebar-button {
|
||||
position: fixed;
|
||||
right: 40px;
|
||||
bottom: 30px;
|
||||
|
||||
&>.dropup {
|
||||
position: sticky;
|
||||
bottom: 20px;
|
||||
right: 0;
|
||||
}
|
||||
.markdown-toc-sidebar-button {
|
||||
position: fixed;
|
||||
right: 40px;
|
||||
bottom: 30px;
|
||||
|
||||
& > .dropup {
|
||||
position: sticky;
|
||||
bottom: 20px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue