Add dark mode (#554)

This commit is contained in:
Philip Molares 2020-09-13 18:04:02 +02:00 committed by GitHub
parent be2428f22c
commit 44637c753e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 2474 additions and 178 deletions

View file

@ -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">

View file

@ -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>
)

View file

@ -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>

View file

@ -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>
)
})}

View file

@ -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>
)
}

View file

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

View file

@ -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;
}
}
}
}
}

View file

@ -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>
)
}

View file

@ -1,3 +0,0 @@
.upper-case {
text-transform: uppercase;
}

View file

@ -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>

View file

@ -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>

View file

@ -12,8 +12,3 @@
font-size: 18px;
height: 100%;
}
.btn-toolbar .btn {
padding: 0.1875rem 0.5rem;
min-width: 30px;
}

View file

@ -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;

View file

@ -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'}>

View file

@ -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

View file

@ -4,4 +4,9 @@
z-index: 1;
cursor: col-resize;
box-shadow: 3px 0 6px #e7e7e7;
body.dark & {
box-shadow: 3px 0 6px #7b7b7b;
}
}

View file

@ -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;
}
}