mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-13 22:54:42 -04:00
parent
5baef25b21
commit
0e8d2f1639
13 changed files with 188 additions and 63 deletions
|
@ -22,5 +22,10 @@
|
||||||
"privacy": "test",
|
"privacy": "test",
|
||||||
"termsOfUse": "test",
|
"termsOfUse": "test",
|
||||||
"imprint": "test"
|
"imprint": "test"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"version": "mock",
|
||||||
|
"sourceCodeUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||||
|
"issueTrackerUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@
|
||||||
"deleteUser": "Delete user",
|
"deleteUser": "Delete user",
|
||||||
"exportUserData": "Export user data",
|
"exportUserData": "Export user data",
|
||||||
"Help us translating on %s": "Help us translating on %s",
|
"Help us translating on %s": "Help us translating on %s",
|
||||||
"sourceCode": "Source Code",
|
"sourceCode": "Read the source code",
|
||||||
"Register": "Register",
|
"Register": "Register",
|
||||||
"poweredBy": "Powered by <0></0>",
|
"poweredBy": "Powered by <0></0>",
|
||||||
"Help us translating": "Help us translating",
|
"Help us translating": "Help us translating",
|
||||||
|
@ -128,5 +128,12 @@
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"cards": "Cards",
|
"cards": "Cards",
|
||||||
"table": "Table",
|
"table": "Table",
|
||||||
"errorOpenIdLogin": "Invalid OpenID provided"
|
"errorOpenIdLogin": "Invalid OpenID provided",
|
||||||
|
"versionInfo": "Version info",
|
||||||
|
"successfullyCopied": "Copied!",
|
||||||
|
"serverVersion": "Server version",
|
||||||
|
"clientVersion": "Client version",
|
||||||
|
"youAreUsing": "You are using",
|
||||||
|
"close": "Close",
|
||||||
|
"issueTracker": "Found a bug? Fill an issue!"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,36 @@
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
|
||||||
import { TranslatedLink } from './translated-link'
|
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { ExternalLink } from './external-link'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { ApplicationState } from '../../../../redux'
|
import { ApplicationState } from '../../../../redux'
|
||||||
|
import { ExternalLink } from '../../../links/external-link'
|
||||||
|
import { TranslatedExternalLink } from '../../../links/translated-external-link'
|
||||||
|
import { VersionInfo } from '../version-info/version-info'
|
||||||
|
|
||||||
export const PoweredByLinks: React.FC = () => {
|
export const PoweredByLinks: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
const defaultLinks =
|
const defaultLinks =
|
||||||
{
|
{
|
||||||
releases: '/s/release-notes',
|
releases: '/n/release-notes'
|
||||||
sourceCode: 'https://github.com/codimd/server/tree/41b13e71b6b1d499238c04b15d65e3bd76442f1d'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = useSelector((state: ApplicationState) => state.backendConfig)
|
const config = useSelector((state: ApplicationState) => state.backendConfig)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
<Trans i18nKey="poweredBy" components={[<ExternalLink href="https://codimd.org" text="CodiMD"/>]}/>
|
<Trans i18nKey="poweredBy">
|
||||||
|
<ExternalLink href="https://codimd.org" text="CodiMD"/>
|
||||||
|
</Trans>
|
||||||
{
|
{
|
||||||
Object.entries({ ...defaultLinks, ...(config.specialLinks) }).map(([i18nKey, href]) =>
|
Object.entries({ ...defaultLinks, ...(config.specialLinks) }).map(([i18nKey, href]) =>
|
||||||
<Fragment key={i18nKey}>
|
<Fragment key={i18nKey}>
|
||||||
|
|
|
|
||||||
<TranslatedLink
|
<TranslatedExternalLink href={href} i18nKey={i18nKey}/>
|
||||||
href={href}
|
|
||||||
i18nKey={i18nKey}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
<VersionInfo/>
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { ExternalLink } from './external-link'
|
import { ExternalLink } from '../../../links/external-link'
|
||||||
|
|
||||||
const SocialLink: React.FC = () => {
|
const SocialLink: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
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 }
|
|
51
src/components/landing/layout/version-info/version-info.tsx
Normal file
51
src/components/landing/layout/version-info/version-info.tsx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import React, { Fragment, useState } from 'react'
|
||||||
|
import { Button, Col, Modal, Row } from 'react-bootstrap'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { ApplicationState } from '../../../../redux'
|
||||||
|
import frontendVersion from '../../../../version.json'
|
||||||
|
import { TranslatedExternalLink } from '../../../links/translated-external-link'
|
||||||
|
import { VersionInputField } from './version-input-field'
|
||||||
|
|
||||||
|
export const VersionInfo: React.FC = () => {
|
||||||
|
const [show, setShow] = useState(false)
|
||||||
|
|
||||||
|
const handleClose = () => setShow(false)
|
||||||
|
const handleShow = () => setShow(true)
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const serverVersion = useSelector((state: ApplicationState) => state.backendConfig.version)
|
||||||
|
|
||||||
|
const column = (title: string, version: string, sourceCodeLink: string, issueTrackerLink: string) => (
|
||||||
|
<Col md={6} className={'flex-column'}>
|
||||||
|
<h5>{title}</h5>
|
||||||
|
<VersionInputField version={version}/>
|
||||||
|
{sourceCodeLink
|
||||||
|
? <TranslatedExternalLink i18nKey={'sourceCode'} className={'btn btn-sm btn-primary d-block mb-2'} href={sourceCodeLink}/> : null}
|
||||||
|
{issueTrackerLink
|
||||||
|
? <TranslatedExternalLink i18nKey={'issueTracker'} className={'btn btn-sm btn-primary d-block mb-2'} href={issueTrackerLink}/> : null}
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Link to={'#'} className={'text-light'} onClick={handleShow}><Trans i18nKey={'versionInfo'}/></Link>
|
||||||
|
<Modal show={show} onHide={handleClose} animation={true}>
|
||||||
|
<Modal.Body className="text-dark">
|
||||||
|
<h3><Trans i18nKey={'youAreUsing'}/></h3>
|
||||||
|
<Row>
|
||||||
|
{column(t('serverVersion'), serverVersion.version, serverVersion.sourceCodeUrl, serverVersion.issueTrackerUrl)}
|
||||||
|
{column(t('clientVersion'), frontendVersion.version, frontendVersion.sourceCodeUrl, frontendVersion.issueTrackerUrl)}
|
||||||
|
</Row>
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<Button variant="secondary" onClick={handleClose}>
|
||||||
|
<Trans i18nKey={'close'}/>
|
||||||
|
</Button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import React, { Fragment, useRef, useState } from 'react'
|
||||||
|
import { Button, FormControl, InputGroup, Overlay, Tooltip } from 'react-bootstrap'
|
||||||
|
import { Trans } from 'react-i18next'
|
||||||
|
|
||||||
|
export interface VersionInputFieldProps {
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VersionInputField: React.FC<VersionInputFieldProps> = ({ version }) => {
|
||||||
|
const inputField = useRef<HTMLInputElement>(null)
|
||||||
|
const [showCopiedTooltip, setShowCopiedTooltip] = useState(false)
|
||||||
|
|
||||||
|
const copyToClipboard = (content: string) => {
|
||||||
|
navigator.clipboard.writeText(content).then(() => {
|
||||||
|
setShowCopiedTooltip(true)
|
||||||
|
setTimeout(() => { setShowCopiedTooltip(false) }, 2000)
|
||||||
|
}).catch(() => {
|
||||||
|
console.error("couldn't copy")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Overlay target={inputField} show={showCopiedTooltip} placement="top">
|
||||||
|
{(props) => (
|
||||||
|
<Tooltip id={'copied_' + version} {...props}>
|
||||||
|
<Trans i18nKey={'successfullyCopied'}/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Overlay>
|
||||||
|
|
||||||
|
<InputGroup className="mb-3">
|
||||||
|
<FormControl readOnly={true} ref={inputField} className={'text-center'} value={version} />
|
||||||
|
<InputGroup.Append>
|
||||||
|
<Button variant="outline-secondary" onClick={() => copyToClipboard(version)} title={'Copy'}>
|
||||||
|
<FontAwesomeIcon icon={'copy'}/>
|
||||||
|
</Button>
|
||||||
|
</InputGroup.Append>
|
||||||
|
</InputGroup>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,19 +1,20 @@
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { IconProp } from '../../../../utils/iconProp'
|
import { IconProp } from '../../utils/iconProp'
|
||||||
|
|
||||||
export interface ExternalLinkProp {
|
export interface ExternalLinkProp {
|
||||||
href: string;
|
href: string;
|
||||||
text: string;
|
text: string;
|
||||||
icon?: IconProp;
|
icon?: IconProp;
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExternalLink: React.FC<ExternalLinkProp> = ({ href, text, icon }) => {
|
export const ExternalLink: React.FC<ExternalLinkProp> = ({ href, text, icon, className = 'text-light' }) => {
|
||||||
return (
|
return (
|
||||||
<a href={href}
|
<a href={href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-light">
|
className={className}>
|
||||||
{
|
{
|
||||||
icon
|
icon
|
||||||
? <Fragment>
|
? <Fragment>
|
20
src/components/links/translated-external-link.tsx
Normal file
20
src/components/links/translated-external-link.tsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { IconProp } from '../../utils/iconProp'
|
||||||
|
import { ExternalLink } from './external-link'
|
||||||
|
|
||||||
|
export interface TranslatedLinkProps {
|
||||||
|
i18nKey: string;
|
||||||
|
href: string;
|
||||||
|
icon?: IconProp;
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const TranslatedExternalLink: React.FC<TranslatedLinkProps> = ({ i18nKey, ...props }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<ExternalLink text={t(i18nKey)} {...props}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { TranslatedExternalLink }
|
|
@ -6,6 +6,7 @@ import {
|
||||||
faClock,
|
faClock,
|
||||||
faCloudDownloadAlt,
|
faCloudDownloadAlt,
|
||||||
faComment,
|
faComment,
|
||||||
|
faCopy,
|
||||||
faDownload,
|
faDownload,
|
||||||
faFileAlt,
|
faFileAlt,
|
||||||
faGlobe,
|
faGlobe,
|
||||||
|
@ -37,5 +38,5 @@ export const setUpFontAwesome: () => void = () => {
|
||||||
library.add(faBolt, faPlus, faChartBar, faTv, faFileAlt, faCloudDownloadAlt,
|
library.add(faBolt, faPlus, faChartBar, faTv, faFileAlt, faCloudDownloadAlt,
|
||||||
faTrash, faSignOutAlt, faComment, faDiscourse, faMastodon, faGlobe,
|
faTrash, faSignOutAlt, faComment, faDiscourse, faMastodon, faGlobe,
|
||||||
faThumbtack, faClock, faTimes, faGithub, faGitlab, faGoogle, faFacebook,
|
faThumbtack, faClock, faTimes, faGithub, faGitlab, faGoogle, faFacebook,
|
||||||
faDropbox, faTwitter, faUsers, faAddressCard, faSort, faDownload, faUpload, faTrash, faSync, faSortUp, faSortDown)
|
faDropbox, faTwitter, faUsers, faAddressCard, faSort, faDownload, faUpload, faTrash, faSync, faSortUp, faSortDown, faCopy)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,11 @@ export const initialState: BackendConfigState = {
|
||||||
privacy: '',
|
privacy: '',
|
||||||
termsOfUse: '',
|
termsOfUse: '',
|
||||||
imprint: ''
|
imprint: ''
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
version: '',
|
||||||
|
sourceCodeUrl: '',
|
||||||
|
issueTrackerUrl: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,43 +3,50 @@ import { Action } from 'redux'
|
||||||
export const SET_BACKEND_CONFIG_ACTION_TYPE = 'backend-config/set'
|
export const SET_BACKEND_CONFIG_ACTION_TYPE = 'backend-config/set'
|
||||||
|
|
||||||
export interface BackendConfigState {
|
export interface BackendConfigState {
|
||||||
allowAnonymous: boolean,
|
allowAnonymous: boolean,
|
||||||
authProviders: AuthProvidersState,
|
authProviders: AuthProvidersState,
|
||||||
customAuthNames: CustomAuthNames,
|
customAuthNames: CustomAuthNames,
|
||||||
specialLinks: SpecialLinks,
|
specialLinks: SpecialLinks,
|
||||||
|
version: BackendVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BackendVersion {
|
||||||
|
version: string,
|
||||||
|
sourceCodeUrl: string
|
||||||
|
issueTrackerUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthProvidersState {
|
export interface AuthProvidersState {
|
||||||
facebook: boolean,
|
facebook: boolean,
|
||||||
github: boolean,
|
github: boolean,
|
||||||
twitter: boolean,
|
twitter: boolean,
|
||||||
gitlab: boolean,
|
gitlab: boolean,
|
||||||
dropbox: boolean,
|
dropbox: boolean,
|
||||||
ldap: boolean,
|
ldap: boolean,
|
||||||
google: boolean,
|
google: boolean,
|
||||||
saml: boolean,
|
saml: boolean,
|
||||||
oauth2: boolean,
|
oauth2: boolean,
|
||||||
email: boolean,
|
email: boolean,
|
||||||
openid: boolean,
|
openid: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomAuthNames {
|
export interface CustomAuthNames {
|
||||||
ldap: string;
|
ldap: string;
|
||||||
oauth2: string;
|
oauth2: string;
|
||||||
saml: string;
|
saml: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpecialLinks {
|
export interface SpecialLinks {
|
||||||
privacy: string,
|
privacy: string,
|
||||||
termsOfUse: string,
|
termsOfUse: string,
|
||||||
imprint: string,
|
imprint: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetBackendConfigAction extends Action {
|
export interface SetBackendConfigAction extends Action {
|
||||||
type: string;
|
type: string;
|
||||||
payload: {
|
payload: {
|
||||||
state: BackendConfigState;
|
state: BackendConfigState;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BackendConfigActions = SetBackendConfigAction;
|
export type BackendConfigActions = SetBackendConfigAction;
|
||||||
|
|
5
src/version.json
Normal file
5
src/version.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"version": "0.0",
|
||||||
|
"sourceCodeUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||||
|
"issueTrackerUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue