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

@ -47,7 +47,6 @@
"fast-deep-equal": "3.1.3",
"flowchart.js": "1.14.1",
"fork-awesome": "1.1.7",
"github-markdown-css": "4.0.0",
"highlight.js": "10.2.0",
"i18next": "19.7.0",
"i18next-browser-languagedetector": "6.0.1",

View file

@ -9,8 +9,8 @@ export interface LoadingScreenProps {
export const LoadingScreen: React.FC<LoadingScreenProps> = ({ failedTitle }) => {
return (
<div className="loader middle text-white">
<div className="mb-3 text-white">
<div className="loader middle text-light">
<div className="mb-3 text-light">
<ForkAwesomeIcon icon="file-text" size="5x"
className={failedTitle ? 'animation-shake' : 'animation-pulse'}/>
</div>

View file

@ -0,0 +1,20 @@
import React, { useEffect } from 'react'
import { useSelector } from 'react-redux'
import { ApplicationState } from '../../../redux'
export const ApplyDarkMode: React.FC = () => {
const darkModeActivated = useSelector((state: ApplicationState) => state.darkMode.darkMode)
useEffect(() => {
if (darkModeActivated) {
window.document.body.classList.add('dark')
} else {
window.document.body.classList.remove('dark')
}
return () => {
window.document.body.classList.remove('dark')
}
}, [darkModeActivated])
return null
}

View file

@ -19,7 +19,7 @@ export const MotdBanner: React.FC = () => {
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'>
<Link to='/s/banner' className='flex-grow-1 align-self-center text-black'>
{bannerState.text}
</Link>
<Button

View file

@ -4,7 +4,7 @@ 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'>
<div className='text-light d-flex align-items-center justify-content-center my-5'>
<h1>404 Not Found <small>oops.</small></h1>
</div>
</LandingLayout>

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

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

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

View file

@ -32,8 +32,8 @@ export class ErrorBoundary extends Component {
render (): ReactElement | undefined | null | string | number | boolean | Record<string, unknown> | ReactNodeArray {
if (this.state.hasError) {
return (
<Container className="text-white d-flex flex-column mvh-100">
<div className='text-white d-flex flex-column align-items-center justify-content-center my-5'>
<Container className="text-light d-flex flex-column mvh-100">
<div className='text-light d-flex flex-column align-items-center justify-content-center my-5'>
<h1>An unknown error occurred</h1>
<p>Don't worry, this happens sometimes. If this is the first time you see this page then try reloading the app.</p>
If you can reproduce this error, then we would be glad if you&#32;

View file

@ -14,8 +14,7 @@
}
.dropup .dropdown-toggle, .dropdown-toggle {
&.no-arrow::after {
content: initial;
}
&.no-arrow::after {
content: initial;
}
}

View file

@ -5,7 +5,7 @@ import { SocialLink } from './social-links'
export const Footer: React.FC = () => {
return (
<footer className="text-white-50 small">
<footer className="text-light-50 small">
<LanguagePicker/>
<PoweredByLinks/>
<SocialLink/>

View file

@ -35,7 +35,9 @@ export const VersionInfo: React.FC = () => {
return (
<Fragment>
<Link id='version' to={'#'} className={'text-light'} onClick={handleShow}><Trans i18nKey={'landing.versionInfo.versionInfo'}/></Link>
<Link id='version' to={'#'} className={'text-light'} onClick={handleShow}>
<Trans i18nKey={'landing.versionInfo.versionInfo'}/>
</Link>
<Modal id='versionModal' show={show} onHide={handleClose} animation={true}>
<Modal.Body className="text-dark">
<h3><Trans i18nKey={'landing.versionInfo.title'}/></h3>

View file

@ -7,7 +7,7 @@ import { HeaderBar } from './navigation/header-bar/header-bar'
export const LandingLayout: React.FC = ({ children }) => {
return (
<Container className="text-white d-flex flex-column mvh-100">
<Container className="text-light d-flex flex-column mvh-100">
<DocumentTitle/>
<MotdBanner/>
<HeaderBar/>

View file

@ -35,7 +35,7 @@ export const ViaInternal: React.FC = () => {
type="text"
size="sm"
placeholder={t('login.auth.username')}
onChange={(event) => setUsername(event.currentTarget.value)} className="bg-dark text-white"
onChange={(event) => setUsername(event.currentTarget.value)} className="bg-dark text-light"
autoComplete='username'
/>
</Form.Group>
@ -47,7 +47,7 @@ export const ViaInternal: React.FC = () => {
size="sm"
placeholder={t('login.auth.password')}
onChange={(event) => setPassword(event.currentTarget.value)}
className="bg-dark text-white"
className="bg-dark text-light"
autoComplete='current-password'
/>
</Form.Group>

View file

@ -37,7 +37,7 @@ export const ViaLdap: React.FC = () => {
type="text"
size="sm"
placeholder={t('login.auth.username')}
onChange={(event) => setUsername(event.currentTarget.value)} className="bg-dark text-white"
onChange={(event) => setUsername(event.currentTarget.value)} className="bg-dark text-light"
autoComplete='username'
/>
</Form.Group>
@ -49,7 +49,7 @@ export const ViaLdap: React.FC = () => {
size="sm"
placeholder={t('login.auth.password')}
onChange={(event) => setPassword(event.currentTarget.value)}
className="bg-dark text-white"
className="bg-dark text-light"
autoComplete='current-password'
/>
</Form.Group>

View file

@ -33,7 +33,7 @@ export const ViaOpenId: React.FC = () => {
size="sm"
placeholder={'OpenID'}
onChange={(event) => setOpenId(event.currentTarget.value)}
className="bg-dark text-white"
className="bg-dark text-light"
/>
</Form.Group>

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
@import '../../../node_modules/github-markdown-css/github-markdown.css';
@import './github-markdown';
.markdown-body {
position: relative;

View file

@ -14,6 +14,6 @@ export const AbcFrame: React.FC<AbcFrameProps> = ({ code }) => {
}, [code])
return (
<div ref={container}/>
<div ref={container} className={'bg-white text-center'}/>
)
}

View file

@ -63,7 +63,7 @@ export const CsvTable: React.FC<CsvTableProps> = ({ code, delimiter, showHeader,
}
return (
<table className={'csv-html-table'}>
<table className={'csv-html-table table-striped'}>
{renderTableHeader(headerRow)}
{renderTableBody(rowsWithColumns)}
</table>

View file

@ -1,42 +1,55 @@
.markdown-body {
@import '../../../../../../node_modules/highlight.js/styles/github-gist.css';
}
.markdown-body code.hljs {
.markdown-body {
@import '../../../../../../node_modules/highlight.js/styles/github';
padding: 16px;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
body.dark & {
@import '../../../../../../node_modules/highlight.js/styles/tomorrow-night';
}
&.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;
code.hljs {
background-color: rgba(27, 31, 35, .05);
body.dark & {
background-color: rgb(27, 31, 35);
}
display: flex;
align-items: flex-end;
body.dark &, & {
padding: 16px;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
&:before {
content: attr(data-line-number);
&.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;
}
}
}
&.showGutter .codeline {
margin: 0 0 0 16px;
}
&.wrapLines .codeline {
white-space: pre-wrap;
}
}

View file

@ -11,7 +11,7 @@ export const DeprecationWarning: React.FC = () => {
<Alert className={'mt-2'} variant={'warning'}>
<Trans i18nKey={'renderer.sequence.deprecationWarning'}/>
&nbsp;
<TranslatedExternalLink i18nKey={'common.why'} className={'text-dark'} href={links.faq}/>
<TranslatedExternalLink i18nKey={'common.why'} className={'text-primary'} href={links.faq}/>
</Alert>
)
}

View file

@ -39,7 +39,7 @@ export const ProfileChangePassword: React.FC = () => {
<Form.Control
type="password"
size="sm"
className="bg-dark text-white"
className="bg-dark text-light"
required
onChange={(event) => setOldPassword(event.target.value)}
/>
@ -49,7 +49,7 @@ export const ProfileChangePassword: React.FC = () => {
<Form.Control
type="password"
size="sm"
className="bg-dark text-white"
className="bg-dark text-light"
required
onChange={onChangeNewPassword}
isValid={newPasswordValid}
@ -61,7 +61,7 @@ export const ProfileChangePassword: React.FC = () => {
<Form.Control
type="password"
size="sm"
className="bg-dark text-white"
className="bg-dark text-light"
required
onChange={onChangeNewPasswordAgain}
isValid={newPasswordAgainValid}

View file

@ -53,7 +53,7 @@ export const ProfileDisplayName: React.FC = () => {
size="sm"
placeholder={t('profile.displayName')}
value={displayName}
className="bg-dark text-white"
className="bg-dark text-light"
onChange={changeNameField}
isValid={submittable}
isInvalid={error}

View file

@ -70,7 +70,7 @@ export const RegisterPage: React.FC = () => {
isValid={username !== ''}
onChange={(event) => setUsername(event.target.value)}
placeholder={t('login.auth.username')}
className='bg-dark text-white'
className='bg-dark text-light'
autoComplete='username'
autoFocus={true}
required
@ -85,7 +85,7 @@ export const RegisterPage: React.FC = () => {
isValid={password !== '' && password.length >= 8}
onChange={(event) => setPassword(event.target.value)}
placeholder={t('login.auth.password')}
className='bg-dark text-white'
className='bg-dark text-light'
minLength={8}
autoComplete='new-password'
required
@ -101,7 +101,7 @@ export const RegisterPage: React.FC = () => {
isValid={passwordAgain !== '' && password === passwordAgain}
onChange={(event) => setPasswordAgain(event.target.value)}
placeholder={t('login.register.passwordAgain')}
className='bg-dark text-white'
className='bg-dark text-light'
autoComplete='new-password'
required
/>

View file

@ -16,6 +16,7 @@ import { RegisterPage } from './components/register-page/register-page'
import { store } from './redux'
import * as serviceWorker from './service-worker'
import './style/index.scss'
import './style/dark.scss'
ReactDOM.render(
<Provider store={store}>

View file

@ -0,0 +1,45 @@
import { store } from '..'
import { DarkModeConfig, DarkModeConfigActionType, SetDarkModeConfigAction } from './types'
export const setDarkMode = (darkMode: boolean): void => {
const action: SetDarkModeConfigAction = {
type: DarkModeConfigActionType.SET_DARK_MODE,
darkMode: darkMode
}
store.dispatch(action)
}
export const saveToLocalStorage = (darkModeConfig: DarkModeConfig): void => {
try {
window.localStorage.setItem('nightMode', String(darkModeConfig.darkMode))
} catch (e) {
console.error('Saving dark-mode setting to local storage failed: ', e)
}
}
export const loadFromLocalStorage = (): DarkModeConfig | undefined => {
try {
const storedValue = window.localStorage.getItem('nightMode')
if (!storedValue) {
return undefined
}
return {
darkMode: storedValue === 'true'
}
} catch (e) {
console.error('Loading dark-mode setting from local storage failed: ', e)
return undefined
}
}
export const determineDarkModeBrowserSetting = (): DarkModeConfig | undefined => {
try {
const mediaQueryResult = window.matchMedia('(prefers-color-scheme: dark)').matches
return {
darkMode: mediaQueryResult
}
} catch (e) {
console.error('Can not determine dark-mode setting from browser: ', e)
return undefined
}
}

View file

@ -0,0 +1,26 @@
import { Reducer } from 'redux'
import { determineDarkModeBrowserSetting, loadFromLocalStorage, saveToLocalStorage } from './methods'
import { DarkModeConfig, DarkModeConfigActions, DarkModeConfigActionType, SetDarkModeConfigAction } from './types'
export const getInitialState = (): DarkModeConfig => {
const initialMode = loadFromLocalStorage() ?? determineDarkModeBrowserSetting() ?? {
darkMode: false
}
saveToLocalStorage(initialMode)
return initialMode
}
export const DarkModeConfigReducer: Reducer<DarkModeConfig, DarkModeConfigActions> = (state: DarkModeConfig = getInitialState(), action: DarkModeConfigActions) => {
let darkModeConfigState: DarkModeConfig
switch (action.type) {
case DarkModeConfigActionType.SET_DARK_MODE:
darkModeConfigState = {
...state,
darkMode: (action as SetDarkModeConfigAction).darkMode
}
saveToLocalStorage(darkModeConfigState)
return darkModeConfigState
default:
return state
}
}

View file

@ -0,0 +1,17 @@
import { Action } from 'redux'
export enum DarkModeConfigActionType {
SET_DARK_MODE = 'dark-mode/set',
}
export interface DarkModeConfig {
darkMode: boolean
}
export interface DarkModeConfigActions extends Action<DarkModeConfigActionType> {
type: DarkModeConfigActionType
}
export interface SetDarkModeConfigAction extends DarkModeConfigActions {
darkMode: boolean
}

View file

@ -5,6 +5,8 @@ import { ApiUrlObject } from './api-url/types'
import { BannerReducer } from './banner/reducers'
import { BannerState } from './banner/types'
import { ConfigReducer } from './config/reducers'
import { DarkModeConfigReducer } from './dark-mode/reducers'
import { DarkModeConfig } from './dark-mode/types'
import { EditorConfigReducer } from './editor/reducers'
import { EditorConfig } from './editor/types'
import { UserReducer } from './user/reducers'
@ -16,6 +18,7 @@ export interface ApplicationState {
banner: BannerState;
apiUrl: ApiUrlObject;
editorConfig: EditorConfig;
darkMode: DarkModeConfig;
}
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
@ -23,7 +26,8 @@ export const allReducers: Reducer<ApplicationState> = combineReducers<Applicatio
config: ConfigReducer,
banner: BannerReducer,
apiUrl: ApiUrlReducer,
editorConfig: EditorConfigReducer
editorConfig: EditorConfigReducer,
darkMode: DarkModeConfigReducer
})
export const store = createStore(allReducers)

View file

@ -0,0 +1,9 @@
// Alternate styles
//
// Generate contextual modifier classes for colorizing the alert.
@each $color, $value in $theme-colors {
.alert-#{$color} {
@include alert-variant(theme-color-level($color, $alert-bg-level), theme-color-level($color, $alert-border-level), theme-color-level($color, $alert-color-level));
}
}

View file

@ -0,0 +1,9 @@
// Colors
//
// Contextual variations (linked badges get darker on :hover).
@each $color, $value in $theme-colors {
.badge-#{$color} {
@include badge-variant($value);
}
}

View file

@ -0,0 +1,15 @@
.breadcrumb {
background-color: $breadcrumb-bg;
}
.breadcrumb-item {
+ .breadcrumb-item {
&::before {
color: $breadcrumb-divider-color;
}
}
&.active {
color: $breadcrumb-active-color;
}
}

View file

@ -0,0 +1,65 @@
// stylelint-disable selector-no-qualifying-type
//
// Base styles
//
.btn {
color: $body-color;
@include hover() {
color: $body-color;
}
&:focus,
&.focus {
box-shadow: $btn-focus-box-shadow;
}
&:not(:disabled):not(.disabled) {
&:active,
&.active {
@include box-shadow($btn-active-box-shadow);
&:focus {
@include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow);
}
}
}
}
//
// Alternate buttons
//
@each $color, $value in $theme-colors {
.btn-#{$color} {
@include button-variant($value, $value);
}
}
@each $color, $value in $theme-colors {
.btn-outline-#{$color} {
@include button-outline-variant($value);
}
}
//
// Link buttons
//
// Make a button look and behave like a link
.btn-link {
color: $link-color;
@include hover() {
color: $link-hover-color;
}
&:disabled,
&.disabled {
color: $btn-link-disabled-color;
}
// No need for an active state here
}

View file

@ -0,0 +1,29 @@
//
// Base styles
//
.card {
background-color: $card-bg;
border-color: $card-border-color;
}
.card-body {
color: $card-color;
}
//
// Optional textual caps
//
.card-header {
color: $card-cap-color;
background-color: $card-cap-bg;
border-bottom-color: $card-border-color;
}
.card-footer {
color: $card-cap-color;
background-color: $card-cap-bg;
border-top-color: $card-border-color;
}

View file

@ -0,0 +1,24 @@
.carousel-control-prev,
.carousel-control-next {
color: $carousel-control-color;
// Hover/focus state
@include hover-focus() {
color: $carousel-control-color;
}
}
.carousel-indicators {
li {
background-color: $carousel-indicator-active-bg;
}
}
// Optional captions
//
//
.carousel-caption {
color: $carousel-caption-color;
}

View file

@ -0,0 +1,12 @@
.close {
color: $close-color;
text-shadow: $close-text-shadow;
@include hover() {
color: $close-color;
}
}
button.close {
background-color: transparent;
}

View file

@ -0,0 +1,18 @@
// Inline code
code {
color: $code-color;
}
kbd {
color: $kbd-color;
background-color: $kbd-bg;
@include box-shadow($kbd-box-shadow);
}
// Blocks of code
pre {
color: $pre-color;
code {
color: inherit;
}
}

View file

@ -0,0 +1,43 @@
.dropdown-menu {
color: $dropdown-color;
background-color: $dropdown-bg;
border-color: $dropdown-border-color;
@include box-shadow($dropdown-box-shadow);
}
// Dividers (basically an `<hr>`) within the dropdown
.dropdown-divider {
@include nav-divider($dropdown-divider-bg, $dropdown-divider-margin-y, true);
}
.dropdown-item {
color: $dropdown-link-color;
@include hover-focus() {
color: $dropdown-link-hover-color;
@include gradient-bg($dropdown-link-hover-bg);
}
&.active,
&:active {
color: $dropdown-link-active-color;
@include gradient-bg($dropdown-link-active-bg);
}
&.disabled,
&:disabled {
color: $dropdown-link-disabled-color;
background-color: transparent;
}
}
// Dropdown section headers
.dropdown-header {
color: $dropdown-header-color;
}
// Dropdown text
.dropdown-item-text {
color: $dropdown-link-color;
}

View file

@ -0,0 +1,59 @@
// stylelint-disable selector-no-qualifying-type
//
// Textual form controls
//
.form-control {
color: $input-color;
background-color: $input-bg;
border-color: $input-border-color;
@include box-shadow($input-box-shadow);
&:-moz-focusring {
text-shadow: 0 0 0 $input-color;
}
// Customize the `:focus` state to imitate native WebKit styles.
@include form-control-focus($ignore-warning: true);
// Placeholder
&::placeholder {
color: $input-placeholder-color;
}
&:disabled,
&[readonly] {
background-color: $input-disabled-bg;
}
}
select.form-control {
&:focus::-ms-value {
// Suppress the nested default white text on blue background highlight given to
// the selected option text when the (still closed) <select> receives focus
// in IE and (under certain conditions) Edge, as it looks bad and cannot be made to
// match the appearance of the native widget.
// See https://github.com/twbs/bootstrap/issues/19398.
color: $input-color;
background-color: $input-bg;
}
}
.form-control-plaintext {
color: $input-plaintext-color;
background-color: transparent;
}
.form-check-input {
&[disabled] ~ .form-check-label,
&:disabled ~ .form-check-label {
color: $text-muted;
}
}
@each $state, $data in $form-validation-states {
@include form-validation-state($state, map-get($data, color), map-get($data, icon));
}

View file

@ -0,0 +1,10 @@
// Image thumbnails
.img-thumbnail {
background-color: $thumbnail-bg;
border-color: $thumbnail-border-color;
@include box-shadow($thumbnail-box-shadow);
}
.figure-caption {
color: $figure-caption-color;
}

View file

@ -0,0 +1,5 @@
.input-group-text {
color: $input-group-addon-color;
background-color: $input-group-addon-bg;
border-color: $input-group-addon-border-color;
}

View file

@ -0,0 +1,4 @@
.jumbotron {
color: $jumbotron-color;
background-color: $jumbotron-bg;
}

View file

@ -0,0 +1,41 @@
.list-group-item-action {
color: $list-group-action-color;
@include hover-focus() {
color: $list-group-action-hover-color;
background-color: $list-group-hover-bg;
}
&:active {
color: $list-group-action-active-color;
background-color: $list-group-action-active-bg;
}
}
.list-group-item {
color: $list-group-color;
background-color: $list-group-bg;
border-color: $list-group-border-color;
&.disabled,
&:disabled {
color: $list-group-disabled-color;
background-color: $list-group-disabled-bg;
}
// Include both here for `<a>`s and `<button>`s
&.active {
color: $list-group-active-color;
background-color: $list-group-active-bg;
border-color: $list-group-active-border-color;
}
}
// Contextual variants
//
// Add modifier classes to change text and background color on individual items.
// Organizationally, this must come after the `:hover` states.
@each $color, $value in $theme-colors {
@include list-group-item-variant($color, theme-color-level($color, -9), theme-color-level($color, 6));
}

View file

@ -0,0 +1,35 @@
&.modal-open .modal {
overflow-x: hidden;
overflow-y: auto;
}
// Actual modal
.modal-content {
background-color: $modal-content-bg;
border-color: $modal-content-border-color;
@include box-shadow($modal-content-box-shadow-xs);
}
// Modal background
.modal-backdrop {
background-color: $modal-backdrop-bg;
}
// Modal header
// Top section of the modal w/ title and dismiss
.modal-header {
border-bottom: $modal-header-border-width solid $modal-header-border-color;
}
// Footer (for actions)
.modal-footer {
border-top: $modal-footer-border-width solid $modal-footer-border-color;
}
// Scale up the modal
@include media-breakpoint-up(sm) {
.modal-content {
@include box-shadow($modal-content-box-shadow-sm-up);
}
}

View file

@ -0,0 +1,36 @@
.nav-link {
&.disabled {
color: $nav-link-disabled-color;
}
}
.nav-tabs {
border-bottom-color: $nav-tabs-border-color;
.nav-link {
@include hover-focus() {
border-color: $nav-tabs-link-hover-border-color;
}
&.disabled {
color: $nav-link-disabled-color;
background-color: transparent;
border-color: transparent;
}
}
.nav-link.active,
.nav-item.show .nav-link {
color: $nav-tabs-link-active-color;
background-color: $nav-tabs-link-active-bg;
border-color: $nav-tabs-link-active-border-color;
}
}
.nav-pills {
.nav-link.active,
.show > .nav-link {
color: $nav-pills-link-active-color;
background-color: $nav-pills-link-active-bg;
}
}

View file

@ -0,0 +1,104 @@
// Button for toggling the navbar when in its collapsed state
.navbar-toggler {
background-color: transparent; // remove default button style
}
// Navbar themes
//
// Styles for switching between navbars with light or dark background.
// Dark links against a light background
.navbar-light {
.navbar-brand {
color: $navbar-light-brand-color;
@include hover-focus() {
color: $navbar-light-brand-hover-color;
}
}
.navbar-nav {
.nav-link {
color: $navbar-light-color;
@include hover-focus() {
color: $navbar-light-hover-color;
}
&.disabled {
color: $navbar-light-disabled-color;
}
}
.show > .nav-link,
.active > .nav-link,
.nav-link.show,
.nav-link.active {
color: $navbar-light-active-color;
}
}
.navbar-toggler {
color: $navbar-light-color;
border-color: $navbar-light-toggler-border-color;
}
.navbar-text {
color: $navbar-light-color;
a {
color: $navbar-light-active-color;
@include hover-focus() {
color: $navbar-light-active-color;
}
}
}
}
// White links against a dark background
.navbar-dark {
.navbar-brand {
color: $navbar-dark-brand-color;
@include hover-focus() {
color: $navbar-dark-brand-hover-color;
}
}
.navbar-nav {
.nav-link {
color: $navbar-dark-color;
@include hover-focus() {
color: $navbar-dark-hover-color;
}
&.disabled {
color: $navbar-dark-disabled-color;
}
}
.show > .nav-link,
.active > .nav-link,
.nav-link.show,
.nav-link.active {
color: $navbar-dark-active-color;
}
}
.navbar-toggler {
color: $navbar-dark-color;
border-color: $navbar-dark-toggler-border-color;
}
.navbar-text {
color: $navbar-dark-color;
a {
color: $navbar-dark-active-color;
@include hover-focus() {
color: $navbar-dark-active-color;
}
}
}
}

View file

@ -0,0 +1,30 @@
.page-link {
color: $pagination-color;
background-color: $pagination-bg;
border-color: $pagination-border-color;
&:hover {
color: $pagination-hover-color;
background-color: $pagination-hover-bg;
border-color: $pagination-hover-border-color;
}
&:focus {
outline: $pagination-focus-outline;
box-shadow: $pagination-focus-box-shadow;
}
}
.page-item {
&.active .page-link {
color: $pagination-active-color;
background-color: $pagination-active-bg;
border-color: $pagination-active-border-color;
}
&.disabled .page-link {
color: $pagination-disabled-color;
background-color: $pagination-disabled-bg;
border-color: $pagination-disabled-border-color;
}
}

View file

@ -0,0 +1,80 @@
.popover {
background-color: $popover-bg;
border: $popover-border-width solid $popover-border-color;
@include box-shadow($popover-box-shadow);
}
.bs-popover-top {
> .arrow {
&::before {
border-top-color: $popover-arrow-outer-color;
}
&::after {
border-top-color: $popover-arrow-color;
}
}
}
.bs-popover-right {
> .arrow {
&::before {
border-right-color: $popover-arrow-outer-color;
}
&::after {
border-right-color: $popover-arrow-color;
}
}
}
.bs-popover-bottom {
> .arrow {
&::before {
border-bottom-color: $popover-arrow-outer-color;
}
&::after {
border-bottom-color: $popover-arrow-color;
}
}
}
.bs-popover-left {
> .arrow {
&::before {
border-left-color: $popover-arrow-outer-color;
}
&::after {
border-left-color: $popover-arrow-color;
}
}
}
.bs-popover-auto {
&[x-placement^="top"] {
@extend .bs-popover-top;
}
&[x-placement^="right"] {
@extend .bs-popover-right;
}
&[x-placement^="bottom"] {
@extend .bs-popover-bottom;
}
&[x-placement^="left"] {
@extend .bs-popover-left;
}
}
// Offset the popover to account for the popover arrow
.popover-header {
color: $popover-header-color;
background-color: $popover-header-bg;
border-bottom-color: darken($popover-header-bg, 5%);
}
.popover-body {
color: $popover-body-color;
}

View file

@ -0,0 +1,9 @@
.progress {
background-color: $progress-bg;
@include box-shadow($progress-box-shadow);
}
.progress-bar {
color: $progress-bar-color;
background-color: $progress-bar-bg;
}

View file

@ -0,0 +1,22 @@
& {
color: $body-color;
background-color: $body-bg; // 2
}
a {
color: $link-color;
background-color: transparent; // Remove the gray background on active links in IE 10.
@include hover() {
color: $link-hover-color;
}
}
caption {
color: $table-caption-color;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}

View file

@ -0,0 +1,8 @@
.spinner-border {
border-color: currentColor;
border-right-color: transparent;
}
.spinner-grow {
background-color: currentColor;
}

View file

@ -0,0 +1,123 @@
//
// Basic Bootstrap table
//
.table {
color: $table-color;
background-color: $table-bg; // Reset for nesting within parents with `background-color`.
th,
td {
border-top-color: $table-border-color;
}
thead th {
border-bottom-color: $table-border-color;
}
tbody + tbody {
border-top-color: $table-border-color;
}
}
// Border versions
//
// Add or remove borders all around the table and between all the columns.
.table-bordered {
border-color: $table-border-color;
th,
td {
border-color: $table-border-color;
}
}
// Zebra-striping
//
// Default zebra-stripe styles (alternating gray and transparent backgrounds)
.table-striped {
tbody tr:nth-of-type(#{$table-striped-order}) {
background-color: $table-accent-bg;
}
}
// Hover effect
//
// Placed here since it has to come after the potential zebra striping
.table-hover {
tbody tr {
@include hover() {
color: $table-hover-color;
background-color: $table-hover-bg;
}
}
}
// Table backgrounds
//
// Exact selectors below required to override `.table-striped` and prevent
// inheritance to nested tables.
@each $color, $value in $theme-colors {
@include table-row-variant($color, theme-color-level($color, $table-bg-level), theme-color-level($color, $table-border-level));
}
@include table-row-variant(active, $table-active-bg);
// Dark styles
//
// Same table markup, but inverted color scheme: dark background and light text.
// stylelint-disable-next-line no-duplicate-selectors
.table {
.thead-dark {
th {
color: $table-dark-color;
background-color: $table-dark-bg;
border-color: $table-dark-border-color;
}
}
.thead-light {
th {
color: $table-head-color;
background-color: $table-head-bg;
border-color: $table-border-color;
}
}
}
.table-dark {
color: $table-dark-color;
background-color: $table-dark-bg;
th,
td,
thead th {
border-color: $table-dark-border-color;
}
&.table-striped {
tbody tr:nth-of-type(#{$table-striped-order}) {
background-color: $table-dark-accent-bg;
}
}
&.table-hover {
tbody tr {
@include hover() {
color: $table-dark-hover-color;
background-color: $table-dark-hover-bg;
}
}
}
}

View file

@ -0,0 +1,11 @@
.toast {
color: $toast-color;
background-color: $toast-background-color;
border-color: $toast-border-color;
}
.toast-header {
color: $toast-header-color;
background-color: $toast-header-background-color;
border-bottom-color: $toast-header-border-color;
}

View file

@ -0,0 +1,52 @@
.bs-tooltip-top {
.arrow {
&::before {
border-top-color: $tooltip-arrow-color;
}
}
}
.bs-tooltip-right {
.arrow {
&::before {
border-right-color: $tooltip-arrow-color;
}
}
}
.bs-tooltip-bottom {
.arrow {
&::before {
border-bottom-color: $tooltip-arrow-color;
}
}
}
.bs-tooltip-left {
.arrow {
&::before {
border-left-color: $tooltip-arrow-color;
}
}
}
.bs-tooltip-auto {
&[x-placement^="top"] {
@extend .bs-tooltip-top;
}
&[x-placement^="right"] {
@extend .bs-tooltip-right;
}
&[x-placement^="bottom"] {
@extend .bs-tooltip-bottom;
}
&[x-placement^="left"] {
@extend .bs-tooltip-left;
}
}
// Wrapper for the tooltip content
.tooltip-inner {
color: $tooltip-color;
background-color: $tooltip-bg;
}

View file

@ -0,0 +1,28 @@
// stylelint-disable declaration-no-important, selector-list-comma-newline-after
//
// Headings
//
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
color: $headings-color;
}
//
// Horizontal rules
//
hr {
border-top-color: $hr-border-color;
}
mark,
.mark {
background-color: $mark-bg;
}
.blockquote-footer {
color: $blockquote-small-color;
}

View file

@ -0,0 +1,33 @@
@import "../../../node_modules/bootstrap/scss/functions";
@import "../../../node_modules/bootstrap/scss/variables";
@import "../../../node_modules/bootstrap/scss/mixins";
@import "reboot";
@import "type";
@import "images";
@import "code";
@import "tables";
@import "forms";
@import "buttons";
@import "dropdown";
@import "input-group";
@import "nav";
@import "navbar";
@import "card";
@import "breadcrumb";
@import "pagination";
@import "badge";
@import "jumbotron";
@import "alert";
@import "progress";
@import "list-group";
@import "close";
@import "toasts";
@import "modal";
@import "tooltip";
@import "popover";
@import "carousel";
@import "spinners";
@import "utilities/background";
@import "utilities/borders";
@import "utilities/text";

View file

@ -0,0 +1,15 @@
// stylelint-disable declaration-no-important
@each $color, $value in $theme-colors {
@include bg-variant(".bg-#{$color}", $value, true);
}
@if $enable-gradients {
@each $color, $value in $theme-colors {
@include bg-gradient-variant(".bg-gradient-#{$color}", $value, true);
}
}
.bg-white {
background-color: $white !important;
}

View file

@ -0,0 +1,21 @@
// stylelint-disable property-blacklist, declaration-no-important
//
// Border
//
.border { border-color: $border-color !important; }
.border-top { border-top-color: $border-color !important; }
.border-right { border-right-color: $border-color !important; }
.border-bottom { border-bottom-color: $border-color !important; }
.border-left { border-left-color: $border-color !important; }
@each $color, $value in $theme-colors {
.border-#{$color} {
border-color: $value !important;
}
}
.border-white {
border-color: $white !important;
}

View file

@ -0,0 +1,13 @@
// Contextual colors
.text-white { color: $white !important; }
@each $color, $value in $theme-colors {
@include text-emphasis-variant(".text-#{$color}", $value, true);
}
.text-body { color: $body-color !important; }
.text-muted { color: $text-muted !important; }
.text-black-50 { color: rgba($black, .5) !important; }
.text-white-50 { color: rgba($white, .5) !important; }

128
src/style/dark.scss Normal file
View file

@ -0,0 +1,128 @@
//
// Color system
//
$white: #fff;
$gray-100: #f8f9fa;
$gray-200: #ebebeb;
$gray-300: #dee2e6;
$gray-400: #ced4da;
$gray-500: #adb5bd;
$gray-600: #888;
$gray-700: #444;
$gray-800: #303030;
$gray-900: #222;
$black: #000;
$blue: #337ab7;
$indigo: #6610f2;
$purple: #6f42c1;
$pink: #e83e8c;
$red: #e74c3c;
$orange: #fd7e14;
$yellow: #f39c12;
$green: #00bc8c;
$teal: #20c997;
$cyan: #5EB7E0;
$primary: $blue;
$secondary: $white;
$success: $green;
$info: $cyan;
$warning: $yellow;
$danger: $red;
$light: $gray-900;
$dark: $white;
$yiq-contrasted-threshold: 175;
// Body
$body-bg: $light;
$body-color: $dark;
// Links
$link-color: $cyan;
// Fonts
$text-muted: $gray-400;
// Tables
$table-accent-bg: $gray-800;
$table-border-color: $gray-700;
// Forms
$input-border-color: $body-bg;
$input-group-addon-color: $gray-500;
$input-group-addon-bg: $gray-700;
$input-bg: $gray-700;
$input-placeholder-color: $gray-500;
$input-color: $white;
$input-disabled-bg: $gray-900;
$custom-file-color: $gray-500;
$custom-file-border-color: $body-bg;
// Dropdowns
$dropdown-bg: $gray-900;
$dropdown-border-color: $gray-700;
$dropdown-divider-bg: $gray-700;
$dropdown-link-color: $white;
$dropdown-link-hover-color: $white;
$dropdown-link-hover-bg: $primary;
// Navs
$nav-link-disabled-color: $gray-500;
$nav-tabs-border-color: $gray-700;
$nav-tabs-link-hover-border-color: $nav-tabs-border-color $nav-tabs-border-color transparent;
$nav-tabs-link-active-color: $white;
$nav-tabs-link-active-border-color: $nav-tabs-border-color $nav-tabs-border-color transparent;
// Navbar
$navbar-dark-color: rgba($white, .6);
$navbar-dark-hover-color: $white;
$navbar-light-color: rgba($gray-900, .7);
$navbar-light-hover-color: $gray-900;
$navbar-light-active-color: $gray-900;
$navbar-light-toggler-border-color: rgba($gray-900, .1);
// Jumbotron
$jumbotron-bg: $gray-800;
// Cards
$card-cap-bg: $gray-700;
$card-bg: $gray-800;
// Popovers
$popover-bg: $gray-800;
$popover-header-bg: $gray-700;
// Toasts
$toast-background-color: $gray-700;
$toast-header-background-color: $gray-800;
// Modals
$modal-content-bg: $gray-800;
$modal-content-border-color: $gray-700;
$modal-header-border-color: $gray-700;
// Progress bars
$progress-bg: $gray-700;
// List group
$list-group-bg: $gray-800;
$list-group-border-color: $gray-700;
$list-group-hover-bg: $gray-700;
// Breadcrumbs
$breadcrumb-bg: $gray-700;
// Close
$close-color: $white;
$close-text-shadow: none;
// Code
$pre-color: $dark;
body.dark {
@import "bootstrap-color-theme/include";
}

View file

@ -1,15 +1,23 @@
@import './light';
@import "../../node_modules/bootstrap/scss/bootstrap";
@import '../../node_modules/react-bootstrap-typeahead/css/Typeahead';
@import "fonts/source-code-pro/source-code-pro";
@import "fonts/twemoji/twemoji";
.text-black, body.dark .text-black {
color: $black;
}
body {
background-color: darken($dark, 8%);
}
html {
height: 100%;
}
body {
min-height: 100%;
background-color: darken($dark, 8%);
font-family: "Source Sans Pro", Helvetica, Arial, twemoji, sans-serif;
}

7
src/style/light.scss Normal file
View file

@ -0,0 +1,7 @@
$blue: #337ab7 !default;
$cyan: #5EB7E0 !default;
@import "../../node_modules/bootstrap/scss/functions";
@import "../../node_modules/bootstrap/scss/variables";
@import "../../node_modules/bootstrap/scss/mixins";

View file

@ -2383,7 +2383,7 @@ abcjs@5.12.0:
integrity sha512-pvi7SjOAKT7cRyRtywUSwYB0SNtRHKLxZUZ9Oc4E+nvpBHr8Z2/M9Pfyv3oIaiEpxlWTFK+B/H5t/DckiNFgpg==
dependencies:
abcjs "5.11.0"
midi "git+https://github.com/paulrosen/MIDI.js.git#abcjs"
midi "https://github.com/paulrosen/MIDI.js.git#abcjs"
accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
version "1.3.7"
@ -6296,11 +6296,6 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
github-markdown-css@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/github-markdown-css/-/github-markdown-css-4.0.0.tgz#be9f4caf7a389228d4c368336260ffc909061f35"
integrity sha512-mH0bcIKv4XAN0mQVokfTdKo2OD5K8WJE9+lbMdM32/q0Ie5tXgVN/2o+zvToRMxSTUuiTRcLg5hzkFfOyBYreg==
glob-parent@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
@ -8737,14 +8732,8 @@ micromatch@^4.0.0:
braces "^3.0.1"
picomatch "^2.0.5"
"midi@git+https://github.com/paulrosen/MIDI.js.git#abcjs":
version "0.4.2"
uid e593ffef81a0350f99448e3ab8111957145ff6b2
resolved "git+https://github.com/paulrosen/MIDI.js.git#e593ffef81a0350f99448e3ab8111957145ff6b2"
"midi@https://github.com/paulrosen/MIDI.js.git#abcjs":
version "0.4.2"
uid e593ffef81a0350f99448e3ab8111957145ff6b2
resolved "https://github.com/paulrosen/MIDI.js.git#e593ffef81a0350f99448e3ab8111957145ff6b2"
miller-rabin@^4.0.0: