Commit 1cc4d5c0 authored by Casey Ydenberg's avatar Casey Ydenberg
Browse files

Add LW-specific nodeViews and plugins

parent 7d4fea7f
......@@ -15,7 +15,7 @@
*/
import 'prosemirror-view/style/prosemirror.css'
import '../lib/smooth-scroll'
import '../../lib/smooth-scroll'
import { GetCitationProcessor } from '@manuscripts/library'
import {
......@@ -29,26 +29,20 @@ import {
Model,
// Section,
} from '@manuscripts/manuscripts-json-schema'
import { RxAttachment, RxAttachmentCreator } from '@manuscripts/rxdb'
import { EditorState, Plugin } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import React from 'react'
import { transformPasted } from '../lib/paste'
import plugins from '../plugins/editor'
import { ChangeReceiver } from '../types'
import { CreateView } from '../useEditor'
import views from '../views/editor'
import { transformPasted } from '../../lib/paste'
import plugins from '../../plugins/editor'
import { ChangeReceiver } from '../../types'
import { CreateView } from '../../useEditor'
import views from './editor-views-lw'
import { ViewerProps } from './ManuscriptsViewer'
export interface EditorProps extends ViewerProps {
plugins?: Array<Plugin<ManuscriptSchema>>
getCitationProcessor: GetCitationProcessor
putAttachment: (
id: string,
attachment: RxAttachmentCreator
) => Promise<RxAttachment<Model>>
removeAttachment: (id: string, attachmentID: string) => Promise<void>
saveModel: <T extends Model>(model: T | Build<T> | Partial<T>) => Promise<T>
deleteModel: (id: string) => Promise<string>
setLibraryItem: (item: BibliographyItem) => void
......@@ -60,10 +54,6 @@ export interface EditorProps extends ViewerProps {
setView: (view: ManuscriptEditorView) => void
retrySync: (componentIDs: string[]) => Promise<void>
setCommentTarget: (commentTarget?: string) => void
jupyterConfig: {
url: string
token: string
}
permissions: {
write: boolean
}
......
......@@ -15,7 +15,7 @@
*/
import 'prosemirror-view/style/prosemirror.css'
import '../lib/smooth-scroll'
import '../../lib/smooth-scroll'
import {
ManuscriptNode,
......@@ -28,22 +28,22 @@ import {
Model,
UserProfile,
} from '@manuscripts/manuscripts-json-schema'
import { RxAttachment } from '@manuscripts/rxdb'
import { Commit } from '@manuscripts/track-changes'
import { History } from 'history'
import { EditorState } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import React from 'react'
import { PopperManager } from '../lib/popper'
import plugins from '../plugins/viewer'
import { CreateView } from '../useEditor'
import views from '../views/viewer'
import { PopperManager } from '../../lib/popper'
import plugins from '../../plugins/viewer'
import { CreateView } from '../../useEditor'
import views from './viewer-views-lw'
export interface ViewerProps {
attributes?: { [key: string]: string }
commit: Commit
doc: ManuscriptNode
getModel: <T extends Model>(id: string) => T | undefined
allAttachments: (id: string) => Promise<Array<RxAttachment<Model>>>
getManuscript: () => Manuscript
getLibraryItem: (id: string) => BibliographyItem | undefined
locale: string
......
/*!
* © 2019 Atypon Systems LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// import { gapCursor } from 'prosemirror-gapcursor'
import 'prosemirror-gapcursor/style/gapcursor.css'
import 'prosemirror-tables/style/tables.css'
import { GetCitationProcessor } from '@manuscripts/library'
import { Build, ManuscriptSchema } from '@manuscripts/manuscript-transform'
import {
BibliographyItem,
Manuscript,
Model,
} from '@manuscripts/manuscripts-json-schema'
import track, { Commit } from '@manuscripts/track-changes'
import { dropCursor } from 'prosemirror-dropcursor'
import { history } from 'prosemirror-history'
import { Plugin } from 'prosemirror-state'
import { tableEditing } from 'prosemirror-tables'
import keys from '../../keys'
import bibliography from '../../plugins/bibliography'
import elements from '../../plugins/elements'
import highlights from '../../plugins/highlight'
import keywords from '../../plugins/keywords'
import models from '../../plugins/models'
import objects from '../../plugins/objects'
import paragraphs from '../../plugins/paragraphs'
import persist from '../../plugins/persist'
import placeholder from '../../plugins/placeholder'
import sections from '../../plugins/sections'
import styles from '../../plugins/styles'
import toc from '../../plugins/toc'
import rules from '../../rules'
interface PluginProps {
commit: Commit
deleteModel: (id: string) => Promise<string>
getCitationProcessor: GetCitationProcessor
getLibraryItem: (id: string) => BibliographyItem | undefined
getModel: <T extends Model>(id: string) => T | undefined
getManuscript: () => Manuscript
modelMap: Map<string, Model>
saveModel: <T extends Model>(model: T | Build<T> | Partial<T>) => Promise<T>
setCommentTarget: (commentTarget?: string) => void
plugins?: Array<Plugin<ManuscriptSchema>>
}
export default (props: PluginProps) => {
const {
commit,
deleteModel,
getCitationProcessor,
getLibraryItem,
getModel,
getManuscript,
modelMap,
saveModel,
setCommentTarget,
} = props
const plugins = props.plugins || []
return [
rules,
...keys,
dropCursor(),
// gapCursor(),
history(),
models({ saveModel, deleteModel }), // NOTE: this should come first
...plugins, // TODO: should these run after persist?
elements(),
persist(),
sections(),
toc({ modelMap }),
styles({ getModel, getManuscript, modelMap }),
keywords({ getManuscript, getModel }),
bibliography({
getCitationProcessor,
getLibraryItem,
getModel,
getManuscript,
}),
objects({ getManuscript, getModel }),
paragraphs(),
placeholder(),
tableEditing(),
track(commit),
highlights({ setCommentTarget }),
]
}
// for tables
document.execCommand('enableObjectResizing', false, 'false')
document.execCommand('enableInlineTableEditing', false, 'false')
/*!
* © 2019 Atypon Systems LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import bibliographyElement from '../../views/bibliography_element_editable'
import blockquoteElement from '../../views/blockquote_element_editable'
import bulletList from '../../views/bullet_list_editable'
import citation, { CitationEditableProps } from '../../views/citation_editable'
import crossReference from '../../views/cross_reference_editable'
import { EditableBlockProps } from '../../views/editable_block'
import equation from '../../views/equation_editable'
import equationElement from '../../views/equation_element_editable'
import figure from '../../views/figure_editable'
import figureElement from '../../views/figure_element_editable'
import inlineEquation from '../../views/inline_equation_editable'
import inlineFootnote from '../../views/inline_footnote_editable'
import keywordsElement from '../../views/keywords_element_editable'
import link from '../../views/link_editable'
import orderedList from '../../views/ordered_list_editable'
import paragraph from '../../views/paragraph_editable'
import placeholder from '../../views/placeholder'
import placeholderElement from '../../views/placeholder_element_editable'
import pullquoteElement from '../../views/pullquote_element_editable'
import sectionTitle from '../../views/section_title_editable'
import tableElement from '../../views/table_element_editable'
import tocElement from '../../views/toc_element_editable'
type EditorProps = EditableBlockProps & CitationEditableProps
export default (props: EditorProps) => ({
bibliography_element: bibliographyElement(props),
blockquote_element: blockquoteElement(props),
bullet_list: bulletList(props),
citation: citation(props),
cross_reference: crossReference(props),
equation: equation(props),
equation_element: equationElement(props),
figure: figure(props),
figure_element: figureElement(props),
inline_equation: inlineEquation(props),
inline_footnote: inlineFootnote(props),
keywords_element: keywordsElement(props),
link: link(props),
ordered_list: orderedList(props),
paragraph: paragraph(props),
placeholder: placeholder(props),
placeholder_element: placeholderElement(props),
pullquote_element: pullquoteElement(props),
section_title: sectionTitle(props),
table_element: tableElement(props),
toc_element: tocElement(props),
})
/*!
* © 2019 Atypon Systems LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'prosemirror-gapcursor/style/gapcursor.css'
import 'prosemirror-tables/style/tables.css'
import { Manuscript, Model } from '@manuscripts/manuscripts-json-schema'
import track, { Commit } from '@manuscripts/track-changes'
import elements from '../../plugins/elements'
import objects from '../../plugins/objects'
import styles from '../../plugins/styles'
interface PluginProps {
getModel: <T extends Model>(id: string) => T | undefined
getManuscript: () => Manuscript
modelMap: Map<string, Model>
commit: Commit
}
export default (props: PluginProps) => {
const { commit, getModel, getManuscript, modelMap } = props
return [
elements(),
styles({ getModel, getManuscript, modelMap }),
objects({ getModel, getManuscript }),
track(commit),
]
}
/*!
* © 2019 Atypon Systems LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import bibliographyElement from '../../views/bibliography_element'
import blockquoteElement from '../../views/blockquote_element'
import bulletList from '../../views/bullet_list'
import citation, { CitationViewProps } from '../../views/citation'
import crossReference, {
CrossReferenceViewProps,
} from '../../views/cross_reference'
import equation from '../../views/equation'
import equationElement from '../../views/equation_element'
import figure from '../../views/figure'
import figureElement from '../../views/figure_element'
import inlineEquation from '../../views/inline_equation'
import inlineFootnote, {
InlineFootnoteProps,
} from '../../views/inline_footnote'
import keywordsElement from '../../views/keywords_element'
import link from '../../views/link'
import orderedList from '../../views/ordered_list'
import paragraph from '../../views/paragraph'
import placeholder from '../../views/placeholder'
import placeholderElement from '../../views/placeholder_element'
import pullquoteElement from '../../views/pullquote_element'
import sectionTitle from '../../views/section_title'
import tableElement from '../../views/table_element'
import tocElement from '../../views/toc_element'
type ViewerProps = CitationViewProps &
CrossReferenceViewProps &
InlineFootnoteProps
export default (props: ViewerProps) => ({
bibliography_element: bibliographyElement(props),
blockquote_element: blockquoteElement(props),
bullet_list: bulletList(props),
citation: citation(props),
cross_reference: crossReference(props),
equation: equation(props),
equation_element: equationElement(props),
figure: figure(props),
figure_element: figureElement(props),
inline_equation: inlineEquation(props),
inline_footnote: inlineFootnote(props),
keywords_element: keywordsElement(props),
link: link(props),
ordered_list: orderedList(props),
paragraph: paragraph(props),
placeholder: placeholder(props),
placeholder_element: placeholderElement(props),
pullquote_element: pullquoteElement(props),
section_title: sectionTitle(props),
table_element: tableElement(props),
toc_element: tocElement(props),
})
......@@ -41,5 +41,5 @@ export * from './plugins/highlight'
export * from './plugins/keywords'
export * from './lib/utils'
export { default as useEditor, EditorHookValue } from './useEditor'
export { default as ManuscriptsViewer } from './configs/ManuscriptsViewer'
export { default as ManuscriptsEditor } from './configs/ManuscriptsEditor'
export { default as ManuscriptsViewer } from './configs/lean-workflow/ManuscriptsViewer'
export { default as ManuscriptsEditor } from './configs/lean-workflow/ManuscriptsEditor'
/*!
* © 2019 Atypon Systems LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FigureNode } from '@manuscripts/manuscript-transform'
import React, { SyntheticEvent, useRef, useState } from 'react'
import styled from 'styled-components'
import { ReactViewComponentProps } from './ReactView'
export interface FigureProps {
putAttachment: (file: File) => Promise<string>
permissions: { write: boolean }
}
const Figure = ({ putAttachment, permissions }: FigureProps) => {
const Component: React.FC<ReactViewComponentProps<FigureNode>> = ({
nodeAttrs,
setNodeAttrs,
}) => {
const [displayUrl, setDisplayUrl] = useState<string>(nodeAttrs.src || '')
const fileInput = useRef<HTMLInputElement>(null)
const handleUpload = async (e: SyntheticEvent) => {
e.preventDefault()
const file =
fileInput.current &&
fileInput.current.files &&
fileInput.current.files[0]
if (!file || !permissions.write) {
return
}
setDisplayUrl(window.URL.createObjectURL(file))
const url = await putAttachment(file)
setNodeAttrs({
contentType: file.type,
// TODO: MPExternalFile
src: url,
})
}
const handleImageClick = (e: SyntheticEvent) => {
e.preventDefault()
if (!permissions.write || !fileInput.current) {
return
}
fileInput.current.click()
}
return (
<React.Fragment>
{permissions.write && (
<HiddenInput
type="file"
ref={fileInput}
onChange={handleUpload}
accept="image/*"
/>
)}
{displayUrl ? (
<UnstyledButton type="button" onClick={handleImageClick}>
<img
src={displayUrl}
alt={nodeAttrs.label}
style={{ cursor: 'pointer' }}
/>
</UnstyledButton>
) : (
<UnstyledButton type="button" onClick={handleImageClick}>
<Placeholder>
<div>
{permissions.write
? 'Click to add image'
: 'No image here yet…'}
</div>
</Placeholder>
</UnstyledButton>
)}
</React.Fragment>
)
}
return Component
}
const HiddenInput = styled.input`
display: none;
`
const UnstyledButton = styled.button`
display: block;
border: none;
background: none;
margin-left: auto;
margin-right: auto;
min-width: 250px;
padding: 0;
&:focus {
outline: rgb(13, 121, 208) auto 1px;
}
`
const Placeholder = styled.div`
align-items: center;
border-radius: 16px;
border: 1px dashed #e2e2e2;
color: #6e6e6e;
cursor: pointer;
display: flex;
justify-content: center;
text-align: center;
padding: 64px 32px;
min-height: 100px;
`
export default Figure
/*!
* © 2019 Atypon Systems LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ManuscriptEditorView,
ManuscriptNode,
ManuscriptSchema,
} from '@manuscripts/manuscript-transform'
import { NodeView } from 'prosemirror-view'
import React, { useCallback, useEffect, useState } from 'react'
import ReactDOM from 'react-dom'
import { Dispatch } from '../commands'
export interface ReactViewComponentProps<NodeT extends ManuscriptNode> {
nodeAttrs: NodeT['attrs']
setNodeAttrs: (nextAttrs: Partial<NodeT['attrs']>) => void
}
export default (dispatch: Dispatch) => <NodeT extends ManuscriptNode>(
Component: React.FC<ReactViewComponentProps<NodeT>>,
contentDOMElementType?: keyof HTMLElementTagNameMap | null
) => (
node: NodeT,
view: ManuscriptEditorView,
getPos: () => number
): NodeView<ManuscriptSchema> => {
const root = document.createElement('div')
const reactChild = root.appendChild(document.createElement('div'))
let contentDOM: HTMLElement | null
if (contentDOMElementType) {
contentDOM = document.createElement(contentDOMElementType)
root.appendChild(contentDOM)
} else {
contentDOM = null
}
// a very simple event emitter that tracks the current value of ManuscriptNode
// and injects it into Component
let subscriber: ((node: NodeT['attrs']) => void) | null
const setNode = (next: NodeT['attrs']) => {
subscriber &&