imported current state of the mockup into the public repo

Co-authored-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
Signed-off-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
This commit is contained in:
Philip Molares 2020-05-14 15:41:38 +02:00
commit 93ce059577
161 changed files with 17419 additions and 0 deletions

View file

@ -0,0 +1,44 @@
import {useDispatch} from "react-redux";
import React from "react";
import {getConfig} from "../../api/config";
import {ApplicationConfigState} from "../../redux/application-config/types";
import {setApplicationConfig} from "../../redux/application-config/actions";
const InitializeConfigStateFromApi: React.FC = () => {
const dispatch = useDispatch();
getConfig()
.then((response) => {
if (response.status === 200) {
return (response.json() as Promise<ApplicationConfigState>);
}
})
.then(config => {
if (!config) {
return;
}
dispatch(setApplicationConfig({
allowAnonymous: config.allowAnonymous,
authProviders: {
facebook: config.authProviders.facebook,
github: config.authProviders.github,
twitter: config.authProviders.twitter,
gitlab: config.authProviders.gitlab,
dropbox: config.authProviders.dropbox,
ldap: config.authProviders.ldap,
google: config.authProviders.google,
saml: config.authProviders.saml,
oauth2: config.authProviders.oauth2,
email: config.authProviders.email
},
specialLinks: {
privacy: config.specialLinks.privacy,
termsOfUse: config.specialLinks.termsOfUse,
imprint: config.specialLinks.imprint,
}
}));
});
return null;
}
export { InitializeConfigStateFromApi }

View file

@ -0,0 +1,30 @@
import {useDispatch} from "react-redux";
import {getMe} from "../../api/user";
import {setUser} from "../../redux/user/actions";
import {LoginStatus, UserState} from "../../redux/user/types";
import React from "react";
const InitializeUserStateFromApi: React.FC = () => {
const dispatch = useDispatch();
getMe()
.then((me) => {
if (me.status === 200) {
return (me.json() as Promise<UserState>);
}
})
.then(user => {
if (!user) {
return;
}
dispatch(setUser({
status: LoginStatus.ok,
id: user.id,
name: user.name,
photo: user.photo,
}));
});
return null;
}
export { InitializeUserStateFromApi }

View file

@ -0,0 +1,28 @@
import React, {Fragment} from "react";
import {IconProp} from "@fortawesome/fontawesome-svg-core";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
export interface ExternalLinkProp {
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}/>&nbsp;
</Fragment>
:
null
}
{text}
</a>
)
}

View file

@ -0,0 +1,14 @@
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>
<LanguagePicker/>
<PoweredByLinks/>
<SocialLink/>
</footer>
);
}

View file

@ -0,0 +1,45 @@
import React from "react";
import {useTranslation} from "react-i18next";
const LanguagePicker: React.FC = () => {
const {i18n} = useTranslation();
const onChangeLang = (event: React.ChangeEvent<HTMLSelectElement>) => {
i18n.changeLanguage(event.currentTarget.value);
}
return (
<select className="ui-locale" 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>
</select>
)
}
export { LanguagePicker }

View file

@ -0,0 +1,51 @@
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'
}
]
const config = useSelector((state: ApplicationState) => state.applicationConfig);
const specialLinks = Object.entries(config.specialLinks)
.filter(([_, value]) => value !== "")
.map(([key, value]) => {
return {
href: value,
i18nKey: key
}
})
return (
<p>
<Trans i18nKey="poweredBy" components={[<ExternalLink href="https://codimd.org" text="CodiMD"/>]}/>
{
(defaultLinks.concat(specialLinks)).map(({href, i18nKey}) =>
<Fragment key={i18nKey}>
&nbsp;|&nbsp;
<TranslatedLink
href={href}
i18nKey={i18nKey}
/>
</Fragment>
)
}
</p>
)
}
export { PoweredByLinks }

View file

@ -0,0 +1,20 @@
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>
)
}
export { SocialLink }

View file

@ -0,0 +1,21 @@
import React from "react";
import {Trans, useTranslation} from "react-i18next";
export interface TranslatedLinkProps {
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>
)
}
export {TranslatedLink}

View file

@ -0,0 +1,28 @@
import React from "react";
import {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>
</Switch>
<Footer/>
</Container>);
}

View file

@ -0,0 +1,9 @@
.header-nav {
.nav-link {
border-bottom: 2px solid transparent
}
.nav-link.active {
border-bottom-color: #fff;
}
}

View file

@ -0,0 +1,50 @@
import React 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);
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 ?
<>
<span className={"mr-1"}>
<NewGuestNoteButton/>
</span>
<SignInButton/>
</>
:
<>
<span className={"mr-1"}>
<NewUserNoteButton/>
</span>
<UserDropdown/>
</>
}
</div>
</Navbar>
);
}
export {HeaderBar}

View file

@ -0,0 +1,17 @@
import {Nav} from "react-bootstrap";
import {LinkContainer} from "react-router-bootstrap";
import React from "react";
export interface HeaderNavLinkProps {
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>
);
}

View file

@ -0,0 +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";
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>)
}

View file

@ -0,0 +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";
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>
)
}

View file

@ -0,0 +1,19 @@
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();
return (
<LinkContainer to="/login" title={i18n.t("signIn")}>
<Button
variant="success"
size="sm"
>
<Trans i18nKey="signIn"/>
</Button>
</LinkContainer>
)
}

View file

@ -0,0 +1,4 @@
.user-avatar {
width: 16px;
height: 16px;
}

View file

@ -0,0 +1,53 @@
import {Dropdown} from "react-bootstrap";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import React from "react";
import {useDispatch, useSelector} from "react-redux";
import {ApplicationState} from "../../../../../redux";
import {LinkContainer} from "react-router-bootstrap";
import {clearUser} from "../../../../../redux/user/actions";
import "./user-dropdown.scss";
import {Trans} from "react-i18next";
export const UserDropdown: React.FC = () => {
const user = useSelector((state: ApplicationState) => state.user);
const dispatch = useDispatch()
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"/>&nbsp;
<Trans i18nKey="features"/>
</Dropdown.Item>
</LinkContainer>
<LinkContainer to={`/me/export`}>
<Dropdown.Item>
<FontAwesomeIcon icon="cloud-download-alt"/>&nbsp;
<Trans i18nKey="exportUserData"/>
</Dropdown.Item>
</LinkContainer>
<Dropdown.Item href="#">
<FontAwesomeIcon icon="trash"/>&nbsp;
<Trans i18nKey="deleteUser"/>
</Dropdown.Item>
<Dropdown.Item
onClick={() => {
dispatch(clearUser());
}}>
<FontAwesomeIcon icon="sign-out-alt"/>&nbsp;
<Trans i18nKey="signOut"/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>)
};

View file

@ -0,0 +1,398 @@
/*
* Globals
*/
/* Links */
a,
a:focus,
a:hover {
color: #fff;
}
/* Custom default button */
.btn-default,
.btn-default:hover,
.btn-default:focus {
color: #333;
text-shadow: none;
/* Prevent inheritence from `body` */
background-color: #fff;
border: 1px solid #fff;
}
/*
* Base structure
*/
html {
height: 100%;
}
html,
body {
background-color: #333;
}
body {
min-height: 100%;
color: #fff;
text-align: center;
text-shadow: 0 1px 3px rgba(0, 0, 0, .5);
}
/* Extra markup and styles for table-esque vertical and horizontal centering */
.site-wrapper {
padding: 10px;
display: table;
width: 100%;
height: 100vh;
/* For at least Firefox */
min-height: 100%;
-webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, .5);
box-shadow: inset 0 0 100px rgba(0, 0, 0, .5);
}
.site-wrapper-inner {
display: table-cell;
vertical-align: middle;
}
.cover-container {
width: 100%;
padding-top: 80px;
margin-right: auto;
margin-left: auto;
}
/* Padding for spacing */
.inner {
padding: 10px;
}
/*
* Header
*/
.masthead {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.masthead-brand {
margin-top: 10px;
margin-bottom: 10px;
}
.masthead-nav {
text-align: left;
max-width: 1000px;
margin: 0 auto;
padding-left: 10px;
padding-right: 10px;
}
.masthead-nav > li {
display: inline-block;
}
.masthead-nav > li + li {
margin-left: 20px;
}
.masthead-nav > li > a {
padding-right: 0;
padding-left: 0;
font-size: 16px;
font-weight: bold;
color: #fff;
/* IE8 proofing */
color: rgba(255, 255, 255, .75);
border-bottom: 2px solid transparent;
}
.masthead-nav > li > a:hover,
.masthead-nav > li > a:focus {
background-color: transparent;
border-bottom-color: #a9a9a9;
border-bottom-color: rgba(255, 255, 255, .25);
}
.masthead-nav > .active > a,
.masthead-nav > .active > a:hover,
.masthead-nav > .active > a:focus {
color: #fff;
border-bottom-color: #fff;
}
@media (min-width: 768px) {
.masthead-brand {
float: left;
}
.masthead-nav {
float: none;
}
.inner {
padding: 30px 25px;
}
}
/*
* Cover
*/
.cover {
padding: 0 20px;
}
.cover .btn-lg {
padding: 10px 20px;
font-weight: bold;
}
/*
* Footer
*/
.mastfoot {
color: #999;
/* IE8 proofing */
color: rgba(255, 255, 255, .5);
}
/*
* Affix and center
*/
@media (min-width: 768px) {
/* Pull out the header and footer */
.masthead {
position: absolute;
top: 0;
left: 0;
width: 100% !important;
}
.mastfoot {
position: fixed;
bottom: 0;
}
/* Start the vertical centering */
.site-wrapper-inner {
vertical-align: middle;
}
/* Handle the widths */
.masthead,
.mastfoot,
.cover-container {
width: 100%;
/* Must be percentage or pixels for horizontal alignment */
}
}
@media (min-width: 992px) {
.masthead,
.mastfoot,
.cover-container {
width: 1000px;
}
}
.section ul {
list-style: none;
}
/* custom */
html,
body {
overflow-x: hidden;
}
input {
color: black;
}
.mastfoot {
position: relative;
}
.select2-container-multi .select2-choices .select2-search-field input {
font-family: inherit;
padding: 5px 12px;
}
.select2-container {
margin: 0 auto !important;
}
.list {
width: 100%;
padding-left: 0;
display: -webkit-inline-flex;
display: -moz-inline-flex;
display: -ms-inline-flex;
display: -o-inline-flex;
display: inline-flex;
-webkit-flex-direction: row;
-moz-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-flow: row wrap;
-moz-flex-flow: row wrap;
-ms-flex-flow: row wrap;
flex-flow: row wrap;
-webkit-justify-content: flex-start;
-moz-justify-content: flex-start;
-ms-justify-content: flex-start;
justify-content: flex-start;
}
.list {
margin: 20px 0;
}
.list li {
padding: 0 10px;
}
.list li * {
word-break: break-word;
word-wrap: break-word;
}
.list li a {
text-decoration: none;
}
.list li p {
color: gray;
}
.list li .item {
padding: 5px 25px;
margin: 10px 0;
background: white;
border-radius: 5px;
color: black;
text-shadow: none;
min-height: 134px;
display: table;
min-width: 100%;
}
.list li .item .content {
display: table-cell;
vertical-align: middle;
}
.list li .item .content .tags {
line-height: 25px;
}
.list li .item .content .tags span {
display: inline-block;
line-height: 15px;
}
.form-inline {
padding: 0 10px;
}
.sort.asc {
text-decoration: overline;
}
.sort.desc {
text-decoration: underline;
}
.ui-avatar {
display: inline-block;
overflow: hidden;
line-height: 1;
vertical-align: middle;
border-radius: 3px;
}
.ui-avatar.circle {
border-radius: 50%;
}
.ui-history-close {
position: absolute;
right: 14px;
top: 15px;
font-size: 16px;
opacity: 0.5;
}
.ui-history-close:hover {
opacity: 1;
}
.ui-history-pin {
position: absolute;
left: 14px;
top: 15px;
font-size: 16px;
opacity: 0.2;
transition: opacity 0.2s ease-in-out;
-webkit-transition: opacity 0.2s ease-in-out;
}
.item:hover .ui-history-pin:hover {
opacity: 1;
}
.item .ui-history-pin.active {
opacity: 1;
color: #d43f3a;
}
.ui-or {
margin: 5px;
}
.ui-use-tags {
min-width: 172px;
max-width: 344px;
}
.modal-title {
text-align: left;
color: black;
}
.modal-body {
color: black;
}
.btn-file {
position: relative;
overflow: hidden;
}
.btn-file input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}
.social-foot {
line-height: 30px;
}
.social-foot > * {
line-height: 20px;
vertical-align: middle !important;
display: inline-block !important;
}
.btn-link, .btn-link:hover, .btn-link:focus, .btn-link:active {
color: white;
}
.screenshot {
margin: 30px auto;
width: 100%;
border-radius: 3px;
}
select {
color: black;
}
@media (max-width: 768px) {
span.ui-or {
display: block;
}
.ui-use-tags {
max-width: 100%;
}
}
/* for all pages should include this */
body {
font-smoothing: subpixel-antialiased !important;
-webkit-font-smoothing: subpixel-antialiased !important;
-moz-osx-font-smoothing: auto !important;
text-shadow: 0 0 1em transparent, 1px 1px 1.2px rgba(0, 0, 0, 0.004);
/*text-rendering: optimizeLegibility;*/
-webkit-overflow-scrolling: touch;
font-family: "Source Sans Pro", Helvetica, Arial, sans-serif;
letter-spacing: 0.025em;
}
:focus, .focus {
outline: none !important;
}
::-moz-focus-inner {
border: 0 !important;
}
/* manual fix for bootstrap issue 14040, there is an unnecessary padding-right on modal open */
body.modal-open {
overflow-y: auto;
padding-right: 0 !important;
}

View file

@ -0,0 +1,208 @@
/* latin-ext */
@font-face {
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 300;
src: local('Source Code Pro Light'), local('SourceCodePro-Light'), url('fonts/SourceCodePro-Light.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 300;
src: local('Source Code Pro Light'), local('SourceCodePro-Light'), url('fonts/SourceCodePro-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;
}
/* latin-ext */
@font-face {
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 400;
src: local('Source Code Pro'), local('SourceCodePro-Regular'), url('fonts/SourceCodePro-Regular.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 400;
src: local('Source Code Pro'), local('SourceCodePro-Regular'), url('fonts/SourceCodePro-Regular.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;
}
/* latin-ext */
@font-face {
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 500;
src: local('Source Code Pro Medium'), local('SourceCodePro-Medium'), url('fonts/SourceCodePro-Medium.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 500;
src: local('Source Code Pro Medium'), local('SourceCodePro-Medium'), url('fonts/SourceCodePro-Medium.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 */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('fonts/SourceCodePro-Medium.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('fonts/SourceSansPro-Light.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
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');
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 */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url('fonts/SourceSansPro-Regular.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url('fonts/SourceSansPro-Regular.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url('fonts/SourceSansPro-Regular.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 */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), url('fonts/SourceSansPro-Semibold.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), url('fonts/SourceSansPro-Semibold.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), url('fonts/SourceSansPro-Semibold.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 */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightIt'), url('fonts/SourceSansPro-LightItalic.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightIt'), url('fonts/SourceSansPro-LightItalic.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightIt'), url('fonts/SourceSansPro-LightItalic.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 */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: local('Source Sans Pro Italic'), local('SourceSansPro-It'), url('fonts/SourceSansPro-Italic.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: local('Source Sans Pro Italic'), local('SourceSansPro-It'), url('fonts/SourceSansPro-Italic.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: local('Source Sans Pro Italic'), local('SourceSansPro-It'), url('fonts/SourceSansPro-Italic.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 */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 600;
src: local('Source Sans Pro Semibold Italic'), local('SourceSansPro-SemiboldIt'), url('fonts/SourceSansPro-SemiboldItalic.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 600;
src: local('Source Sans Pro Semibold Italic'), local('SourceSansPro-SemiboldIt'), url('fonts/SourceSansPro-SemiboldItalic.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 600;
src: local('Source Sans Pro Semibold Italic'), local('SourceSansPro-SemiboldIt'), url('fonts/SourceSansPro-SemiboldItalic.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;
}
/* latin-ext */
@font-face {
font-family: 'Source Serif Pro';
font-style: normal;
font-weight: 400;
src: local('Source Serif Pro'), local('SourceSerifPro-Regular'), url('fonts/SourceSerifPro-Regular.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Serif Pro';
font-style: normal;
font-weight: 400;
src: local('Source Serif Pro'), local('SourceSerifPro-Regular'), url('fonts/SourceSerifPro-Regular.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;
}

View file

@ -0,0 +1,23 @@
@import "../../../../../node_modules/bootstrap/scss/bootstrap";
@import "font-pack";
//@import "cover.scss";
html {
height: 100%;
}
html,
body {
background-color: #333;
}
body {
min-height: 100%;
color: #fff;
text-align: center;
//text-shadow: 0 1px 3px rgba(0, 0, 0, .5);
font-family: "Source Sans Pro", Helvetica, Arial, sans-serif;
}
*:focus {
outline: 0 !important;
}

View file

@ -0,0 +1,21 @@
.history-pin {
opacity: 0.2;
transition: opacity 0.2s ease-in-out, color 0.2s ease-in-out;
&:hover {
opacity: 1;
}
&.active {
color: #d43f3a;
opacity: 1;
}
}
.history-close {
opacity: 0.5;
&:hover {
opacity: 1;
}
}

View file

@ -0,0 +1,13 @@
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import React from "react";
const CloseButton: React.FC = () => {
return (
<FontAwesomeIcon
className="history-close"
icon="times"
/>
);
}
export { CloseButton }

View file

@ -0,0 +1,19 @@
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import React from "react";
export interface PinButtonProps {
pin: boolean;
onPinChange: () => void;
}
const PinButton: React.FC<PinButtonProps> = ({pin, onPinChange}) => {
return (
<FontAwesomeIcon
icon="thumbtack"
className={`history-pin ${pin? 'active' : ''}`}
onClick={onPinChange}
/>
);
}
export { PinButton }

View file

@ -0,0 +1,33 @@
import React from 'react'
import {HistoryInput} from '../history'
import {Badge, Card} from 'react-bootstrap'
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
import {format, formatDistance} from 'date-fns'
import "../common/button.scss"
import {PinButton} from "../common/pin-button";
import {CloseButton} from "../common/close-button";
export const HistoryCard: React.FC<HistoryInput> = ({pinned, title, lastVisited, tags, onPinChange}) => {
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">
<PinButton pin={pinned} onPinChange={onPinChange}/>
<Card.Title className="m-0 mt-3">{title}</Card.Title>
<CloseButton/>
</div>
<Card.Body>
<div className="text-black-50">
<FontAwesomeIcon icon="clock"/> {formatDistance(lastVisited, new Date())}<br/>
{format(lastVisited, 'EEE, LLL d, YYY h:mm a')}
<div children=
{
tags.map((tag) => <Badge variant={"dark"} key={tag}>{tag}</Badge>)
}
/>
</div>
</Card.Body>
</Card>
</div>
)
}

View file

@ -0,0 +1,19 @@
import React from "react";
import {HistoryInput} from "../history";
import {format} from "date-fns";
import {PinButton} from "../common/pin-button";
import {CloseButton} from "../common/close-button";
export const HistoryTableRow: React.FC<HistoryInput> = ({pinned, title, lastVisited, onPinChange}) => {
return (
<tr>
<td>{title}</td>
<td>{format(lastVisited, 'EEE, LLL d, YYY h:mm a')}</td>
<td>
<PinButton pin={pinned} onPinChange={onPinChange}/>
&nbsp;
<CloseButton/>
</td>
</tr>
)
}

View file

@ -0,0 +1,21 @@
import React from "react";
import {Table} from "react-bootstrap"
const HistoryTable: React.FC = ({children}) => {
return (
<Table striped bordered hover size="sm" variant="dark">
<thead>
<tr>
<th>Title</th>
<th>Last visited</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{children}
</tbody>
</Table>
)
}
export { HistoryTable }

View file

@ -0,0 +1,103 @@
import React, {Fragment, useEffect, useState} from 'react'
import {HistoryCard} from "./history-card/history-card";
import {HistoryTable} from "./history-table/history-table";
import {HistoryTableRow} from './history-table/history-table-row';
import {ToggleButton, ToggleButtonGroup} from 'react-bootstrap';
interface HistoryChange {
onPinChange: () => void,
}
interface ViewState {
viewState: ViewStateEnum
}
enum ViewStateEnum {
card,
table
}
export type HistoryInput = HistoryEntry & HistoryChange
interface HistoryEntry {
id: string,
title: string,
lastVisited: Date,
tags: string[],
pinned: boolean
}
function loadHistoryFromLocalStore() {
const historyJsonString = window.localStorage.getItem("notehistory");
return historyJsonString ? JSON.parse(historyJsonString) : [];
}
const History: React.FC = () => {
const [historyEntries, setHistoryEntries] = useState<HistoryEntry[]>([])
const [viewState, setViewState] = useState<ViewState>({
viewState: ViewStateEnum.card
})
useEffect(() => {
let history = loadHistoryFromLocalStore();
setHistoryEntries(history);
}, [])
return (
<Fragment>
<h1>History</h1>
<ToggleButtonGroup type="radio" name="options" defaultValue={ViewStateEnum.card} className="mb-2"
onChange={(newState: ViewStateEnum) => setViewState(() => ({viewState: newState}))}>
<ToggleButton value={ViewStateEnum.card}>Card</ToggleButton>
<ToggleButton value={ViewStateEnum.table}>Table</ToggleButton>
</ToggleButtonGroup>
{
viewState.viewState === ViewStateEnum.card ? (
<div className="d-flex flex-wrap">
{
historyEntries.length === 0 ?
''
:
historyEntries.map((entry) =>
<HistoryCard
id={entry.id}
tags={entry.tags}
pinned={entry.pinned}
title={entry.title}
lastVisited={entry.lastVisited}
onPinChange={() => {
// setHistoryEntries((prev: HistoryEntry) => {
// return {...prev, pinned: !prev.pinned};
// });
}}
/>)
}
</div>
) : (
<HistoryTable>
{
historyEntries.length === 0 ?
''
:
historyEntries.map((entry) =>
<HistoryTableRow
id={entry.id}
tags={entry.tags}
pinned={entry.pinned}
title={entry.title}
lastVisited={entry.lastVisited}
onPinChange={() => {
// setEntry((prev: HistoryEntry) => {
// return {...prev, pinned: !prev.pinned};
// });
}}
/>)
}
</HistoryTable>
)
}
</Fragment>
)
}
export {History}

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

View file

@ -0,0 +1,82 @@
import React from 'react'
import screenshot from './img/screenshot.png';
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {Button} from 'react-bootstrap';
import {Trans, useTranslation} from "react-i18next";
import {Link} from "react-router-dom";
const Intro: React.FC = () => {
// ToDo replace this with comment
const url = "http://localhost:3000";//useServerUrl();
useTranslation();
return (
<div id="home" className="section">
<div className="inner cover">
<h1 className="cover-heading">
<FontAwesomeIcon icon="file-alt"/> CodiMD
</h1>
<p className="lead mb-5">
<Trans i18nKey="slogan"/>
</p>
<div className="mb-5">
<Link to="/login">
<Button
variant="success"
size="lg"
style={{minWidth: "200px"}}
>
<Trans i18nKey="signIn"/>
</Button>
</Link>
<span className="m-2">
or
</span>
<Button
variant="primary"
size="lg"
href={`${url}/features`}
style={{minWidth: "200px"}}
>
<Trans i18nKey="exploreFeatures"/>
</Button>
</div>
<img alt="CodiMD Screenshot" src={screenshot} className="screenshot img-fluid mb-5"/>
<div className="lead row mb-5" style={{width: '90%', margin: '0 auto'}}>
<div className="col-md-4 inner">
<a href={`${url}/features#Share-Notes`} className="text-light">
<FontAwesomeIcon icon="bolt" size="3x"/>
<h5>
<Trans i18nKey="featureCollaboration"/>
</h5>
</a>
</div>
<div className="col-md-4 inner">
<a href={`${url}/features#MathJax`} className="text-light">
<FontAwesomeIcon icon="chart-bar" size="3x"/>
<h5>
<Trans i18nKey="featureMathJax"/>
</h5>
</a>
</div>
<div className="col-md-4 inner">
<a href={`${url}/features#Slide-Mode`} className="text-light">
<FontAwesomeIcon icon="tv" size="3x"/>
<h5>
<Trans i18nKey="featureSlides"/>
</h5>
</a>
</div>
</div>
</div>
</div>
)
}
export {Intro}

View file

@ -0,0 +1,48 @@
.btn.btn-icon {
color: #FFFFFF;
@mixin button($color) {
background-color: $color;
&:hover {
background-color: darken($color, 10%);
}
}
&.btn-social-dropbox {
@include button(#1087DD);
}
&.btn-social-facebook {
@include button(#3B5998);
}
&.btn-social-github {
@include button(#444444);
}
&.btn-social-gitlab {
@include button(#FA7035);
}
&.btn-social-google {
@include button(#DD4B39);
}
&.btn-social-twitter {
@include button(#55ACEE);
}
}
.btn-social-button {
padding: 0.375rem 0.375rem;
border-right: 1px solid rgba(0, 0, 0, 0.2);
display: flex;
.social-icon {
font-size: 1.5em;
}
}
.btn-social-text {
padding: 0.375rem 0.75rem;
}

View file

@ -0,0 +1,25 @@
import React from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import "./icon-button.scss";
import {IconProp} from "@fortawesome/fontawesome-svg-core";
export interface SocialButtonProps {
backgroundClass: string,
href: string
icon: IconProp
title?: string
}
export const IconButton: React.FC<SocialButtonProps> = ({title, backgroundClass, href, icon, children}) => {
return (
<a href={href} title={title}
className={"btn btn-icon p-0 d-inline-flex align-items-stretch " + backgroundClass}>
<span className="btn-social-button d-flex align-items-center">
<FontAwesomeIcon icon={icon} className={"social-icon"}/>
</span>
<span className="btn-social-text d-flex align-items-center">
{children}
</span>
</a>
)
}

View file

@ -0,0 +1,39 @@
import {Trans, useTranslation} from "react-i18next";
import {Button, Form} from "react-bootstrap";
import React, { Fragment } from "react";
import {useDispatch} from "react-redux";
import {setUser} from "../../../../../redux/user/actions";
import {LoginStatus} from "../../../../../redux/user/types";
const ViaEMail: React.FC = () => {
useTranslation();
const dispatch = useDispatch();
const login = () => {
dispatch(setUser({photo: "https://robohash.org/testy.png", name: "Test", status: LoginStatus.ok}));
}
return (
<Fragment>
<h5 className="center">
<Trans i18nKey="signInVia" values={{service: "E-Mail"}}/>
</h5>
<Form>
<Form.Group controlId="formBasicEmail">
<Form.Control type="email" size="sm" placeholder="E-Mail" />
</Form.Group>
<Form.Group controlId="formBasicPassword">
<Form.Control type="password" size="sm" placeholder="Password" />
</Form.Group>
<Button
size="sm"
variant="primary"
onClick={login}
>
<Trans i18nKey="signIn"/>
</Button>
</Form>
</Fragment>
);
}
export { ViaEMail }

View file

@ -0,0 +1,30 @@
import React, {Fragment} from "react";
import {Trans} from "react-i18next";
import {Button, Form} from "react-bootstrap";
const ViaLdap: React.FC = () => {
return (
<Fragment>
<h5 className="center">
<Trans i18nKey="signInVia" values={{service: "LDAP"}}/>
</h5>
<Form>
<Form.Group controlId="formBasicUsername">
<Form.Control type="text" size="sm" placeholder="Username" />
</Form.Group>
<Form.Group controlId="formBasicPassword">
<Form.Control type="password" size="sm" placeholder="Password" />
</Form.Group>
<Button
size="sm"
variant="primary"
>
<Trans i18nKey="signIn"/>
</Button>
</Form>
</Fragment>
)
};
export { ViaLdap }

View file

@ -0,0 +1,115 @@
import React from "react";
import {IconProp} from "@fortawesome/fontawesome-svg-core";
import {IconButton} from "./icon-button/icon-button";
export enum OAuth2Type {
'DROPBOX'="dropbox",
'FACEBOOK'="facebook",
'GITHUB'="github",
'GITLAB'="gitlab",
'GOOGLE'="google",
'OAUTH2'="oauth2",
'SAML'="saml",
'TWITTER'="twitter"
}
type OAuth2Map = (oauth2type: OAuth2Type) => {
name: string,
icon: IconProp,
className: string,
url: string
};
const buildBackendAuthUrl = (backendName: string) => {
return `https://localhost:3000/auth/${backendName}`
};
const getMetadata: OAuth2Map = (oauth2type: OAuth2Type) => {
switch (oauth2type) {
case OAuth2Type.DROPBOX:
return {
name: "Dropbox",
icon: ["fab", "dropbox"],
className: "btn-social-dropbox",
url: buildBackendAuthUrl("dropbox")
}
case OAuth2Type.FACEBOOK:
return {
name: "Facebook",
icon: ["fab", "facebook"],
className: "btn-social-facebook",
url: buildBackendAuthUrl("facebook")
}
case OAuth2Type.GITHUB:
return {
name: "GitHub",
icon: ["fab", "github"],
className: "btn-social-github",
url: buildBackendAuthUrl("github")
}
case OAuth2Type.GITLAB:
return {
name: "GitLab",
icon: ["fab", "gitlab"],
className: "btn-social-gitlab",
url: buildBackendAuthUrl("gitlab")
}
case OAuth2Type.GOOGLE:
return {
name: "Google",
icon: ["fab", "google"],
className: "btn-social-google",
url: buildBackendAuthUrl("google")
}
case OAuth2Type.OAUTH2:
return {
name: "oAuth2",
icon: "share",
className: "btn-primary",
url: buildBackendAuthUrl("oauth2")
}
case OAuth2Type.SAML:
return {
name: "SAML",
icon: "users",
className: "btn-success",
url: buildBackendAuthUrl("saml")
}
case OAuth2Type.TWITTER:
return {
name: "Twitter",
icon: ["fab", "twitter"],
className: "btn-social-twitter",
url: buildBackendAuthUrl("twitter")
}
default:
return {
name: "",
icon: "exclamation",
className: "",
url: "#"
}
}
}
export interface ViaOAuth2Props {
oauth2Type: OAuth2Type;
optionalName?: string;
}
const ViaOAuth2: React.FC<ViaOAuth2Props> = ({oauth2Type, optionalName}) => {
const {name, icon, className, url} = getMetadata(oauth2Type);
const text = !!optionalName ? optionalName : name;
return (
<IconButton
backgroundClass={className}
icon={icon}
href={url}
title={text}
>
{text}
</IconButton>
)
}
export {ViaOAuth2}

Some files were not shown because too many files have changed in this diff Show more