Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LEAN-2702 TCP-100 FE #108

Merged
merged 26 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@manuscripts/body-editor",
"description": "Prosemirror components for editing and viewing manuscripts",
"version": "1.5.5",
"version": "1.5.5-LEAN-2702",
"repository": "github:Atypon-OpenSource/manuscripts-body-editor",
"license": "Apache-2.0",
"main": "dist/cjs",
Expand Down Expand Up @@ -45,6 +45,7 @@
"lodash-es": "^4.17.21",
"popper.js": "^1.16.1",
"pretty-bytes": "^6.0.0",
"prosemirror-collab": "^1.3.1",
"prosemirror-commands": "^1.5.0",
"prosemirror-dropcursor": "^1.6.1",
"prosemirror-history": "^1.3.0",
Expand All @@ -71,8 +72,8 @@
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@manuscripts/eslint-config": "^0.5.1",
"@inline-svg-unique-id/react": "^1.2.3",
"@manuscripts/eslint-config": "^0.5.1",
"@types/codemirror": "^5.60.5",
"@types/dompurify": "^2.4.0",
"@types/enzyme": "^3.10.12",
Expand Down
40 changes: 40 additions & 0 deletions src/classes/collabProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*!
* © 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 { Step } from 'prosemirror-transform'

export abstract class CollabProvider {
currentVersion: number
protected newStepsListener: (
version: number,
steps: Step[],
clientIDs: number[]
) => void
abstract sendSteps(
version: number,
steps: readonly Step[],
clientID: string | number
): Promise<void>
abstract onNewSteps(listener: CollabProvider['newStepsListener']): void
abstract stepsSince(version: number): Promise<
| {
steps: Step[]
clientIDs: number[]
version: number
}
| undefined
>
}
2 changes: 2 additions & 0 deletions src/configs/ManuscriptsEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { EditorView } from 'prosemirror-view'
import React from 'react'
import { DefaultTheme } from 'styled-components'

import { CollabProvider } from '../classes/collabProvider'
import { transformPasted } from '../lib/paste'
import { PopperManager } from '../lib/popper'
import { CreateView } from '../useEditor'
Expand Down Expand Up @@ -91,6 +92,7 @@ export interface EditorProps {
environment?: string
setCiteprocCitations: (citations: Map<string, string>) => void
getCiteprocCitations: () => Map<string, string>
collabProvider?: CollabProvider
}

export default {
Expand Down
11 changes: 10 additions & 1 deletion src/configs/editor-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ import {
import { CitationProvider } from '@manuscripts/library'
import { Capabilities } from '@manuscripts/style-guide'
import { Build, ManuscriptSchema } from '@manuscripts/transform'
import { collab } from 'prosemirror-collab'
import { dropCursor } from 'prosemirror-dropcursor'
import { history } from 'prosemirror-history'
import { Plugin } from 'prosemirror-state'
import { tableEditing } from 'prosemirror-tables'

import { CollabProvider } from '../classes/collabProvider'
import keys from '../keys'
import { PopperManager } from '../lib/popper'
import auxiliary_object_order from '../plugins/auxiliary_object_order'
Expand Down Expand Up @@ -67,11 +69,12 @@ interface PluginProps {
cslProps: CSLProps
popper: PopperManager
setCiteprocCitations: (citations: Map<string, string>) => void
collabProvider?: CollabProvider
}

export default (props: PluginProps) => {
const plugins = props.plugins || []
return [
const allPlugins = [
rules,
...keys,
dropCursor(),
Expand All @@ -95,6 +98,12 @@ export default (props: PluginProps) => {
track_changes_ui(props),
tracking_mark(),
]

if (props.collabProvider) {
allPlugins.push(collab({ version: props.collabProvider.currentVersion }))
}

return allPlugins
}

// for tables
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export {
} from './components/toolbar/ManuscriptToolbar'
export { default as getMenus } from './menus'
export { ChangeReceiver } from './types'
export { CollabProvider } from './classes/collabProvider'
export { PopperManager } from './lib/popper'
export { toolbar } from './toolbar'
export * from './plugins/highlight'
Expand Down
39 changes: 39 additions & 0 deletions src/lib/use-do-with-debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*!
* © 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 { useRef } from 'react'

export const useDoWithDebounce = () => {
const debounced = useRef<() => void>(() => undefined)
const timeout = useRef<number>()

const doWithDebounce = (fn: () => void, interval = 1000, flush = false) => {
debounced.current = fn

if (flush) {
fn()
debounced.current = () => undefined
window.clearTimeout(timeout.current)
}

window.clearTimeout(timeout.current)
timeout.current = window.setTimeout(() => {
debounced.current()
timeout.current = 0
}, interval)
}

return doWithDebounce
}
86 changes: 83 additions & 3 deletions src/useEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@
* limitations under the License.
*/

import {
trackChangesPluginKey,
TrackChangesStatus,
} from '@manuscripts/track-changes-plugin'
import {
getVersion,
receiveTransaction,
sendableSteps,
} from 'prosemirror-collab'
import {
Command,
EditorState,
Expand All @@ -25,6 +34,8 @@ import { EditorView } from 'prosemirror-view'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useHistory } from 'react-router-dom'

import { EditorProps } from './configs/ManuscriptsEditor'
import { useDoWithDebounce } from './lib/use-do-with-debounce'
import { bibliographyKey } from './plugins/bibliography'

export type CreateView = (
Expand All @@ -33,11 +44,56 @@ export type CreateView = (
dispatch: (tr: Transaction) => EditorState
) => EditorView

const useEditor = (initialState: EditorState, createView: CreateView) => {
const useEditor = (
initialState: EditorState,
createView: CreateView,
editorProps: EditorProps
) => {
const view = useRef<EditorView>()
const [state, setState] = useState<EditorState>(initialState)
const [viewElement, setViewElement] = useState<HTMLDivElement | null>(null)
const history = useHistory()
const { collabProvider } = editorProps

// Receiving steps from backend
if (collabProvider) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
collabProvider.onNewSteps(async (newVersion, steps, clientIDs) => {
if (state && view.current) {
// @TODO: make sure received steps are ignored by the quarterback plugin
const localVersion = getVersion(view.current.state)

// @TODO - save unconfirmed verison and compare it with newVersion to check if we can consume this update and don't have to call collabProvider.stepsSince
// if (newVersion == lastLocalUnconfirmed) {
// view.current.dispatch(
// receiveTransaction(
// // has to be called for the collab to increment version and drop buffered steps
// view.current.state,
// steps,
// clientIDs
// )
// )
// }

const since = await collabProvider.stepsSince(localVersion)

if (since?.steps && since.clientIDs) {
view.current.dispatch(
receiveTransaction(
// has to be called for the collab to increment version and drop buffered steps
view.current.state,
since?.steps,
since.clientIDs
)
)
} else {
console.warn('Inconsistent new steps event from the authority.')
}
}
})
}

const debounce = useDoWithDebounce()

const dispatch = useCallback(
(tr: Transaction) => {
Expand All @@ -48,8 +104,32 @@ const useEditor = (initialState: EditorState, createView: CreateView) => {
const nextState = view.current.state.apply(tr)
view.current.updateState(nextState)

// TODO: this part should be debounced??
setState(nextState)
const trackState = trackChangesPluginKey.getState(view.current.state)

if (
collabProvider &&
trackState &&
trackState.status !== TrackChangesStatus.viewSnapshots
) {
const sendable = sendableSteps(nextState)
if (sendable) {
collabProvider.sendSteps(
sendable.version,
sendable.steps,
sendable.clientID
)
}
}

debounce(
() => {
setState(nextState)
},
1000,
!tr.isGeneric
)

// need to communicate updates of the body-editor the article-editor

return state
},
Expand Down
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7041,6 +7041,13 @@ prosemirror-collab@^1.3.0:
dependencies:
prosemirror-state "^1.0.0"

prosemirror-collab@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz#0e8c91e76e009b53457eb3b3051fb68dad029a33"
integrity sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==
dependencies:
prosemirror-state "^1.0.0"

prosemirror-commands@^1.0.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.5.1.tgz#89ddfa14e144dcc7fb0938aa0e2568c7fdde306f"
Expand Down