mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-04-08 16:51:33 +00:00
Enhance share dialog (#860)
This commit is contained in:
parent
1c6e6e10fb
commit
721c8c0e5a
10 changed files with 120 additions and 25 deletions
|
@ -57,6 +57,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|||
- Markdown files can be imported into an existing note directly from the editor.
|
||||
- The table button in the toolbar opens an overlay where the user can choose the number of columns and rows
|
||||
- A toggle in the editor preferences for turning ligatures on and off.
|
||||
- Easier possibility to share notes via native share-buttons on supported devices.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -389,7 +389,9 @@
|
|||
},
|
||||
"shareLink": {
|
||||
"title": "Share link",
|
||||
"viewOnlyDescription": "This link points to a read-only version of this note. You can use this e.g. for feedback from friends and colleagues."
|
||||
"editorDescription": "This link points to this note in the editor as you currently see it.",
|
||||
"viewOnlyDescription": "This link points to a read-only version of this note. You can use this e.g. for feedback from friends and colleagues.",
|
||||
"slidesDescription": "This link points to the presentation view of the slides."
|
||||
},
|
||||
"preferences": {
|
||||
"title": "Preferences",
|
||||
|
|
|
@ -4,31 +4,52 @@ SPDX-FileCopyrightText: 2020 The HedgeDoc developers (see AUTHORS file)
|
|||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useRef } from 'react'
|
||||
import React, { Fragment, useCallback, useRef } from 'react'
|
||||
import { Button, FormControl, InputGroup } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ForkAwesomeIcon } from '../../fork-awesome/fork-awesome-icon'
|
||||
import { ShowIf } from '../../show-if/show-if'
|
||||
import { CopyOverlay } from '../copy-overlay'
|
||||
|
||||
export interface CopyableFieldProps {
|
||||
content: string
|
||||
nativeShareButton?: boolean
|
||||
url?: string
|
||||
}
|
||||
|
||||
export const CopyableField: React.FC<CopyableFieldProps> = ({ content }) => {
|
||||
export const CopyableField: React.FC<CopyableFieldProps> = ({ content, nativeShareButton, url }) => {
|
||||
useTranslation()
|
||||
const button = useRef<HTMLButtonElement>(null)
|
||||
const copyButton = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const doShareAction = useCallback(() => {
|
||||
navigator.share({
|
||||
text: content,
|
||||
url: url
|
||||
}).catch(err => {
|
||||
console.error('Native sharing failed: ', err)
|
||||
})
|
||||
}, [content, url])
|
||||
|
||||
const sharingSupported = typeof navigator.share === 'function'
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<InputGroup className="my-3">
|
||||
<FormControl readOnly={true} className={'text-center'} value={content} />
|
||||
<InputGroup.Append>
|
||||
<Button variant="outline-secondary" ref={button} title={'Copy'}>
|
||||
<Button variant="outline-secondary" ref={copyButton} title={'Copy'}>
|
||||
<ForkAwesomeIcon icon='files-o'/>
|
||||
</Button>
|
||||
</InputGroup.Append>
|
||||
<ShowIf condition={!!nativeShareButton && sharingSupported}>
|
||||
<InputGroup.Append>
|
||||
<Button variant="outline-secondary" title={'Share'} onClick={doShareAction}>
|
||||
<ForkAwesomeIcon icon='share-alt'/>
|
||||
</Button>
|
||||
</InputGroup.Append>
|
||||
</ShowIf>
|
||||
</InputGroup>
|
||||
<CopyOverlay content={content} clickComponent={button}/>
|
||||
<CopyOverlay content={content} clickComponent={copyButton}/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -13,9 +13,9 @@ import { setEditorMode } from '../../../redux/editor/methods'
|
|||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||
|
||||
export enum EditorMode {
|
||||
PREVIEW,
|
||||
BOTH,
|
||||
EDITOR,
|
||||
PREVIEW = 'view',
|
||||
BOTH = 'both',
|
||||
EDITOR = 'edit',
|
||||
}
|
||||
|
||||
export const EditorViewMode: React.FC = () => {
|
||||
|
|
|
@ -4,27 +4,54 @@ SPDX-FileCopyrightText: 2020 The HedgeDoc developers (see AUTHORS file)
|
|||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import equal from 'fast-deep-equal'
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { Modal } from 'react-bootstrap'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useFrontendBaseUrl } from '../../../../hooks/common/use-frontend-base-url'
|
||||
import { ApplicationState } from '../../../../redux'
|
||||
import { CopyableField } from '../../../common/copyable/copyable-field/copyable-field'
|
||||
import { TranslatedIconButton } from '../../../common/icon-button/translated-icon-button'
|
||||
import { CommonModal } from '../../../common/modals/common-modal'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
import { EditorPathParams } from '../../editor'
|
||||
|
||||
export const ShareLinkButton: React.FC = () => {
|
||||
const [showReadOnly, setShowReadOnly] = useState(false)
|
||||
useTranslation()
|
||||
const [showShareDialog, setShowShareDialog] = useState(false)
|
||||
const noteMetadata = useSelector((state: ApplicationState) => state.documentContent.metadata, equal)
|
||||
const editorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode)
|
||||
const baseUrl = useFrontendBaseUrl()
|
||||
const { id } = useParams<EditorPathParams>()
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<TranslatedIconButton size={'sm'} className={'mx-1'} icon={'share'} variant={'light'} onClick={() => setShowReadOnly(true)} i18nKey={'editor.documentBar.shareLink'}/>
|
||||
<TranslatedIconButton
|
||||
size={'sm'}
|
||||
className={'mx-1'}
|
||||
icon={'share'}
|
||||
variant={'light'}
|
||||
onClick={() => setShowShareDialog(true)}
|
||||
i18nKey={'editor.documentBar.shareLink'}
|
||||
/>
|
||||
<CommonModal
|
||||
show={showReadOnly}
|
||||
onHide={() => setShowReadOnly(false)}
|
||||
show={showShareDialog}
|
||||
onHide={() => setShowShareDialog(false)}
|
||||
closeButton={true}
|
||||
titleI18nKey={'editor.modal.shareLink.title'}>
|
||||
<Modal.Body>
|
||||
<span className={'my-4'}><Trans i18nKey={'editor.modal.shareLink.viewOnlyDescription'}/></span>
|
||||
<CopyableField content={'https://example.com'}/>
|
||||
<Trans i18nKey={'editor.modal.shareLink.editorDescription'}/>
|
||||
<CopyableField content={`${baseUrl}/n/${id}?${editorMode}`} nativeShareButton={true} url={`${baseUrl}/n/${id}?${editorMode}`}/>
|
||||
<ShowIf condition={noteMetadata.type === 'slide'}>
|
||||
<Trans i18nKey={'editor.modal.shareLink.slidesDescription'}/>
|
||||
<CopyableField content={`${baseUrl}/p/${id}`} nativeShareButton={true} url={`${baseUrl}/p/${id}`}/>
|
||||
</ShowIf>
|
||||
<ShowIf condition={noteMetadata.type === ''}>
|
||||
<Trans i18nKey={'editor.modal.shareLink.viewOnlyDescription'}/>
|
||||
<CopyableField content={`${baseUrl}/s/${id}`} nativeShareButton={true} url={`${baseUrl}/s/${id}`}/>
|
||||
</ShowIf>
|
||||
</Modal.Body>
|
||||
</CommonModal>
|
||||
</Fragment>
|
||||
|
|
|
@ -12,7 +12,7 @@ import useMedia from 'use-media'
|
|||
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
||||
import { useDocumentTitle } from '../../hooks/common/use-document-title'
|
||||
import { ApplicationState } from '../../redux'
|
||||
import { setDocumentContent, setNoteId } from '../../redux/document-content/methods'
|
||||
import { setDocumentContent, setDocumentMetadata, setNoteId } from '../../redux/document-content/methods'
|
||||
import { setEditorMode } from '../../redux/editor/methods'
|
||||
import { extractNoteTitle } from '../common/document-title/note-title-extractor'
|
||||
import { MotdBanner } from '../common/motd-banner/motd-banner'
|
||||
|
@ -74,6 +74,7 @@ export const Editor: React.FC = () => {
|
|||
|
||||
const onMetadataChange = useCallback((metaData: YAMLMetaData | undefined) => {
|
||||
noteMetadata.current = metaData
|
||||
setDocumentMetadata(metaData)
|
||||
updateDocumentTitle()
|
||||
}, [updateDocumentTitle])
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { useParams } from 'react-router'
|
|||
import { getNote, Note } from '../../api/notes'
|
||||
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
||||
import { useDocumentTitle } from '../../hooks/common/use-document-title'
|
||||
import { setDocumentContent } from '../../redux/document-content/methods'
|
||||
import { setDocumentContent, setDocumentMetadata } from '../../redux/document-content/methods'
|
||||
import { extractNoteTitle } from '../common/document-title/note-title-extractor'
|
||||
import { MotdBanner } from '../common/motd-banner/motd-banner'
|
||||
import { ShowIf } from '../common/show-if/show-if'
|
||||
|
@ -44,6 +44,7 @@ export const PadViewOnly: React.FC = () => {
|
|||
|
||||
const onMetadataChange = useCallback((metaData: YAMLMetaData | undefined) => {
|
||||
noteMetadata.current = metaData
|
||||
setDocumentMetadata(metaData)
|
||||
updateDocumentTitle()
|
||||
}, [updateDocumentTitle])
|
||||
|
||||
|
|
|
@ -5,12 +5,18 @@
|
|||
*/
|
||||
|
||||
import { store } from '..'
|
||||
import { DocumentContentActionType, SetDocumentContentAction, SetNoteIdAction } from './types'
|
||||
import { YAMLMetaData } from '../../components/editor/yaml-metadata/yaml-metadata'
|
||||
import {
|
||||
DocumentContentActionType,
|
||||
SetDocumentContentAction,
|
||||
SetDocumentMetadataAction,
|
||||
SetNoteIdAction
|
||||
} from './types'
|
||||
|
||||
export const setDocumentContent = (content: string): void => {
|
||||
const action: SetDocumentContentAction = {
|
||||
type: DocumentContentActionType.SET_DOCUMENT_CONTENT,
|
||||
content: content
|
||||
content
|
||||
}
|
||||
store.dispatch(action)
|
||||
}
|
||||
|
@ -18,7 +24,18 @@ export const setDocumentContent = (content: string): void => {
|
|||
export const setNoteId = (noteId: string): void => {
|
||||
const action: SetNoteIdAction = {
|
||||
type: DocumentContentActionType.SET_NOTE_ID,
|
||||
noteId: noteId
|
||||
noteId
|
||||
}
|
||||
store.dispatch(action)
|
||||
}
|
||||
|
||||
export const setDocumentMetadata = (metadata: YAMLMetaData | undefined): void => {
|
||||
if (!metadata) {
|
||||
return
|
||||
}
|
||||
const action: SetDocumentMetadataAction = {
|
||||
type: DocumentContentActionType.SET_DOCUMENT_METADATA,
|
||||
metadata
|
||||
}
|
||||
store.dispatch(action)
|
||||
}
|
||||
|
|
|
@ -9,13 +9,26 @@ import {
|
|||
DocumentContent,
|
||||
DocumentContentAction,
|
||||
DocumentContentActionType,
|
||||
SetDocumentContentAction,
|
||||
SetDocumentContentAction, SetDocumentMetadataAction,
|
||||
SetNoteIdAction
|
||||
} from './types'
|
||||
|
||||
export const initialState: DocumentContent = {
|
||||
content: '',
|
||||
noteId: ''
|
||||
noteId: '',
|
||||
metadata: {
|
||||
title: '',
|
||||
description: '',
|
||||
tags: [],
|
||||
robots: '',
|
||||
lang: 'en',
|
||||
dir: 'ltr',
|
||||
breaks: true,
|
||||
GA: '',
|
||||
disqus: '',
|
||||
type: '',
|
||||
opengraph: new Map<string, string>()
|
||||
}
|
||||
}
|
||||
|
||||
export const DocumentContentReducer: Reducer<DocumentContent, DocumentContentAction> = (state: DocumentContent = initialState, action: DocumentContentAction) => {
|
||||
|
@ -30,6 +43,11 @@ export const DocumentContentReducer: Reducer<DocumentContent, DocumentContentAct
|
|||
...state,
|
||||
noteId: (action as SetNoteIdAction).noteId
|
||||
}
|
||||
case DocumentContentActionType.SET_DOCUMENT_METADATA:
|
||||
return {
|
||||
...state,
|
||||
metadata: (action as SetDocumentMetadataAction).metadata
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
|
|
|
@ -5,15 +5,18 @@
|
|||
*/
|
||||
|
||||
import { Action } from 'redux'
|
||||
import { YAMLMetaData } from '../../components/editor/yaml-metadata/yaml-metadata'
|
||||
|
||||
export enum DocumentContentActionType {
|
||||
SET_DOCUMENT_CONTENT = 'document-content/set',
|
||||
SET_NOTE_ID = 'document-content/noteid/set'
|
||||
SET_NOTE_ID = 'document-content/noteid/set',
|
||||
SET_DOCUMENT_METADATA = 'document-content/metadata/set'
|
||||
}
|
||||
|
||||
export interface DocumentContent {
|
||||
content: string
|
||||
noteId: string
|
||||
noteId: string,
|
||||
metadata: YAMLMetaData
|
||||
}
|
||||
|
||||
export interface DocumentContentAction extends Action<DocumentContentActionType> {
|
||||
|
@ -27,3 +30,7 @@ export interface SetDocumentContentAction extends DocumentContentAction {
|
|||
export interface SetNoteIdAction extends DocumentContentAction {
|
||||
noteId: string
|
||||
}
|
||||
|
||||
export interface SetDocumentMetadataAction extends DocumentContentAction {
|
||||
metadata: YAMLMetaData
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue