mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-29 22:35:50 -04:00
better linting (#72)
Improve linting and fix linting errors Signed-off-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
This commit is contained in:
parent
efb6513205
commit
eba59ae622
70 changed files with 2413 additions and 1867 deletions
|
@ -1,28 +1,27 @@
|
|||
import React, {Fragment} from "react";
|
||||
import {IconProp} from "@fortawesome/fontawesome-svg-core";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import React, { Fragment } from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { IconProp } from '../../../../utils/iconProp'
|
||||
|
||||
export interface ExternalLinkProp {
|
||||
href: string;
|
||||
text: string;
|
||||
icon?: IconProp;
|
||||
href: string;
|
||||
text: string;
|
||||
icon?: IconProp;
|
||||
}
|
||||
|
||||
export const ExternalLink: React.FC<ExternalLinkProp> = ({href, text, icon}) => {
|
||||
return (
|
||||
<a href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-light">
|
||||
{
|
||||
icon ?
|
||||
<Fragment>
|
||||
<FontAwesomeIcon icon={icon}/>
|
||||
</Fragment>
|
||||
:
|
||||
null
|
||||
}
|
||||
{text}
|
||||
</a>
|
||||
)
|
||||
export const ExternalLink: React.FC<ExternalLinkProp> = ({ href, text, icon }) => {
|
||||
return (
|
||||
<a href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-light">
|
||||
{
|
||||
icon
|
||||
? <Fragment>
|
||||
<FontAwesomeIcon icon={icon}/>
|
||||
</Fragment>
|
||||
: null
|
||||
}
|
||||
{text}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import React from "react";
|
||||
import {LanguagePicker} from "./language-picker";
|
||||
import {PoweredByLinks} from "./powered-by-links";
|
||||
import {SocialLink} from "./social-links";
|
||||
import React from 'react'
|
||||
import { LanguagePicker } from './language-picker'
|
||||
import { PoweredByLinks } from './powered-by-links'
|
||||
import { SocialLink } from './social-links'
|
||||
|
||||
export const Footer: React.FC = () => {
|
||||
return (
|
||||
<footer className="text-white-50 small">
|
||||
<LanguagePicker/>
|
||||
<PoweredByLinks/>
|
||||
<SocialLink/>
|
||||
</footer>
|
||||
);
|
||||
return (
|
||||
<footer className="text-white-50 small">
|
||||
<LanguagePicker/>
|
||||
<PoweredByLinks/>
|
||||
<SocialLink/>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import moment from "moment";
|
||||
import { Form } from "react-bootstrap";
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import moment from 'moment'
|
||||
import { Form } from 'react-bootstrap'
|
||||
|
||||
const LanguagePicker: React.FC = () => {
|
||||
const {i18n} = useTranslation();
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const onChangeLang = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
moment.locale(event.currentTarget.value);
|
||||
i18n.changeLanguage(event.currentTarget.value);
|
||||
}
|
||||
const onChangeLang = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
moment.locale(event.currentTarget.value)
|
||||
await i18n.changeLanguage(event.currentTarget.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<Form.Control
|
||||
as="select"
|
||||
size="sm"
|
||||
className="mb-2 mx-auto w-auto"
|
||||
value={i18n.language}
|
||||
onChange={onChangeLang}
|
||||
>
|
||||
<option value="en">English</option>
|
||||
<option value="zh-CN">简体中文</option>
|
||||
<option value="zh-TW">繁體中文</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="es">Español</option>
|
||||
<option value="ca">Català</option>
|
||||
<option value="el">Ελληνικά</option>
|
||||
<option value="pt">Português</option>
|
||||
<option value="it">Italiano</option>
|
||||
<option value="tr">Türkçe</option>
|
||||
<option value="ru">Русский</option>
|
||||
<option value="nl">Nederlands</option>
|
||||
<option value="hr">Hrvatski</option>
|
||||
<option value="pl">Polski</option>
|
||||
<option value="uk">Українська</option>
|
||||
<option value="hi">हिन्दी</option>
|
||||
<option value="sv">Svenska</option>
|
||||
<option value="eo">Esperanto</option>
|
||||
<option value="da">Dansk</option>
|
||||
<option value="ko">한국어</option>
|
||||
<option value="id">Bahasa Indonesia</option>
|
||||
<option value="sr">Cрпски</option>
|
||||
<option value="vi">Tiếng Việt</option>
|
||||
<option value="ar">العربية</option>
|
||||
<option value="cs">Česky</option>
|
||||
<option value="sk">Slovensky</option>
|
||||
</Form.Control>
|
||||
)
|
||||
return (
|
||||
<Form.Control
|
||||
as="select"
|
||||
size="sm"
|
||||
className="mb-2 mx-auto w-auto"
|
||||
value={i18n.language}
|
||||
onChange={onChangeLang}
|
||||
>
|
||||
<option value="en">English</option>
|
||||
<option value="zh-CN">简体中文</option>
|
||||
<option value="zh-TW">繁體中文</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="es">Español</option>
|
||||
<option value="ca">Català</option>
|
||||
<option value="el">Ελληνικά</option>
|
||||
<option value="pt">Português</option>
|
||||
<option value="it">Italiano</option>
|
||||
<option value="tr">Türkçe</option>
|
||||
<option value="ru">Русский</option>
|
||||
<option value="nl">Nederlands</option>
|
||||
<option value="hr">Hrvatski</option>
|
||||
<option value="pl">Polski</option>
|
||||
<option value="uk">Українська</option>
|
||||
<option value="hi">हिन्दी</option>
|
||||
<option value="sv">Svenska</option>
|
||||
<option value="eo">Esperanto</option>
|
||||
<option value="da">Dansk</option>
|
||||
<option value="ko">한국어</option>
|
||||
<option value="id">Bahasa Indonesia</option>
|
||||
<option value="sr">Cрпски</option>
|
||||
<option value="vi">Tiếng Việt</option>
|
||||
<option value="ar">العربية</option>
|
||||
<option value="cs">Česky</option>
|
||||
<option value="sk">Slovensky</option>
|
||||
</Form.Control>
|
||||
)
|
||||
}
|
||||
|
||||
export { LanguagePicker }
|
||||
|
|
|
@ -1,51 +1,35 @@
|
|||
import {Trans, useTranslation} from "react-i18next";
|
||||
import {TranslatedLink} from "./translated-link";
|
||||
import React, {Fragment} from "react";
|
||||
import {ExternalLink} from "./external-link";
|
||||
import {useSelector} from "react-redux";
|
||||
import {ApplicationState} from "../../../../redux";
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { TranslatedLink } from './translated-link'
|
||||
import React, { Fragment } from 'react'
|
||||
import { ExternalLink } from './external-link'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../redux'
|
||||
|
||||
const PoweredByLinks: React.FC = () => {
|
||||
useTranslation();
|
||||
const defaultLinks = [
|
||||
{
|
||||
href: '/s/release-notes',
|
||||
i18nKey: 'releases'
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/codimd/server/tree/41b13e71b6b1d499238c04b15d65e3bd76442f1d',
|
||||
i18nKey: 'sourceCode'
|
||||
}
|
||||
]
|
||||
export const PoweredByLinks: React.FC = () => {
|
||||
useTranslation()
|
||||
|
||||
const config = useSelector((state: ApplicationState) => state.backendConfig);
|
||||
const defaultLinks =
|
||||
{
|
||||
releases: '/s/release-notes',
|
||||
sourceCode: 'https://github.com/codimd/server/tree/41b13e71b6b1d499238c04b15d65e3bd76442f1d'
|
||||
}
|
||||
|
||||
const specialLinks = Object.entries(config.specialLinks)
|
||||
.filter(([_, value]) => value !== "")
|
||||
.map(([key, value]) => {
|
||||
return {
|
||||
href: value,
|
||||
i18nKey: key
|
||||
}
|
||||
})
|
||||
const config = useSelector((state: ApplicationState) => state.backendConfig)
|
||||
|
||||
return (
|
||||
<p>
|
||||
<Trans i18nKey="poweredBy" components={[<ExternalLink href="https://codimd.org" text="CodiMD"/>]}/>
|
||||
|
||||
{
|
||||
(defaultLinks.concat(specialLinks)).map(({href, i18nKey}) =>
|
||||
<Fragment key={i18nKey}>
|
||||
|
|
||||
<TranslatedLink
|
||||
href={href}
|
||||
i18nKey={i18nKey}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
</p>
|
||||
)
|
||||
return (
|
||||
<p>
|
||||
<Trans i18nKey="poweredBy" components={[<ExternalLink href="https://codimd.org" text="CodiMD"/>]}/>
|
||||
{
|
||||
Object.entries({ ...defaultLinks, ...(config.specialLinks) }).map(([i18nKey, href]) =>
|
||||
<Fragment key={i18nKey}>
|
||||
|
|
||||
<TranslatedLink
|
||||
href={href}
|
||||
i18nKey={i18nKey}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
export { PoweredByLinks }
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import React from "react";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import {ExternalLink} from "./external-link";
|
||||
import React from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { ExternalLink } from './external-link'
|
||||
|
||||
const SocialLink: React.FC = () => {
|
||||
useTranslation();
|
||||
return (
|
||||
<p>
|
||||
<Trans i18nKey="followUs" components={[
|
||||
<ExternalLink href="https://github.com/codimd/server" icon={['fab', 'github']} text="GitHub"/>,
|
||||
<ExternalLink href="https://community.codimd.org" icon={['fab', 'discourse']} text="Discourse"/>,
|
||||
<ExternalLink href="https://riot.im/app/#/room/#codimd:matrix.org" icon="comment" text="Riot"/>,
|
||||
<ExternalLink href="https://social.codimd.org/mastodon" icon={['fab', 'mastodon']} text="Mastodon"/>,
|
||||
<ExternalLink href="https://translate.codimd.org" icon="globe" text="POEditor"/>
|
||||
]}/>
|
||||
</p>
|
||||
)
|
||||
useTranslation()
|
||||
return (
|
||||
<p>
|
||||
<Trans i18nKey="followUs" components={[
|
||||
<ExternalLink href="https://github.com/codimd/server" icon={['fab', 'github']} text="GitHub"/>,
|
||||
<ExternalLink href="https://community.codimd.org" icon={['fab', 'discourse']} text="Discourse"/>,
|
||||
<ExternalLink href="https://riot.im/app/#/room/#codimd:matrix.org" icon="comment" text="Riot"/>,
|
||||
<ExternalLink href="https://social.codimd.org/mastodon" icon={['fab', 'mastodon']} text="Mastodon"/>,
|
||||
<ExternalLink href="https://translate.codimd.org" icon="globe" text="POEditor"/>
|
||||
]}/>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
export { SocialLink }
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import React from "react";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import React from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export interface TranslatedLinkProps {
|
||||
href: string;
|
||||
i18nKey: string;
|
||||
href: string;
|
||||
i18nKey: string;
|
||||
}
|
||||
|
||||
const TranslatedLink: React.FC<TranslatedLinkProps> = ({href, i18nKey}) => {
|
||||
useTranslation();
|
||||
return (
|
||||
<a href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-light">
|
||||
<Trans i18nKey={i18nKey}/>
|
||||
</a>
|
||||
)
|
||||
const TranslatedLink: React.FC<TranslatedLinkProps> = ({ href, i18nKey }) => {
|
||||
useTranslation()
|
||||
return (
|
||||
<a href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-light">
|
||||
<Trans i18nKey={i18nKey}/>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
export {TranslatedLink}
|
||||
export { TranslatedLink }
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
import React from "react";
|
||||
import {Redirect, Route, Switch} from "react-router-dom";
|
||||
import {History} from "../pages/history/history";
|
||||
import {Intro} from "../pages/intro/intro";
|
||||
import {Container} from "react-bootstrap";
|
||||
import {HeaderBar} from "./navigation/header-bar/header-bar";
|
||||
import {Footer} from "./footer/footer";
|
||||
import "./style/index.scss";
|
||||
import {Login} from "../pages/login/login";
|
||||
import React from 'react'
|
||||
import { Redirect, Route, Switch } from 'react-router-dom'
|
||||
import { History } from '../pages/history/history'
|
||||
import { Intro } from '../pages/intro/intro'
|
||||
import { Container } from 'react-bootstrap'
|
||||
import { HeaderBar } from './navigation/header-bar/header-bar'
|
||||
import { Footer } from './footer/footer'
|
||||
import './style/index.scss'
|
||||
import { Login } from '../pages/login/login'
|
||||
|
||||
export const Landing: React.FC = () => {
|
||||
return (<Container>
|
||||
<HeaderBar/>
|
||||
<Switch>
|
||||
<Route path="/history">
|
||||
<History/>
|
||||
</Route>
|
||||
<Route path="/intro">
|
||||
<Intro/>
|
||||
</Route>
|
||||
<Route path="/login">
|
||||
<Login/>
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<Redirect to="/intro"/>
|
||||
</Route>
|
||||
</Switch>
|
||||
<Footer/>
|
||||
</Container>)
|
||||
return (<Container>
|
||||
<HeaderBar/>
|
||||
<Switch>
|
||||
<Route path="/history">
|
||||
<History/>
|
||||
</Route>
|
||||
<Route path="/intro">
|
||||
<Intro/>
|
||||
</Route>
|
||||
<Route path="/login">
|
||||
<Login/>
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<Redirect to="/intro"/>
|
||||
</Route>
|
||||
</Switch>
|
||||
<Footer/>
|
||||
</Container>)
|
||||
}
|
||||
|
|
|
@ -1,50 +1,48 @@
|
|||
import React, {Fragment} from 'react'
|
||||
import {Navbar} from 'react-bootstrap';
|
||||
import {useSelector} from "react-redux";
|
||||
import {ApplicationState} from "../../../../../redux";
|
||||
import {NewUserNoteButton} from "../new-user-note-button";
|
||||
import {UserDropdown} from "../user-dropdown/user-dropdown";
|
||||
import {SignInButton} from "../sign-in-button";
|
||||
import {NewGuestNoteButton} from "../new-guest-note-button";
|
||||
import {LoginStatus} from "../../../../../redux/user/types";
|
||||
import {HeaderNavLink} from "../header-nav-link";
|
||||
import "./header-bar.scss";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import React, { Fragment } from 'react'
|
||||
import { Navbar } from 'react-bootstrap'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../../redux'
|
||||
import { NewUserNoteButton } from '../new-user-note-button'
|
||||
import { UserDropdown } from '../user-dropdown/user-dropdown'
|
||||
import { SignInButton } from '../sign-in-button'
|
||||
import { NewGuestNoteButton } from '../new-guest-note-button'
|
||||
import { LoginStatus } from '../../../../../redux/user/types'
|
||||
import { HeaderNavLink } from '../header-nav-link'
|
||||
import './header-bar.scss'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
const HeaderBar: React.FC = () => {
|
||||
useTranslation()
|
||||
const user = useSelector((state: ApplicationState) => state.user);
|
||||
useTranslation()
|
||||
const user = useSelector((state: ApplicationState) => state.user)
|
||||
|
||||
return (
|
||||
<Navbar className="justify-content-between">
|
||||
<div className="nav header-nav">
|
||||
<HeaderNavLink to="/intro">
|
||||
<Trans i18nKey="intro"/>
|
||||
</HeaderNavLink>
|
||||
<HeaderNavLink to="/history">
|
||||
<Trans i18nKey="history"/>
|
||||
</HeaderNavLink>
|
||||
</div>
|
||||
<div className="d-inline-flex">
|
||||
{user.status === LoginStatus.forbidden ?
|
||||
<Fragment>
|
||||
<span className={"mr-1"}>
|
||||
<NewGuestNoteButton/>
|
||||
</span>
|
||||
<SignInButton/>
|
||||
</Fragment>
|
||||
:
|
||||
<Fragment>
|
||||
<span className={"mr-1"}>
|
||||
<NewUserNoteButton/>
|
||||
</span>
|
||||
<UserDropdown/>
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
</Navbar>
|
||||
);
|
||||
return (
|
||||
<Navbar className="justify-content-between">
|
||||
<div className="nav header-nav">
|
||||
<HeaderNavLink to="/intro">
|
||||
<Trans i18nKey="intro"/>
|
||||
</HeaderNavLink>
|
||||
<HeaderNavLink to="/history">
|
||||
<Trans i18nKey="history"/>
|
||||
</HeaderNavLink>
|
||||
</div>
|
||||
<div className="d-inline-flex">
|
||||
{user.status === LoginStatus.forbidden
|
||||
? <Fragment>
|
||||
<span className={'mr-1'}>
|
||||
<NewGuestNoteButton/>
|
||||
</span>
|
||||
<SignInButton/>
|
||||
</Fragment>
|
||||
: <Fragment>
|
||||
<span className={'mr-1'}>
|
||||
<NewUserNoteButton/>
|
||||
</span>
|
||||
<UserDropdown/>
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
</Navbar>
|
||||
)
|
||||
}
|
||||
|
||||
export {HeaderBar}
|
||||
|
||||
export { HeaderBar }
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import {Nav} from "react-bootstrap";
|
||||
import {LinkContainer} from "react-router-bootstrap";
|
||||
import React from "react";
|
||||
import { Nav } from 'react-bootstrap'
|
||||
import { LinkContainer } from 'react-router-bootstrap'
|
||||
import React from 'react'
|
||||
|
||||
export interface HeaderNavLinkProps {
|
||||
to: string
|
||||
to: string
|
||||
}
|
||||
|
||||
export const HeaderNavLink: React.FC<HeaderNavLinkProps> = (props) => {
|
||||
return (
|
||||
<Nav.Item>
|
||||
<LinkContainer to={props.to}>
|
||||
<Nav.Link className="text-light" href={props.to}>{props.children}</Nav.Link>
|
||||
</LinkContainer>
|
||||
</Nav.Item>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Nav.Item>
|
||||
<LinkContainer to={props.to}>
|
||||
<Nav.Link className="text-light" href={props.to}>{props.children}</Nav.Link>
|
||||
</LinkContainer>
|
||||
</Nav.Item>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import React from "react";
|
||||
import {LinkContainer} from "react-router-bootstrap";
|
||||
import {Button} from "react-bootstrap";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import React from 'react'
|
||||
import { LinkContainer } from 'react-router-bootstrap'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export const NewGuestNoteButton: React.FC = () => {
|
||||
const {i18n} = useTranslation();
|
||||
return (
|
||||
<LinkContainer to={'/new'} title={i18n.t("newGuestNote")}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className="d-inline-flex align-items-center">
|
||||
<FontAwesomeIcon icon="plus" className="mr-1"/>
|
||||
<span>
|
||||
<Trans i18nKey='newGuestNote'/>
|
||||
</span>
|
||||
</Button>
|
||||
</LinkContainer>)
|
||||
const { i18n } = useTranslation()
|
||||
return (
|
||||
<LinkContainer to={'/new'} title={i18n.t('newGuestNote')}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className="d-inline-flex align-items-center">
|
||||
<FontAwesomeIcon icon="plus" className="mr-1"/>
|
||||
<span>
|
||||
<Trans i18nKey='newGuestNote'/>
|
||||
</span>
|
||||
</Button>
|
||||
</LinkContainer>)
|
||||
}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import {LinkContainer} from "react-router-bootstrap";
|
||||
import {Button} from "react-bootstrap";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import React from "react";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import { LinkContainer } from 'react-router-bootstrap'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import React from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export const NewUserNoteButton: React.FC = () => {
|
||||
const {i18n} = useTranslation()
|
||||
return (
|
||||
<LinkContainer to={'/new'} title={i18n.t("newNote")}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className="d-inline-flex align-items-center">
|
||||
<FontAwesomeIcon icon="plus" className="mr-1"/>
|
||||
<span>
|
||||
<Trans i18nKey='newNote'/>
|
||||
</span>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
)
|
||||
const { i18n } = useTranslation()
|
||||
return (
|
||||
<LinkContainer to={'/new'} title={i18n.t('newNote')}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className="d-inline-flex align-items-center">
|
||||
<FontAwesomeIcon icon="plus" className="mr-1"/>
|
||||
<span>
|
||||
<Trans i18nKey='newNote'/>
|
||||
</span>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import React from "react";
|
||||
import {Button} from "react-bootstrap";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import {LinkContainer} from "react-router-bootstrap";
|
||||
import React from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { LinkContainer } from 'react-router-bootstrap'
|
||||
|
||||
export const SignInButton: React.FC = () => {
|
||||
const {i18n} = useTranslation();
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
return (
|
||||
<LinkContainer to="/login" title={i18n.t("signIn")}>
|
||||
<Button
|
||||
variant="success"
|
||||
size="sm"
|
||||
>
|
||||
<Trans i18nKey="signIn"/>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
)
|
||||
return (
|
||||
<LinkContainer to="/login" title={i18n.t('signIn')}>
|
||||
<Button
|
||||
variant="success"
|
||||
size="sm"
|
||||
>
|
||||
<Trans i18nKey="signIn"/>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
import {Dropdown} from "react-bootstrap";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import React from "react";
|
||||
import {useSelector} from "react-redux";
|
||||
import {ApplicationState} from "../../../../../redux";
|
||||
import {LinkContainer} from "react-router-bootstrap";
|
||||
import {clearUser} from "../../../../../redux/user/methods";
|
||||
import "./user-dropdown.scss";
|
||||
import {Trans} from "react-i18next";
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../../redux'
|
||||
import { LinkContainer } from 'react-router-bootstrap'
|
||||
import { clearUser } from '../../../../../redux/user/methods'
|
||||
import './user-dropdown.scss'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
export const UserDropdown: React.FC = () => {
|
||||
const user = useSelector((state: ApplicationState) => state.user);
|
||||
const user = useSelector((state: ApplicationState) => state.user)
|
||||
|
||||
return (
|
||||
<Dropdown alignRight>
|
||||
<Dropdown.Toggle size="sm" variant="dark" id="dropdown-basic">
|
||||
<div className='d-inline-flex align-items-baseline'>
|
||||
<img
|
||||
src={user.photo}
|
||||
className="user-avatar"
|
||||
alt={`Avatar of ${user.name}`}
|
||||
/><span>{user.name}</span>
|
||||
</div>
|
||||
</Dropdown.Toggle>
|
||||
return (
|
||||
<Dropdown alignRight>
|
||||
<Dropdown.Toggle size="sm" variant="dark" id="dropdown-basic">
|
||||
<div className='d-inline-flex align-items-baseline'>
|
||||
<img
|
||||
src={user.photo}
|
||||
className="user-avatar"
|
||||
alt={`Avatar of ${user.name}`}
|
||||
/><span>{user.name}</span>
|
||||
</div>
|
||||
</Dropdown.Toggle>
|
||||
|
||||
<Dropdown.Menu>
|
||||
<LinkContainer to={`/features`}>
|
||||
<Dropdown.Item>
|
||||
<FontAwesomeIcon icon="bolt" fixedWidth={true} className="mr-2"/>
|
||||
<Trans i18nKey="features"/>
|
||||
</Dropdown.Item>
|
||||
</LinkContainer>
|
||||
<LinkContainer to={`/me/export`}>
|
||||
<Dropdown.Item>
|
||||
<FontAwesomeIcon icon="cloud-download-alt" fixedWidth={true} className="mr-2"/>
|
||||
<Trans i18nKey="exportUserData"/>
|
||||
</Dropdown.Item>
|
||||
</LinkContainer>
|
||||
<Dropdown.Item href="#">
|
||||
<FontAwesomeIcon icon="trash" fixedWidth={true} className="mr-2"/>
|
||||
<Trans i18nKey="deleteUser"/>
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
onClick={() => {
|
||||
clearUser();
|
||||
}}>
|
||||
<FontAwesomeIcon icon="sign-out-alt" fixedWidth={true} className="mr-2"/>
|
||||
<Trans i18nKey="signOut"/>
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>)
|
||||
};
|
||||
<Dropdown.Menu>
|
||||
<LinkContainer to={'/features'}>
|
||||
<Dropdown.Item>
|
||||
<FontAwesomeIcon icon="bolt" fixedWidth={true} className="mr-2"/>
|
||||
<Trans i18nKey="features"/>
|
||||
</Dropdown.Item>
|
||||
</LinkContainer>
|
||||
<LinkContainer to={'/me/export'}>
|
||||
<Dropdown.Item>
|
||||
<FontAwesomeIcon icon="cloud-download-alt" fixedWidth={true} className="mr-2"/>
|
||||
<Trans i18nKey="exportUserData"/>
|
||||
</Dropdown.Item>
|
||||
</LinkContainer>
|
||||
<Dropdown.Item href="#">
|
||||
<FontAwesomeIcon icon="trash" fixedWidth={true} className="mr-2"/>
|
||||
<Trans i18nKey="deleteUser"/>
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
onClick={() => {
|
||||
clearUser()
|
||||
}}>
|
||||
<FontAwesomeIcon icon="sign-out-alt" fixedWidth={true} className="mr-2"/>
|
||||
<Trans i18nKey="signOut"/>
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>)
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('/src/components/indexnents/index/fonts/SourceSansPro-Light.woff') format('woff');
|
||||
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('fonts/SourceSansPro-Light.woff') format('woff');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
}
|
||||
/* vietnamese */
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import React from "react";
|
||||
import "./close-button.scss"
|
||||
import {Button} from "react-bootstrap";
|
||||
import React from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import './close-button.scss'
|
||||
|
||||
export interface CloseButtonProps {
|
||||
isDark: boolean;
|
||||
isDark: boolean;
|
||||
}
|
||||
|
||||
const CloseButton: React.FC<CloseButtonProps> = ({isDark}) => {
|
||||
return (
|
||||
<Button variant={isDark ? "secondary" : "light"}>
|
||||
<FontAwesomeIcon
|
||||
className="history-close"
|
||||
icon="times"
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
const CloseButton: React.FC<CloseButtonProps> = ({ isDark }) => {
|
||||
return (
|
||||
<Button variant={isDark ? 'secondary' : 'light'}>
|
||||
<FontAwesomeIcon
|
||||
className="history-close"
|
||||
icon="times"
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export {CloseButton}
|
||||
export { CloseButton }
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import React from "react";
|
||||
import "./pin-button.scss"
|
||||
import {Button} from "react-bootstrap";
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import React from 'react'
|
||||
import './pin-button.scss'
|
||||
import { Button } from 'react-bootstrap'
|
||||
|
||||
export interface PinButtonProps {
|
||||
isPinned: boolean;
|
||||
onPinClick: () => void;
|
||||
isDark: boolean;
|
||||
isPinned: boolean;
|
||||
onPinClick: () => void;
|
||||
isDark: boolean;
|
||||
}
|
||||
|
||||
export const PinButton: React.FC<PinButtonProps> = ({isPinned, onPinClick, isDark}) => {
|
||||
return (
|
||||
<Button variant={isDark ? "secondary" : "light"}
|
||||
onClick={onPinClick}>
|
||||
<FontAwesomeIcon
|
||||
icon="thumbtack"
|
||||
className={`history-pin ${isPinned ? 'active' : ''}`}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
export const PinButton: React.FC<PinButtonProps> = ({ isPinned, onPinClick, isDark }) => {
|
||||
return (
|
||||
<Button variant={isDark ? 'secondary' : 'light'}
|
||||
onClick={onPinClick}>
|
||||
<FontAwesomeIcon
|
||||
icon="thumbtack"
|
||||
className={`history-pin ${isPinned ? 'active' : ''}`}
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
import React from 'react'
|
||||
import {HistoryEntriesProps} from "../history-content/history-content";
|
||||
import {HistoryCard} from "./history-card";
|
||||
import {Pager} from '../../../../pagination/pager';
|
||||
import {Row} from 'react-bootstrap';
|
||||
import { HistoryEntriesProps } from '../history-content/history-content'
|
||||
import { HistoryCard } from './history-card'
|
||||
import { Pager } from '../../../../pagination/pager'
|
||||
import { Row } from 'react-bootstrap'
|
||||
|
||||
export const HistoryCardList: React.FC<HistoryEntriesProps> = ({entries, onPinClick, pageIndex, onLastPageIndexChange}) => {
|
||||
|
||||
return (
|
||||
<Row className="justify-content-center">
|
||||
<Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}>
|
||||
{
|
||||
entries.map((entry) => (
|
||||
<HistoryCard
|
||||
key={entry.id}
|
||||
entry={entry}
|
||||
onPinClick={onPinClick}
|
||||
/>))
|
||||
}
|
||||
</Pager>
|
||||
</Row>
|
||||
);
|
||||
export const HistoryCardList: React.FC<HistoryEntriesProps> = ({ entries, onPinClick, pageIndex, onLastPageIndexChange }) => {
|
||||
return (
|
||||
<Row className="justify-content-center">
|
||||
<Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}>
|
||||
{
|
||||
entries.map((entry) => (
|
||||
<HistoryCard
|
||||
key={entry.id}
|
||||
entry={entry}
|
||||
onPinClick={onPinClick}
|
||||
/>))
|
||||
}
|
||||
</Pager>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
import React from 'react'
|
||||
import {Badge, Card} from 'react-bootstrap'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
|
||||
import {PinButton} from "../common/pin-button";
|
||||
import {CloseButton} from "../common/close-button";
|
||||
import moment from "moment";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {HistoryEntryProps} from "../history-content/history-content";
|
||||
import {formatHistoryDate} from "../../../../../utils/historyUtils";
|
||||
import { Badge, Card } from 'react-bootstrap'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { PinButton } from '../common/pin-button'
|
||||
import { CloseButton } from '../common/close-button'
|
||||
import moment from 'moment'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { HistoryEntryProps } from '../history-content/history-content'
|
||||
import { formatHistoryDate } from '../../../../../utils/historyUtils'
|
||||
|
||||
export const HistoryCard: React.FC<HistoryEntryProps> = ({entry, onPinClick}) => {
|
||||
useTranslation()
|
||||
return (
|
||||
<div className="p-2 col-xs-12 col-sm-6 col-md-6 col-lg-4">
|
||||
<Card className="p-0" text={"dark"} bg={"light"}>
|
||||
<div className="d-flex justify-content-between p-2 align-items-start">
|
||||
<PinButton isDark={false} isPinned={entry.pinned} onPinClick={() => {
|
||||
onPinClick(entry.id)
|
||||
}}/>
|
||||
<Card.Title className="m-0 mt-3">{entry.title}</Card.Title>
|
||||
<CloseButton isDark={false}/>
|
||||
</div>
|
||||
<Card.Body>
|
||||
<div className="text-black-50">
|
||||
<FontAwesomeIcon icon="clock"/> {moment(entry.lastVisited).fromNow()}<br/>
|
||||
{formatHistoryDate(entry.lastVisited)}
|
||||
<div>
|
||||
{
|
||||
entry.tags.map((tag) => <Badge variant={"dark"} className={"mr-1 mb-1"}
|
||||
key={tag}>{tag}</Badge>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
export const HistoryCard: React.FC<HistoryEntryProps> = ({ entry, onPinClick }) => {
|
||||
useTranslation()
|
||||
return (
|
||||
<div className="p-2 col-xs-12 col-sm-6 col-md-6 col-lg-4">
|
||||
<Card className="p-0" text={'dark'} bg={'light'}>
|
||||
<div className="d-flex justify-content-between p-2 align-items-start">
|
||||
<PinButton isDark={false} isPinned={entry.pinned} onPinClick={() => {
|
||||
onPinClick(entry.id)
|
||||
}}/>
|
||||
<Card.Title className="m-0 mt-3">{entry.title}</Card.Title>
|
||||
<CloseButton isDark={false}/>
|
||||
</div>
|
||||
)
|
||||
<Card.Body>
|
||||
<div className="text-black-50">
|
||||
<FontAwesomeIcon icon="clock"/> {moment(entry.lastVisited).fromNow()}<br/>
|
||||
{formatHistoryDate(entry.lastVisited)}
|
||||
<div>
|
||||
{
|
||||
entry.tags.map((tag) => <Badge variant={'dark'} className={'mr-1 mb-1'}
|
||||
key={tag}>{tag}</Badge>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import React, {Fragment, useState} from "react";
|
||||
import {HistoryEntry, pinClick} from "../history";
|
||||
import {HistoryTable} from "../history-table/history-table";
|
||||
import {Alert, Row} from "react-bootstrap";
|
||||
import {Trans} from "react-i18next";
|
||||
import {HistoryCardList} from "../history-card/history-card-list";
|
||||
import {ViewStateEnum} from "../history-toolbar/history-toolbar";
|
||||
import {PagerPagination} from "../../../../pagination/pager-pagination";
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { HistoryEntry, pinClick } from '../history'
|
||||
import { HistoryTable } from '../history-table/history-table'
|
||||
import { Alert, Row } from 'react-bootstrap'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { HistoryCardList } from '../history-card/history-card-list'
|
||||
import { ViewStateEnum } from '../history-toolbar/history-toolbar'
|
||||
import { PagerPagination } from '../../../../pagination/pager-pagination'
|
||||
|
||||
export interface HistoryContentProps {
|
||||
viewState: ViewStateEnum
|
||||
entries: HistoryEntry[]
|
||||
onPinClick: pinClick
|
||||
viewState: ViewStateEnum
|
||||
entries: HistoryEntry[]
|
||||
onPinClick: pinClick
|
||||
}
|
||||
|
||||
export interface HistoryEntryProps {
|
||||
entry: HistoryEntry,
|
||||
onPinClick: pinClick
|
||||
entry: HistoryEntry,
|
||||
onPinClick: pinClick
|
||||
}
|
||||
|
||||
export interface HistoryEntriesProps {
|
||||
|
@ -25,39 +25,38 @@ export interface HistoryEntriesProps {
|
|||
onLastPageIndexChange: (lastPageIndex: number) => void
|
||||
}
|
||||
|
||||
export const HistoryContent: React.FC<HistoryContentProps> = ({ viewState, entries, onPinClick }) => {
|
||||
const [pageIndex, setPageIndex] = useState(0)
|
||||
const [lastPageIndex, setLastPageIndex] = useState(0)
|
||||
|
||||
export const HistoryContent: React.FC<HistoryContentProps> = ({viewState, entries, onPinClick}) => {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [lastPageIndex, setLastPageIndex] = useState(0);
|
||||
|
||||
if (entries.length === 0) {
|
||||
return (
|
||||
<Row className={"justify-content-center"}>
|
||||
<Alert variant={"secondary"}>
|
||||
<Trans i18nKey={"noHistory"}/>
|
||||
</Alert>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
const mapViewStateToComponent = (viewState: ViewStateEnum) => {
|
||||
switch (viewState) {
|
||||
default:
|
||||
case ViewStateEnum.card:
|
||||
return <HistoryCardList entries={entries} onPinClick={onPinClick} pageIndex={pageIndex}
|
||||
onLastPageIndexChange={setLastPageIndex}/>
|
||||
case ViewStateEnum.table:
|
||||
return <HistoryTable entries={entries} onPinClick={onPinClick} pageIndex={pageIndex}
|
||||
onLastPageIndexChange={setLastPageIndex}/>
|
||||
}
|
||||
}
|
||||
|
||||
if (entries.length === 0) {
|
||||
return (
|
||||
<Fragment>
|
||||
{mapViewStateToComponent(viewState)}
|
||||
<Row className="justify-content-center">
|
||||
<PagerPagination numberOfPageButtonsToShowAfterAndBeforeCurrent={2} lastPageIndex={lastPageIndex}
|
||||
onPageChange={setPageIndex}/>
|
||||
</Row>
|
||||
</Fragment>);
|
||||
}
|
||||
<Row className={'justify-content-center'}>
|
||||
<Alert variant={'secondary'}>
|
||||
<Trans i18nKey={'noHistory'}/>
|
||||
</Alert>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
const mapViewStateToComponent = (viewState: ViewStateEnum) => {
|
||||
switch (viewState) {
|
||||
default:
|
||||
case ViewStateEnum.card:
|
||||
return <HistoryCardList entries={entries} onPinClick={onPinClick} pageIndex={pageIndex}
|
||||
onLastPageIndexChange={setLastPageIndex}/>
|
||||
case ViewStateEnum.table:
|
||||
return <HistoryTable entries={entries} onPinClick={onPinClick} pageIndex={pageIndex}
|
||||
onLastPageIndexChange={setLastPageIndex}/>
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{mapViewStateToComponent(viewState)}
|
||||
<Row className="justify-content-center">
|
||||
<PagerPagination numberOfPageButtonsToShowAfterAndBeforeCurrent={2} lastPageIndex={lastPageIndex}
|
||||
onPageChange={setPageIndex}/>
|
||||
</Row>
|
||||
</Fragment>)
|
||||
}
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
import React from "react";
|
||||
import {PinButton} from "../common/pin-button";
|
||||
import {CloseButton} from "../common/close-button";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {HistoryEntryProps} from "../history-content/history-content";
|
||||
import {formatHistoryDate} from "../../../../../utils/historyUtils";
|
||||
import {Badge} from "react-bootstrap";
|
||||
import React from 'react'
|
||||
import { PinButton } from '../common/pin-button'
|
||||
import { CloseButton } from '../common/close-button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { HistoryEntryProps } from '../history-content/history-content'
|
||||
import { formatHistoryDate } from '../../../../../utils/historyUtils'
|
||||
import { Badge } from 'react-bootstrap'
|
||||
|
||||
export const HistoryTableRow: React.FC<HistoryEntryProps> = ({entry, onPinClick}) => {
|
||||
useTranslation()
|
||||
return (
|
||||
<tr>
|
||||
<td>{entry.title}</td>
|
||||
<td>{formatHistoryDate(entry.lastVisited)}</td>
|
||||
<td>
|
||||
{
|
||||
entry.tags.map((tag) => <Badge variant={"dark"} className={"mr-1 mb-1"}
|
||||
key={tag}>{tag}</Badge>)
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<PinButton isDark={true} isPinned={entry.pinned} onPinClick={() => {
|
||||
onPinClick(entry.id)
|
||||
}}/>
|
||||
|
||||
<CloseButton isDark={true}/>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
export const HistoryTableRow: React.FC<HistoryEntryProps> = ({ entry, onPinClick }) => {
|
||||
useTranslation()
|
||||
return (
|
||||
<tr>
|
||||
<td>{entry.title}</td>
|
||||
<td>{formatHistoryDate(entry.lastVisited)}</td>
|
||||
<td>
|
||||
{
|
||||
entry.tags.map((tag) => <Badge variant={'dark'} className={'mr-1 mb-1'}
|
||||
key={tag}>{tag}</Badge>)
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<PinButton isDark={true} isPinned={entry.pinned} onPinClick={() => {
|
||||
onPinClick(entry.id)
|
||||
}}/>
|
||||
|
||||
<CloseButton isDark={true}/>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
import React from "react";
|
||||
import {Table} from "react-bootstrap"
|
||||
import {HistoryTableRow} from "./history-table-row";
|
||||
import {HistoryEntriesProps} from "../history-content/history-content";
|
||||
import {Trans} from "react-i18next";
|
||||
import {Pager} from "../../../../pagination/pager";
|
||||
import React from 'react'
|
||||
import { Table } from 'react-bootstrap'
|
||||
import { HistoryTableRow } from './history-table-row'
|
||||
import { HistoryEntriesProps } from '../history-content/history-content'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { Pager } from '../../../../pagination/pager'
|
||||
|
||||
export const HistoryTable: React.FC<HistoryEntriesProps> = ({entries, onPinClick, pageIndex, onLastPageIndexChange}) => {
|
||||
return (
|
||||
<Table striped bordered hover size="sm" variant="dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><Trans i18nKey={"title"}/></th>
|
||||
<th><Trans i18nKey={"lastVisit"}/></th>
|
||||
<th><Trans i18nKey={"tags"}/></th>
|
||||
<th><Trans i18nKey={"actions"}/></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}>
|
||||
{
|
||||
entries.map((entry) =>
|
||||
<HistoryTableRow
|
||||
key={entry.id}
|
||||
entry={entry}
|
||||
onPinClick={onPinClick}
|
||||
/>)
|
||||
}
|
||||
</Pager>
|
||||
</tbody>
|
||||
</Table>
|
||||
)
|
||||
export const HistoryTable: React.FC<HistoryEntriesProps> = ({ entries, onPinClick, pageIndex, onLastPageIndexChange }) => {
|
||||
return (
|
||||
<Table striped bordered hover size="sm" variant="dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><Trans i18nKey={'title'}/></th>
|
||||
<th><Trans i18nKey={'lastVisit'}/></th>
|
||||
<th><Trans i18nKey={'tags'}/></th>
|
||||
<th><Trans i18nKey={'actions'}/></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}>
|
||||
{
|
||||
entries.map((entry) =>
|
||||
<HistoryTableRow
|
||||
key={entry.id}
|
||||
entry={entry}
|
||||
onPinClick={onPinClick}
|
||||
/>)
|
||||
}
|
||||
</Pager>
|
||||
</tbody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import {Button, Form, FormControl, InputGroup, ToggleButton, ToggleButtonGroup} from "react-bootstrap";
|
||||
import React, {ChangeEvent, useEffect, useState} from "react";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {SortButton, SortModeEnum} from "../../../../sort-button/sort-button";
|
||||
import {Typeahead} from 'react-bootstrap-typeahead';
|
||||
import "./typeahead-hacks.scss";
|
||||
import React, { ChangeEvent, useEffect, useState } from 'react'
|
||||
import { Button, Form, FormControl, InputGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { SortButton, SortModeEnum } from '../../../../sort-button/sort-button'
|
||||
import { Typeahead } from 'react-bootstrap-typeahead'
|
||||
import './typeahead-hacks.scss'
|
||||
|
||||
export type HistoryToolbarChange = (settings: HistoryToolbarState) => void;
|
||||
|
||||
export interface HistoryToolbarState {
|
||||
viewState: ViewStateEnum
|
||||
titleSortDirection: SortModeEnum
|
||||
lastVisitedSortDirection: SortModeEnum
|
||||
keywordSearch: string
|
||||
selectedTags: string[]
|
||||
viewState: ViewStateEnum
|
||||
titleSortDirection: SortModeEnum
|
||||
lastVisitedSortDirection: SortModeEnum
|
||||
keywordSearch: string
|
||||
selectedTags: string[]
|
||||
}
|
||||
|
||||
export enum ViewStateEnum {
|
||||
|
@ -27,102 +27,101 @@ export interface HistoryToolbarProps {
|
|||
}
|
||||
|
||||
export const initState: HistoryToolbarState = {
|
||||
viewState: ViewStateEnum.card,
|
||||
titleSortDirection: SortModeEnum.no,
|
||||
lastVisitedSortDirection: SortModeEnum.no,
|
||||
keywordSearch: "",
|
||||
selectedTags: []
|
||||
viewState: ViewStateEnum.card,
|
||||
titleSortDirection: SortModeEnum.no,
|
||||
lastVisitedSortDirection: SortModeEnum.no,
|
||||
keywordSearch: '',
|
||||
selectedTags: []
|
||||
}
|
||||
|
||||
export const HistoryToolbar: React.FC<HistoryToolbarProps> = ({onSettingsChange, tags}) => {
|
||||
export const HistoryToolbar: React.FC<HistoryToolbarProps> = ({ onSettingsChange, tags }) => {
|
||||
const [t] = useTranslation()
|
||||
const [state, setState] = useState<HistoryToolbarState>(initState)
|
||||
|
||||
const [t] = useTranslation()
|
||||
const [state, setState] = useState<HistoryToolbarState>(initState);
|
||||
const titleSortChanged = (direction: SortModeEnum) => {
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
titleSortDirection: direction,
|
||||
lastVisitedSortDirection: SortModeEnum.no
|
||||
}))
|
||||
}
|
||||
|
||||
const titleSortChanged = (direction: SortModeEnum) => {
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
titleSortDirection: direction,
|
||||
lastVisitedSortDirection: SortModeEnum.no
|
||||
}))
|
||||
}
|
||||
const lastVisitedSortChanged = (direction: SortModeEnum) => {
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
lastVisitedSortDirection: direction,
|
||||
titleSortDirection: SortModeEnum.no
|
||||
}))
|
||||
}
|
||||
|
||||
const lastVisitedSortChanged = (direction: SortModeEnum) => {
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
lastVisitedSortDirection: direction,
|
||||
titleSortDirection: SortModeEnum.no
|
||||
}))
|
||||
}
|
||||
const keywordSearchChanged = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setState(prevState => ({ ...prevState, keywordSearch: event.currentTarget.value }))
|
||||
}
|
||||
|
||||
const keywordSearchChanged = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setState(prevState => ({...prevState, keywordSearch: event.currentTarget.value}));
|
||||
}
|
||||
const toggleViewChanged = (newViewState: ViewStateEnum) => {
|
||||
setState((prevState) => ({ ...prevState, viewState: newViewState }))
|
||||
}
|
||||
|
||||
const toggleViewChanged = (newViewState: ViewStateEnum) => {
|
||||
setState((prevState) => ({...prevState, viewState: newViewState}))
|
||||
}
|
||||
const selectedTagsChanged = (selected: string[]) => {
|
||||
setState(prevState => ({ ...prevState, selectedTags: selected }))
|
||||
}
|
||||
|
||||
const selectedTagsChanged = (selected: string[]) => {
|
||||
setState((prevState => ({...prevState, selectedTags: selected})))
|
||||
}
|
||||
useEffect(() => {
|
||||
onSettingsChange(state)
|
||||
}, [onSettingsChange, state])
|
||||
|
||||
useEffect(() => {
|
||||
onSettingsChange(state);
|
||||
}, [onSettingsChange, state])
|
||||
|
||||
return (
|
||||
<Form inline={true}>
|
||||
<InputGroup className={"mr-1"}>
|
||||
<Typeahead id={"tagsSelection"} options={tags} multiple={true} placeholder={t("selectTags")}
|
||||
onChange={selectedTagsChanged}/>
|
||||
</InputGroup>
|
||||
<InputGroup className={"mr-1"}>
|
||||
<FormControl
|
||||
placeholder={t("searchKeywords")}
|
||||
aria-label={t("searchKeywords")}
|
||||
onChange={keywordSearchChanged}
|
||||
/>
|
||||
</InputGroup>
|
||||
<InputGroup className={"mr-1"}>
|
||||
<SortButton onChange={titleSortChanged} direction={state.titleSortDirection} variant={"light"}><Trans
|
||||
i18nKey={"sortByTitle"}/></SortButton>
|
||||
</InputGroup>
|
||||
<InputGroup className={"mr-1"}>
|
||||
<SortButton onChange={lastVisitedSortChanged} direction={state.lastVisitedSortDirection}
|
||||
variant={"light"}><Trans i18nKey={"sortByLastVisited"}/></SortButton>
|
||||
</InputGroup>
|
||||
<InputGroup className={"mr-1"}>
|
||||
<Button variant={"light"} title={t("exportHistory")}>
|
||||
<FontAwesomeIcon icon={"download"}/>
|
||||
</Button>
|
||||
</InputGroup>
|
||||
<InputGroup className={"mr-1"}>
|
||||
<Button variant={"light"} title={t("importHistory")}>
|
||||
<FontAwesomeIcon icon={"upload"}/>
|
||||
</Button>
|
||||
</InputGroup>
|
||||
<InputGroup className={"mr-1"}>
|
||||
<Button variant={"light"} title={t("clearHistory")}>
|
||||
<FontAwesomeIcon icon={"trash"}/>
|
||||
</Button>
|
||||
</InputGroup>
|
||||
<InputGroup className={"mr-1"}>
|
||||
<Button variant={"light"} title={t("refreshHistory")}>
|
||||
<FontAwesomeIcon icon={"sync"}/>
|
||||
</Button>
|
||||
</InputGroup>
|
||||
<InputGroup className={"mr-1"}>
|
||||
<ToggleButtonGroup type="radio" name="options" value={state.viewState}
|
||||
onChange={(newViewState: ViewStateEnum) => {
|
||||
toggleViewChanged(newViewState)
|
||||
}}>
|
||||
<ToggleButton className={"btn-light"} value={ViewStateEnum.card}><Trans
|
||||
i18nKey={"cards"}/></ToggleButton>
|
||||
<ToggleButton className={"btn-light"} value={ViewStateEnum.table}><Trans
|
||||
i18nKey={"table"}/></ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</InputGroup>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Form inline={true}>
|
||||
<InputGroup className={'mr-1'}>
|
||||
<Typeahead id={'tagsSelection'} options={tags} multiple={true} placeholder={t('selectTags')}
|
||||
onChange={selectedTagsChanged}/>
|
||||
</InputGroup>
|
||||
<InputGroup className={'mr-1'}>
|
||||
<FormControl
|
||||
placeholder={t('searchKeywords')}
|
||||
aria-label={t('searchKeywords')}
|
||||
onChange={keywordSearchChanged}
|
||||
/>
|
||||
</InputGroup>
|
||||
<InputGroup className={'mr-1'}>
|
||||
<SortButton onChange={titleSortChanged} direction={state.titleSortDirection} variant={'light'}><Trans
|
||||
i18nKey={'sortByTitle'}/></SortButton>
|
||||
</InputGroup>
|
||||
<InputGroup className={'mr-1'}>
|
||||
<SortButton onChange={lastVisitedSortChanged} direction={state.lastVisitedSortDirection}
|
||||
variant={'light'}><Trans i18nKey={'sortByLastVisited'}/></SortButton>
|
||||
</InputGroup>
|
||||
<InputGroup className={'mr-1'}>
|
||||
<Button variant={'light'} title={t('exportHistory')}>
|
||||
<FontAwesomeIcon icon={'download'}/>
|
||||
</Button>
|
||||
</InputGroup>
|
||||
<InputGroup className={'mr-1'}>
|
||||
<Button variant={'light'} title={t('importHistory')}>
|
||||
<FontAwesomeIcon icon={'upload'}/>
|
||||
</Button>
|
||||
</InputGroup>
|
||||
<InputGroup className={'mr-1'}>
|
||||
<Button variant={'light'} title={t('clearHistory')}>
|
||||
<FontAwesomeIcon icon={'trash'}/>
|
||||
</Button>
|
||||
</InputGroup>
|
||||
<InputGroup className={'mr-1'}>
|
||||
<Button variant={'light'} title={t('refreshHistory')}>
|
||||
<FontAwesomeIcon icon={'sync'}/>
|
||||
</Button>
|
||||
</InputGroup>
|
||||
<InputGroup className={'mr-1'}>
|
||||
<ToggleButtonGroup type="radio" name="options" value={state.viewState}
|
||||
onChange={(newViewState: ViewStateEnum) => {
|
||||
toggleViewChanged(newViewState)
|
||||
}}>
|
||||
<ToggleButton className={'btn-light'} value={ViewStateEnum.card}><Trans
|
||||
i18nKey={'cards'}/></ToggleButton>
|
||||
<ToggleButton className={'btn-light'} value={ViewStateEnum.table}><Trans
|
||||
i18nKey={'table'}/></ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</InputGroup>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,67 +1,67 @@
|
|||
import React, {Fragment, useEffect, useState} from 'react'
|
||||
import {HistoryContent} from './history-content/history-content';
|
||||
import {HistoryToolbar, HistoryToolbarState, initState as toolbarInitState} from './history-toolbar/history-toolbar';
|
||||
import {loadHistoryFromLocalStore, sortAndFilterEntries} from "../../../../utils/historyUtils";
|
||||
import {Row} from 'react-bootstrap';
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import React, { Fragment, useEffect, useState } from 'react'
|
||||
import { Row } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { loadHistoryFromLocalStore, sortAndFilterEntries } from '../../../../utils/historyUtils'
|
||||
import { HistoryContent } from './history-content/history-content'
|
||||
import { HistoryToolbar, HistoryToolbarState, initState as toolbarInitState } from './history-toolbar/history-toolbar'
|
||||
|
||||
export interface HistoryEntry {
|
||||
id: string,
|
||||
title: string,
|
||||
lastVisited: Date,
|
||||
tags: string[],
|
||||
pinned: boolean
|
||||
id: string,
|
||||
title: string,
|
||||
lastVisited: Date,
|
||||
tags: string[],
|
||||
pinned: boolean
|
||||
}
|
||||
|
||||
export type pinClick = (entryId: string) => void;
|
||||
|
||||
export const History: React.FC = () => {
|
||||
useTranslation();
|
||||
const [historyEntries, setHistoryEntries] = useState<HistoryEntry[]>([])
|
||||
const [viewState, setViewState] = useState<HistoryToolbarState>(toolbarInitState)
|
||||
useTranslation()
|
||||
const [historyEntries, setHistoryEntries] = useState<HistoryEntry[]>([])
|
||||
const [viewState, setViewState] = useState<HistoryToolbarState>(toolbarInitState)
|
||||
|
||||
useEffect(() => {
|
||||
const history = loadHistoryFromLocalStore();
|
||||
setHistoryEntries(history);
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
const history = loadHistoryFromLocalStore()
|
||||
setHistoryEntries(history)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (historyEntries === []) {
|
||||
return;
|
||||
}
|
||||
window.localStorage.setItem("history", JSON.stringify(historyEntries));
|
||||
}, [historyEntries])
|
||||
|
||||
const pinClick: pinClick = (entryId: string) => {
|
||||
setHistoryEntries((entries) => {
|
||||
return entries.map((entry) => {
|
||||
if (entry.id === entryId) {
|
||||
entry.pinned = !entry.pinned;
|
||||
}
|
||||
return entry;
|
||||
});
|
||||
})
|
||||
useEffect(() => {
|
||||
if (historyEntries === []) {
|
||||
return
|
||||
}
|
||||
window.localStorage.setItem('history', JSON.stringify(historyEntries))
|
||||
}, [historyEntries])
|
||||
|
||||
const tags = historyEntries.map(entry => entry.tags)
|
||||
.reduce((a, b) => ([...a, ...b]), [])
|
||||
.filter((value, index, array) => {
|
||||
if (index === 0) {
|
||||
return true;
|
||||
}
|
||||
return (value !== array[index - 1])
|
||||
})
|
||||
const entriesToShow = sortAndFilterEntries(historyEntries, viewState);
|
||||
const pinClick: pinClick = (entryId: string) => {
|
||||
setHistoryEntries((entries) => {
|
||||
return entries.map((entry) => {
|
||||
if (entry.id === entryId) {
|
||||
entry.pinned = !entry.pinned
|
||||
}
|
||||
return entry
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 className="mb-4"><Trans i18nKey="history"/></h1>
|
||||
<Row className={"justify-content-center mb-3"}>
|
||||
<HistoryToolbar onSettingsChange={setViewState} tags={tags}/>
|
||||
</Row>
|
||||
<HistoryContent viewState={viewState.viewState}
|
||||
entries={entriesToShow}
|
||||
onPinClick={pinClick}/>
|
||||
</Fragment>
|
||||
)
|
||||
const tags = historyEntries.map(entry => entry.tags)
|
||||
.reduce((a, b) => ([...a, ...b]), [])
|
||||
.filter((value, index, array) => {
|
||||
if (index === 0) {
|
||||
return true
|
||||
}
|
||||
return (value !== array[index - 1])
|
||||
})
|
||||
const entriesToShow = sortAndFilterEntries(historyEntries, viewState)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 className="mb-4"><Trans i18nKey="history"/></h1>
|
||||
<Row className={'justify-content-center mb-3'}>
|
||||
<HistoryToolbar onSettingsChange={setViewState} tags={tags}/>
|
||||
</Row>
|
||||
<HistoryContent viewState={viewState.viewState}
|
||||
entries={entriesToShow}
|
||||
onPinClick={pinClick}/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,46 +1,45 @@
|
|||
import {LoginStatus} from "../../../../../redux/user/types";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button} from "react-bootstrap";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import React from "react";
|
||||
import {useSelector} from "react-redux";
|
||||
import {ApplicationState} from "../../../../../redux";
|
||||
import "./cover-buttons.scss";
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { LoginStatus } from '../../../../../redux/user/types'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../../redux'
|
||||
import './cover-buttons.scss'
|
||||
|
||||
export const CoverButtons: React.FC = () => {
|
||||
useTranslation();
|
||||
const user = useSelector((state: ApplicationState) => state.user);
|
||||
useTranslation()
|
||||
const user = useSelector((state: ApplicationState) => state.user)
|
||||
|
||||
if (user.status === LoginStatus.ok) {
|
||||
return null;
|
||||
}
|
||||
if (user.status === LoginStatus.ok) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-5">
|
||||
<Link to="/login">
|
||||
<Button
|
||||
className="cover-button"
|
||||
variant="success"
|
||||
size="lg"
|
||||
>
|
||||
<Trans i18nKey="signIn"/>
|
||||
</Button>
|
||||
</Link>
|
||||
return (
|
||||
<div className="mb-5">
|
||||
<Link to="/login">
|
||||
<Button
|
||||
className="cover-button"
|
||||
variant="success"
|
||||
size="lg"
|
||||
>
|
||||
<Trans i18nKey="signIn"/>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<span className="m-2">
|
||||
<Trans i18nKey="or"/>
|
||||
</span>
|
||||
|
||||
<Link to="/features">
|
||||
<Button
|
||||
className="cover-button"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
>
|
||||
<Trans i18nKey="exploreFeatures"/>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
<span className="m-2">
|
||||
<Trans i18nKey="or"/>
|
||||
</span>
|
||||
|
||||
<Link to="/features">
|
||||
<Button
|
||||
className="cover-button"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
>
|
||||
<Trans i18nKey="exploreFeatures"/>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
import {Col, Row} from "react-bootstrap";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Col, Row } from 'react-bootstrap'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export const FeatureLinks: React.FC = () => {
|
||||
useTranslation();
|
||||
return (
|
||||
<Row className="mb-5">
|
||||
<Col md={4}>
|
||||
<Link to={"/features#Share-Notes"} className="text-light">
|
||||
<FontAwesomeIcon icon="bolt" size="3x"/>
|
||||
<h5>
|
||||
<Trans i18nKey="featureCollaboration"/>
|
||||
</h5>
|
||||
</Link>
|
||||
</Col>
|
||||
<Col md={4}>
|
||||
<Link to={"/features#MathJax"} className="text-light">
|
||||
<FontAwesomeIcon icon="chart-bar" size="3x"/>
|
||||
<h5>
|
||||
<Trans i18nKey="featureMathJax"/>
|
||||
</h5>
|
||||
</Link>
|
||||
</Col>
|
||||
<Col md={4}>
|
||||
<Link to={"/features#Slide-Mode"} className="text-light">
|
||||
<FontAwesomeIcon icon="tv" size="3x"/>
|
||||
<h5>
|
||||
<Trans i18nKey="featureSlides"/>
|
||||
</h5>
|
||||
</Link>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
useTranslation()
|
||||
return (
|
||||
<Row className="mb-5">
|
||||
<Col md={4}>
|
||||
<Link to={'/features#Share-Notes'} className="text-light">
|
||||
<FontAwesomeIcon icon="bolt" size="3x"/>
|
||||
<h5>
|
||||
<Trans i18nKey="featureCollaboration"/>
|
||||
</h5>
|
||||
</Link>
|
||||
</Col>
|
||||
<Col md={4}>
|
||||
<Link to={'/features#MathJax'} className="text-light">
|
||||
<FontAwesomeIcon icon="chart-bar" size="3x"/>
|
||||
<h5>
|
||||
<Trans i18nKey="featureMathJax"/>
|
||||
</h5>
|
||||
</Link>
|
||||
</Col>
|
||||
<Col md={4}>
|
||||
<Link to={'/features#Slide-Mode'} className="text-light">
|
||||
<FontAwesomeIcon icon="tv" size="3x"/>
|
||||
<h5>
|
||||
<Trans i18nKey="featureSlides"/>
|
||||
</h5>
|
||||
</Link>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,29 +1,28 @@
|
|||
import React from 'react'
|
||||
import screenshot from './img/screenshot.png';
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import {FeatureLinks} from "./feature-links";
|
||||
import {CoverButtons} from "./cover-buttons/cover-buttons";
|
||||
import screenshot from './img/screenshot.png'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { FeatureLinks } from './feature-links'
|
||||
import { CoverButtons } from './cover-buttons/cover-buttons'
|
||||
|
||||
const Intro: React.FC = () => {
|
||||
useTranslation();
|
||||
useTranslation()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>
|
||||
<FontAwesomeIcon icon="file-alt"/> CodiMD
|
||||
</h1>
|
||||
<p className="lead mb-5">
|
||||
<Trans i18nKey="coverSlogan"/>
|
||||
</p>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>
|
||||
<FontAwesomeIcon icon="file-alt"/> CodiMD
|
||||
</h1>
|
||||
<p className="lead mb-5">
|
||||
<Trans i18nKey="coverSlogan"/>
|
||||
</p>
|
||||
<CoverButtons/>
|
||||
|
||||
<CoverButtons/>
|
||||
|
||||
<img alt="CodiMD Screenshot" src={screenshot} className="img-fluid mb-5"/>
|
||||
<FeatureLinks/>
|
||||
</div>
|
||||
)
|
||||
<img alt="CodiMD Screenshot" src={screenshot} className="img-fluid mb-5"/>
|
||||
<FeatureLinks/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {Intro}
|
||||
export { Intro }
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import React from "react";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import "./social-link-button.scss";
|
||||
import {IconProp} from "@fortawesome/fontawesome-svg-core";
|
||||
import React from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import './social-link-button.scss'
|
||||
import { IconProp } from '../../../../../../utils/iconProp'
|
||||
|
||||
export interface SocialButtonProps {
|
||||
backgroundClass: string,
|
||||
href: string
|
||||
icon: IconProp
|
||||
title?: string
|
||||
backgroundClass: string,
|
||||
href: string
|
||||
icon: IconProp
|
||||
title?: string
|
||||
}
|
||||
|
||||
export const SocialLinkButton: React.FC<SocialButtonProps> = ({title, backgroundClass, href, icon, children}) => {
|
||||
return (
|
||||
<a href={href} title={title}
|
||||
className={"btn social-link-button p-0 d-inline-flex align-items-stretch " + backgroundClass}>
|
||||
<span className="icon-part d-flex align-items-center">
|
||||
<FontAwesomeIcon icon={icon} className={"social-icon"} fixedWidth={true}/>
|
||||
</span>
|
||||
<span className="text-part d-flex align-items-center mx-auto">
|
||||
{children}
|
||||
</span>
|
||||
</a>
|
||||
)
|
||||
export const SocialLinkButton: React.FC<SocialButtonProps> = ({ title, backgroundClass, href, icon, children }) => {
|
||||
return (
|
||||
<a href={href} title={title}
|
||||
className={'btn social-link-button p-0 d-inline-flex align-items-stretch ' + backgroundClass}>
|
||||
<span className="icon-part d-flex align-items-center">
|
||||
<FontAwesomeIcon icon={icon} className={'social-icon'} fixedWidth={true}/>
|
||||
</span>
|
||||
<span className="text-part d-flex align-items-center mx-auto">
|
||||
{children}
|
||||
</span>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,72 +1,64 @@
|
|||
import {Trans, useTranslation} from "react-i18next";
|
||||
import {Alert, Button, Card, Form} from "react-bootstrap";
|
||||
import React, {useState} from "react";
|
||||
import {postEmailLogin} from "../../../../../api/user";
|
||||
import {getAndSetUser} from "../../../../../utils/apiUtils";
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||
import React, { FormEvent, useState } from 'react'
|
||||
import { postEmailLogin } from '../../../../../api/user'
|
||||
import { getAndSetUser } from '../../../../../utils/apiUtils'
|
||||
|
||||
export const ViaEMail: React.FC = () => {
|
||||
const {t} = useTranslation();
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState(false);
|
||||
const { t } = useTranslation()
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const doAsyncLogin = () => {
|
||||
(async () => {
|
||||
try {
|
||||
await postEmailLogin(email, password);
|
||||
await getAndSetUser();
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
})();
|
||||
}
|
||||
const doAsyncLogin = async () => {
|
||||
await postEmailLogin(email, password)
|
||||
await getAndSetUser()
|
||||
}
|
||||
|
||||
const onFormSubmit = (event: any) => {
|
||||
doAsyncLogin();
|
||||
event.preventDefault();
|
||||
};
|
||||
const onFormSubmit = (event: FormEvent) => {
|
||||
doAsyncLogin().catch(() => setError(true))
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="bg-dark mb-4">
|
||||
<Card.Body>
|
||||
<Card.Title>
|
||||
<Trans i18nKey="signInVia" values={{service: "E-Mail"}}/>
|
||||
</Card.Title>
|
||||
return (
|
||||
<Card className="bg-dark mb-4">
|
||||
<Card.Body>
|
||||
<Card.Title>
|
||||
<Trans i18nKey="signInVia" values={{ service: 'E-Mail' }}/>
|
||||
</Card.Title>
|
||||
<Form onSubmit={onFormSubmit}>
|
||||
<Form.Group controlId="email">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="email"
|
||||
size="sm"
|
||||
placeholder={t('email')}
|
||||
onChange={(event) => setEmail(event.currentTarget.value)} className="bg-dark text-white"
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form onSubmit={onFormSubmit}>
|
||||
<Form.Group controlId="email">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="email"
|
||||
size="sm"
|
||||
placeholder={t("email")}
|
||||
onChange={(event) => setEmail(event.currentTarget.value)}
|
||||
className="bg-dark text-white"
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group controlId="password">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="password"
|
||||
size="sm"
|
||||
placeholder={t('password')}
|
||||
onChange={(event) => setPassword(event.currentTarget.value)}
|
||||
className="bg-dark text-white"/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="password">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="password"
|
||||
size="sm"
|
||||
placeholder={t("password")}
|
||||
onChange={(event) => setPassword(event.currentTarget.value)}
|
||||
className="bg-dark text-white"
|
||||
/>
|
||||
</Form.Group>
|
||||
<Alert className="small" show={error} variant="danger">
|
||||
<Trans i18nKey="errorEmailLogin"/>
|
||||
</Alert>
|
||||
|
||||
<Alert className="small" show={error} variant="danger">
|
||||
<Trans i18nKey="errorEmailLogin"/>
|
||||
</Alert>
|
||||
<Button
|
||||
type="submit"
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary">
|
||||
<Trans i18nKey="signIn"/>
|
||||
</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
variant="primary">
|
||||
<Trans i18nKey="signIn"/>
|
||||
</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,80 +1,74 @@
|
|||
import React, {useState} from "react";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import {Alert, Button, Card, Form} from "react-bootstrap";
|
||||
import {postLdapLogin} from "../../../../../api/user";
|
||||
import {getAndSetUser} from "../../../../../utils/apiUtils";
|
||||
import {useSelector} from "react-redux";
|
||||
import {ApplicationState} from "../../../../../redux";
|
||||
import React, { FormEvent, useState } from 'react'
|
||||
|
||||
const ViaLdap: React.FC = () => {
|
||||
const {t} = useTranslation();
|
||||
const ldapCustomName = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames.ldap);
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||
import { postLdapLogin } from '../../../../../api/user'
|
||||
import { getAndSetUser } from '../../../../../utils/apiUtils'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../../redux'
|
||||
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState(false);
|
||||
export const ViaLdap: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const ldapCustomName = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames.ldap)
|
||||
|
||||
const name = ldapCustomName ? `${ldapCustomName} (LDAP)` : "LDAP";
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const doAsyncLogin = () => {
|
||||
(async () => {
|
||||
try {
|
||||
await postLdapLogin(username, password);
|
||||
await getAndSetUser();
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
})();
|
||||
const name = ldapCustomName ? `${ldapCustomName} (LDAP)` : 'LDAP'
|
||||
|
||||
const doAsyncLogin = async () => {
|
||||
try {
|
||||
await postLdapLogin(username, password)
|
||||
await getAndSetUser()
|
||||
} catch {
|
||||
setError(true)
|
||||
}
|
||||
}
|
||||
|
||||
const onFormSubmit = (event: any) => {
|
||||
doAsyncLogin();
|
||||
event.preventDefault();
|
||||
}
|
||||
const onFormSubmit = (event: FormEvent) => {
|
||||
doAsyncLogin().catch(() => setError(true))
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="bg-dark mb-4">
|
||||
<Card.Body>
|
||||
<Card.Title>
|
||||
<Trans i18nKey="signInVia" values={{service: name}}/>
|
||||
</Card.Title>
|
||||
return (
|
||||
<Card className="bg-dark mb-4">
|
||||
<Card.Body>
|
||||
<Card.Title>
|
||||
<Trans i18nKey="signInVia" values={{ service: name }}/>
|
||||
</Card.Title>
|
||||
<Form onSubmit={onFormSubmit}>
|
||||
<Form.Group controlId="username">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="text"
|
||||
size="sm"
|
||||
placeholder={t('username')}
|
||||
onChange={(event) => setUsername(event.currentTarget.value)} className="bg-dark text-white"
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form onSubmit={onFormSubmit}>
|
||||
<Form.Group controlId="username">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="text"
|
||||
size="sm"
|
||||
placeholder={t("username")}
|
||||
onChange={(event) => setUsername(event.currentTarget.value)}
|
||||
className="bg-dark text-white"
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group controlId="password">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="password"
|
||||
size="sm"
|
||||
placeholder={t('password')}
|
||||
onChange={(event) => setPassword(event.currentTarget.value)}
|
||||
className="bg-dark text-white"/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="password">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="password"
|
||||
size="sm"
|
||||
placeholder={t("password")}
|
||||
onChange={(event) => setPassword(event.currentTarget.value)}
|
||||
className="bg-dark text-white"
|
||||
/>
|
||||
</Form.Group>
|
||||
<Alert className="small" show={error} variant="danger">
|
||||
<Trans i18nKey="errorLdapLogin"/>
|
||||
</Alert>
|
||||
|
||||
<Alert className="small" show={error} variant="danger">
|
||||
<Trans i18nKey="errorLdapLogin"/>
|
||||
</Alert>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary">
|
||||
<Trans i18nKey="signIn"/>
|
||||
</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export { ViaLdap }
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary">
|
||||
<Trans i18nKey="signIn"/>
|
||||
</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,115 +1,115 @@
|
|||
import React from "react";
|
||||
import {IconProp} from "@fortawesome/fontawesome-svg-core";
|
||||
import {SocialLinkButton} from "./social-link-button/social-link-button";
|
||||
import React from 'react'
|
||||
import { IconProp } from '../../../../../utils/iconProp'
|
||||
import { SocialLinkButton } from './social-link-button/social-link-button'
|
||||
|
||||
export enum OneClickType {
|
||||
'DROPBOX'="dropbox",
|
||||
'FACEBOOK'="facebook",
|
||||
'GITHUB'="github",
|
||||
'GITLAB'="gitlab",
|
||||
'GOOGLE'="google",
|
||||
'OAUTH2'="oauth2",
|
||||
'SAML'="saml",
|
||||
'TWITTER'="twitter"
|
||||
'DROPBOX' = 'dropbox',
|
||||
'FACEBOOK' = 'facebook',
|
||||
'GITHUB' = 'github',
|
||||
'GITLAB' = 'gitlab',
|
||||
'GOOGLE' = 'google',
|
||||
'OAUTH2' = 'oauth2',
|
||||
'SAML' = 'saml',
|
||||
'TWITTER' = 'twitter'
|
||||
}
|
||||
|
||||
type OneClick2Map = (oneClickType: OneClickType) => {
|
||||
name: string,
|
||||
icon: IconProp,
|
||||
className: string,
|
||||
url: string
|
||||
name: string,
|
||||
icon: IconProp,
|
||||
className: string,
|
||||
url: string
|
||||
};
|
||||
|
||||
const buildBackendAuthUrl = (backendName: string) => {
|
||||
return `https://localhost:3000/auth/${backendName}`
|
||||
};
|
||||
return `https://localhost:3000/auth/${backendName}`
|
||||
}
|
||||
|
||||
const getMetadata: OneClick2Map = (oneClickType: OneClickType) => {
|
||||
switch (oneClickType) {
|
||||
case OneClickType.DROPBOX:
|
||||
return {
|
||||
name: "Dropbox",
|
||||
icon: ["fab", "dropbox"],
|
||||
className: "btn-social-dropbox",
|
||||
url: buildBackendAuthUrl("dropbox")
|
||||
}
|
||||
case OneClickType.FACEBOOK:
|
||||
return {
|
||||
name: "Facebook",
|
||||
icon: ["fab", "facebook"],
|
||||
className: "btn-social-facebook",
|
||||
url: buildBackendAuthUrl("facebook")
|
||||
}
|
||||
case OneClickType.GITHUB:
|
||||
return {
|
||||
name: "GitHub",
|
||||
icon: ["fab", "github"],
|
||||
className: "btn-social-github",
|
||||
url: buildBackendAuthUrl("github")
|
||||
}
|
||||
case OneClickType.GITLAB:
|
||||
return {
|
||||
name: "GitLab",
|
||||
icon: ["fab", "gitlab"],
|
||||
className: "btn-social-gitlab",
|
||||
url: buildBackendAuthUrl("gitlab")
|
||||
}
|
||||
case OneClickType.GOOGLE:
|
||||
return {
|
||||
name: "Google",
|
||||
icon: ["fab", "google"],
|
||||
className: "btn-social-google",
|
||||
url: buildBackendAuthUrl("google")
|
||||
}
|
||||
case OneClickType.OAUTH2:
|
||||
return {
|
||||
name: "OAuth2",
|
||||
icon: "address-card",
|
||||
className: "btn-primary",
|
||||
url: buildBackendAuthUrl("oauth2")
|
||||
}
|
||||
case OneClickType.SAML:
|
||||
return {
|
||||
name: "SAML",
|
||||
icon: "users",
|
||||
className: "btn-success",
|
||||
url: buildBackendAuthUrl("saml")
|
||||
}
|
||||
case OneClickType.TWITTER:
|
||||
return {
|
||||
name: "Twitter",
|
||||
icon: ["fab", "twitter"],
|
||||
className: "btn-social-twitter",
|
||||
url: buildBackendAuthUrl("twitter")
|
||||
}
|
||||
default:
|
||||
return {
|
||||
name: "",
|
||||
icon: "exclamation",
|
||||
className: "",
|
||||
url: "#"
|
||||
}
|
||||
}
|
||||
switch (oneClickType) {
|
||||
case OneClickType.DROPBOX:
|
||||
return {
|
||||
name: 'Dropbox',
|
||||
icon: ['fab', 'dropbox'],
|
||||
className: 'btn-social-dropbox',
|
||||
url: buildBackendAuthUrl('dropbox')
|
||||
}
|
||||
case OneClickType.FACEBOOK:
|
||||
return {
|
||||
name: 'Facebook',
|
||||
icon: ['fab', 'facebook'],
|
||||
className: 'btn-social-facebook',
|
||||
url: buildBackendAuthUrl('facebook')
|
||||
}
|
||||
case OneClickType.GITHUB:
|
||||
return {
|
||||
name: 'GitHub',
|
||||
icon: ['fab', 'github'],
|
||||
className: 'btn-social-github',
|
||||
url: buildBackendAuthUrl('github')
|
||||
}
|
||||
case OneClickType.GITLAB:
|
||||
return {
|
||||
name: 'GitLab',
|
||||
icon: ['fab', 'gitlab'],
|
||||
className: 'btn-social-gitlab',
|
||||
url: buildBackendAuthUrl('gitlab')
|
||||
}
|
||||
case OneClickType.GOOGLE:
|
||||
return {
|
||||
name: 'Google',
|
||||
icon: ['fab', 'google'],
|
||||
className: 'btn-social-google',
|
||||
url: buildBackendAuthUrl('google')
|
||||
}
|
||||
case OneClickType.OAUTH2:
|
||||
return {
|
||||
name: 'OAuth2',
|
||||
icon: 'address-card',
|
||||
className: 'btn-primary',
|
||||
url: buildBackendAuthUrl('oauth2')
|
||||
}
|
||||
case OneClickType.SAML:
|
||||
return {
|
||||
name: 'SAML',
|
||||
icon: 'users',
|
||||
className: 'btn-success',
|
||||
url: buildBackendAuthUrl('saml')
|
||||
}
|
||||
case OneClickType.TWITTER:
|
||||
return {
|
||||
name: 'Twitter',
|
||||
icon: ['fab', 'twitter'],
|
||||
className: 'btn-social-twitter',
|
||||
url: buildBackendAuthUrl('twitter')
|
||||
}
|
||||
default:
|
||||
return {
|
||||
name: '',
|
||||
icon: 'exclamation',
|
||||
className: '',
|
||||
url: '#'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ViaOneClickProps {
|
||||
oneClickType: OneClickType;
|
||||
optionalName?: string;
|
||||
oneClickType: OneClickType;
|
||||
optionalName?: string;
|
||||
}
|
||||
|
||||
const ViaOneClick: React.FC<ViaOneClickProps> = ({oneClickType, optionalName}) => {
|
||||
const {name, icon, className, url} = getMetadata(oneClickType);
|
||||
const text = !!optionalName ? optionalName : name;
|
||||
return (
|
||||
<SocialLinkButton
|
||||
backgroundClass={className}
|
||||
icon={icon}
|
||||
href={url}
|
||||
title={text}
|
||||
>
|
||||
{text}
|
||||
</SocialLinkButton>
|
||||
)
|
||||
const ViaOneClick: React.FC<ViaOneClickProps> = ({ oneClickType, optionalName }) => {
|
||||
const { name, icon, className, url } = getMetadata(oneClickType)
|
||||
const text = optionalName || name
|
||||
return (
|
||||
<SocialLinkButton
|
||||
backgroundClass={className}
|
||||
icon={icon}
|
||||
href={url}
|
||||
title={text}
|
||||
>
|
||||
{text}
|
||||
</SocialLinkButton>
|
||||
)
|
||||
}
|
||||
|
||||
export {ViaOneClick}
|
||||
export { ViaOneClick }
|
||||
|
|
|
@ -1,61 +1,53 @@
|
|||
import React, {useState} from "react";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import {Alert, Button, Card, Form} from "react-bootstrap";
|
||||
import {postOpenIdLogin} from "../../../../../api/user";
|
||||
import {getAndSetUser} from "../../../../../utils/apiUtils";
|
||||
import React, { FormEvent, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||
import { postOpenIdLogin } from '../../../../../api/user'
|
||||
import { getAndSetUser } from '../../../../../utils/apiUtils'
|
||||
|
||||
const ViaOpenId: React.FC = () => {
|
||||
useTranslation();
|
||||
const [openId, setOpenId] = useState("");
|
||||
const [error, setError] = useState(false);
|
||||
const doAsyncLogin = () => {
|
||||
(async () => {
|
||||
try {
|
||||
await postOpenIdLogin(openId);
|
||||
await getAndSetUser();
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
})();
|
||||
}
|
||||
export const ViaOpenId: React.FC = () => {
|
||||
useTranslation()
|
||||
const [openId, setOpenId] = useState('')
|
||||
const [error, setError] = useState(false)
|
||||
const doAsyncLogin: (() => Promise<void>) = async () => {
|
||||
await postOpenIdLogin(openId)
|
||||
await getAndSetUser()
|
||||
}
|
||||
|
||||
const onFormSubmit = (event: any) => {
|
||||
doAsyncLogin();
|
||||
event.preventDefault();
|
||||
}
|
||||
const onFormSubmit = (event: FormEvent) => {
|
||||
doAsyncLogin().catch(() => setError(true))
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="bg-dark mb-4">
|
||||
<Card.Body>
|
||||
<Card.Title>
|
||||
<Trans i18nKey="signInVia" values={{service: "OpenID"}}/>
|
||||
</Card.Title>
|
||||
return (
|
||||
<Card className="bg-dark mb-4">
|
||||
<Card.Body>
|
||||
<Card.Title>
|
||||
<Trans i18nKey="signInVia" values={{ service: 'OpenID' }}/>
|
||||
</Card.Title>
|
||||
|
||||
<Form onSubmit={onFormSubmit}>
|
||||
<Form.Group controlId="openid">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="text"
|
||||
size="sm"
|
||||
placeholder={"OpenID"}
|
||||
onChange={(event) => setOpenId(event.currentTarget.value)}
|
||||
className="bg-dark text-white"
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form onSubmit={onFormSubmit}>
|
||||
<Form.Group controlId="openid">
|
||||
<Form.Control
|
||||
isInvalid={error}
|
||||
type="text"
|
||||
size="sm"
|
||||
placeholder={'OpenID'}
|
||||
onChange={(event) => setOpenId(event.currentTarget.value)}
|
||||
className="bg-dark text-white"
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Alert className="small" show={error} variant="danger">
|
||||
<Trans i18nKey="errorOpenIdLogin"/>
|
||||
</Alert>
|
||||
<Alert className="small" show={error} variant="danger">
|
||||
<Trans i18nKey="errorOpenIdLogin"/>
|
||||
</Alert>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary">
|
||||
<Trans i18nKey="signIn"/>
|
||||
</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export { ViaOpenId }
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary">
|
||||
<Trans i18nKey="signIn"/>
|
||||
</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,83 +1,81 @@
|
|||
import React from "react"
|
||||
import {Card, Col, Row} from "react-bootstrap"
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import {ViaEMail} from "./auth/via-email";
|
||||
import {OneClickType, ViaOneClick} from "./auth/via-one-click";
|
||||
import {ViaLdap} from "./auth/via-ldap";
|
||||
import {useSelector} from "react-redux";
|
||||
import {ApplicationState} from "../../../../redux";
|
||||
import {ViaOpenId} from "./auth/via-openid";
|
||||
import {Redirect} from "react-router";
|
||||
import {LoginStatus} from "../../../../redux/user/types";
|
||||
import React from 'react'
|
||||
import { Card, Col, Row } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { ViaEMail } from './auth/via-email'
|
||||
import { OneClickType, ViaOneClick } from './auth/via-one-click'
|
||||
import { ViaLdap } from './auth/via-ldap'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../redux'
|
||||
import { ViaOpenId } from './auth/via-openid'
|
||||
import { Redirect } from 'react-router'
|
||||
import { LoginStatus } from '../../../../redux/user/types'
|
||||
|
||||
const Login: React.FC = () => {
|
||||
useTranslation();
|
||||
const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders);
|
||||
const customAuthNames = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames);
|
||||
const userLoginState = useSelector((state: ApplicationState) => state.user.status);
|
||||
const emailForm = authProviders.email ? <ViaEMail/> : null
|
||||
const ldapForm = authProviders.ldap ? <ViaLdap/> : null
|
||||
const openIdForm = authProviders.openid ? <ViaOpenId/> : null
|
||||
export const Login: React.FC = () => {
|
||||
useTranslation()
|
||||
const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders)
|
||||
const customAuthNames = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames)
|
||||
const userLoginState = useSelector((state: ApplicationState) => state.user.status)
|
||||
const emailForm = authProviders.email ? <ViaEMail/> : null
|
||||
const ldapForm = authProviders.ldap ? <ViaLdap/> : null
|
||||
const openIdForm = authProviders.openid ? <ViaOpenId/> : null
|
||||
|
||||
const oneClickCustomName: (type: OneClickType) => string | undefined = (type) => {
|
||||
switch (type) {
|
||||
case OneClickType.SAML:
|
||||
return customAuthNames.saml;
|
||||
case OneClickType.OAUTH2:
|
||||
return customAuthNames.oauth2;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (userLoginState === LoginStatus.ok) {
|
||||
// TODO Redirect to previous page?
|
||||
return (
|
||||
<Redirect to='/history' />
|
||||
)
|
||||
const oneClickCustomName: (type: OneClickType) => string | undefined = (type) => {
|
||||
switch (type) {
|
||||
case OneClickType.SAML:
|
||||
return customAuthNames.saml
|
||||
case OneClickType.OAUTH2:
|
||||
return customAuthNames.oauth2
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
if (userLoginState === LoginStatus.ok) {
|
||||
// TODO Redirect to previous page?
|
||||
return (
|
||||
<div className="my-3">
|
||||
<Row className="h-100 flex justify-content-center">
|
||||
{
|
||||
authProviders.email || authProviders.ldap || authProviders.openid ?
|
||||
<Col xs={12} sm={10} lg={4}>
|
||||
{emailForm}
|
||||
{ldapForm}
|
||||
{openIdForm}
|
||||
</Col>
|
||||
: null
|
||||
}
|
||||
<Col xs={12} sm={10} lg={4}>
|
||||
<Card className="bg-dark mb-4">
|
||||
<Card.Body>
|
||||
<Card.Title>
|
||||
<Trans i18nKey="signInVia" values={{service: ""}}/>
|
||||
</Card.Title>
|
||||
{
|
||||
Object.values(OneClickType)
|
||||
.filter((value) => authProviders[value])
|
||||
.map((value) => {
|
||||
return (
|
||||
<div
|
||||
className="p-2 d-flex flex-column social-button-container"
|
||||
key={value}
|
||||
>
|
||||
<ViaOneClick
|
||||
oneClickType={value}
|
||||
optionalName={oneClickCustomName(value)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
<Redirect to='/history'/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export {Login}
|
||||
return (
|
||||
<div className="my-3">
|
||||
<Row className="h-100 flex justify-content-center">
|
||||
{
|
||||
authProviders.email || authProviders.ldap || authProviders.openid
|
||||
? <Col xs={12} sm={10} lg={4}>
|
||||
{emailForm}
|
||||
{ldapForm}
|
||||
{openIdForm}
|
||||
</Col>
|
||||
: null
|
||||
}
|
||||
<Col xs={12} sm={10} lg={4}>
|
||||
<Card className="bg-dark mb-4">
|
||||
<Card.Body>
|
||||
<Card.Title>
|
||||
<Trans i18nKey="signInVia" values={{ service: '' }}/>
|
||||
</Card.Title>
|
||||
{
|
||||
Object.values(OneClickType)
|
||||
.filter((value) => authProviders[value])
|
||||
.map((value) => {
|
||||
return (
|
||||
<div
|
||||
className="p-2 d-flex flex-column social-button-container"
|
||||
key={value}
|
||||
>
|
||||
<ViaOneClick
|
||||
oneClickType={value}
|
||||
optionalName={oneClickCustomName(value)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue