mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Standardise types for ranges
(#16927)
GitOrigin-RevId: 28dd0eb67e1684e6bd0e452d15315ce1f9e3481a
This commit is contained in:
parent
c997d1dc2b
commit
7b3ffb9fae
13 changed files with 171 additions and 185 deletions
|
@ -2,8 +2,10 @@
|
||||||
"name": "@overleaf/ranges-tracker",
|
"name": "@overleaf/ranges-tracker",
|
||||||
"description": "Shared logic for syncing comments and tracked changes with operational transforms",
|
"description": "Shared logic for syncing comments and tracked changes with operational transforms",
|
||||||
"main": "index.cjs",
|
"main": "index.cjs",
|
||||||
|
"types": "types/index.d.cts",
|
||||||
"files": [
|
"files": [
|
||||||
"index.cjs"
|
"index.cjs",
|
||||||
|
"types"
|
||||||
],
|
],
|
||||||
"author": "Overleaf (https://www.overleaf.com)",
|
"author": "Overleaf (https://www.overleaf.com)",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|
|
@ -16,7 +16,7 @@ import EditorWatchdogManager from '@/features/ide-react/connection/editor-watchd
|
||||||
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
|
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
|
||||||
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
|
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
import { Document } from '@/features/ide-react/editor/document'
|
import { DocumentContainer } from '@/features/ide-react/editor/document-container'
|
||||||
import { useLayoutContext } from '@/shared/context/layout-context'
|
import { useLayoutContext } from '@/shared/context/layout-context'
|
||||||
import { GotoLineOptions } from '@/features/ide-react/types/goto-line-options'
|
import { GotoLineOptions } from '@/features/ide-react/types/goto-line-options'
|
||||||
import { Doc } from '../../../../../types/doc'
|
import { Doc } from '../../../../../types/doc'
|
||||||
|
@ -45,7 +45,7 @@ interface OpenDocOptions
|
||||||
export type EditorManager = {
|
export type EditorManager = {
|
||||||
getEditorType: () => EditorType | null
|
getEditorType: () => EditorType | null
|
||||||
showSymbolPalette: boolean
|
showSymbolPalette: boolean
|
||||||
currentDocument: Document
|
currentDocument: DocumentContainer
|
||||||
currentDocumentId: DocId | null
|
currentDocumentId: DocId | null
|
||||||
getCurrentDocValue: () => string | null
|
getCurrentDocValue: () => string | null
|
||||||
getCurrentDocId: () => DocId | null
|
getCurrentDocId: () => DocId | null
|
||||||
|
@ -73,7 +73,7 @@ function hasGotoOffset(options: OpenDocOptions): options is GotoOffsetOptions {
|
||||||
export type EditorScopeValue = {
|
export type EditorScopeValue = {
|
||||||
showSymbolPalette: false
|
showSymbolPalette: false
|
||||||
toggleSymbolPalette: () => void
|
toggleSymbolPalette: () => void
|
||||||
sharejs_doc: Document | null
|
sharejs_doc: DocumentContainer | null
|
||||||
open_doc_id: string | null
|
open_doc_id: string | null
|
||||||
open_doc_name: string | null
|
open_doc_name: string | null
|
||||||
opening: boolean
|
opening: boolean
|
||||||
|
@ -101,7 +101,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
|
||||||
)
|
)
|
||||||
const [showVisual] = useScopeValue<boolean>('editor.showVisual')
|
const [showVisual] = useScopeValue<boolean>('editor.showVisual')
|
||||||
const [currentDocument, setCurrentDocument] =
|
const [currentDocument, setCurrentDocument] =
|
||||||
useScopeValue<Document>('editor.sharejs_doc')
|
useScopeValue<DocumentContainer>('editor.sharejs_doc')
|
||||||
const [openDocId, setOpenDocId] = useScopeValue<DocId | null>(
|
const [openDocId, setOpenDocId] = useScopeValue<DocId | null>(
|
||||||
'editor.open_doc_id'
|
'editor.open_doc_id'
|
||||||
)
|
)
|
||||||
|
@ -140,7 +140,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
|
||||||
// prevents circular dependencies in useCallbacks
|
// prevents circular dependencies in useCallbacks
|
||||||
const [docError, setDocError] = useState<{
|
const [docError, setDocError] = useState<{
|
||||||
doc: Doc
|
doc: Doc
|
||||||
document: Document
|
document: DocumentContainer
|
||||||
error: Error | string
|
error: Error | string
|
||||||
meta?: Record<string, any>
|
meta?: Record<string, any>
|
||||||
editorContent?: string
|
editorContent?: string
|
||||||
|
@ -225,12 +225,12 @@ export const EditorManagerProvider: FC = ({ children }) => {
|
||||||
[goToLineEmitter]
|
[goToLineEmitter]
|
||||||
)
|
)
|
||||||
|
|
||||||
const unbindFromDocumentEvents = (document: Document) => {
|
const unbindFromDocumentEvents = (document: DocumentContainer) => {
|
||||||
document.off()
|
document.off()
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachErrorHandlerToDocument = useCallback(
|
const attachErrorHandlerToDocument = useCallback(
|
||||||
(doc: Doc, document: Document) => {
|
(doc: Doc, document: DocumentContainer) => {
|
||||||
document.on(
|
document.on(
|
||||||
'error',
|
'error',
|
||||||
(
|
(
|
||||||
|
@ -246,7 +246,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
const bindToDocumentEvents = useCallback(
|
const bindToDocumentEvents = useCallback(
|
||||||
(doc: Doc, document: Document) => {
|
(doc: Doc, document: DocumentContainer) => {
|
||||||
attachErrorHandlerToDocument(doc, document)
|
attachErrorHandlerToDocument(doc, document)
|
||||||
|
|
||||||
document.on('externalUpdate', (update: Update) => {
|
document.on('externalUpdate', (update: Update) => {
|
||||||
|
@ -276,7 +276,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
|
||||||
const syncTimeoutRef = useRef<number | null>(null)
|
const syncTimeoutRef = useRef<number | null>(null)
|
||||||
|
|
||||||
const syncTrackChangesState = useCallback(
|
const syncTrackChangesState = useCallback(
|
||||||
(doc: Document) => {
|
(doc: DocumentContainer) => {
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -310,7 +310,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
|
||||||
|
|
||||||
const doOpenNewDocument = useCallback(
|
const doOpenNewDocument = useCallback(
|
||||||
(doc: Doc) =>
|
(doc: Doc) =>
|
||||||
new Promise<Document>((resolve, reject) => {
|
new Promise<DocumentContainer>((resolve, reject) => {
|
||||||
debugConsole.log('[doOpenNewDocument] Opening...')
|
debugConsole.log('[doOpenNewDocument] Opening...')
|
||||||
const newDocument = openDocs.getDocument(doc._id)
|
const newDocument = openDocs.getDocument(doc._id)
|
||||||
if (!newDocument) {
|
if (!newDocument) {
|
||||||
|
@ -344,7 +344,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
const openNewDocument = useCallback(
|
const openNewDocument = useCallback(
|
||||||
async (doc: Doc): Promise<Document> => {
|
async (doc: Doc): Promise<DocumentContainer> => {
|
||||||
// Leave the current document
|
// Leave the current document
|
||||||
// - when we are opening a different new one, to avoid race conditions
|
// - when we are opening a different new one, to avoid race conditions
|
||||||
// between leaving and joining the same document
|
// between leaving and joining the same document
|
||||||
|
|
|
@ -27,7 +27,6 @@ import { debugConsole } from '@/utils/debugging'
|
||||||
import { useEditorContext } from '@/shared/context/editor-context'
|
import { useEditorContext } from '@/shared/context/editor-context'
|
||||||
import { deleteJSON, getJSON, postJSON } from '@/infrastructure/fetch-json'
|
import { deleteJSON, getJSON, postJSON } from '@/infrastructure/fetch-json'
|
||||||
import ColorManager from '@/ide/colors/ColorManager'
|
import ColorManager from '@/ide/colors/ColorManager'
|
||||||
// @ts-ignore
|
|
||||||
import RangesTracker from '@overleaf/ranges-tracker'
|
import RangesTracker from '@overleaf/ranges-tracker'
|
||||||
import * as ReviewPanel from '../types/review-panel-state'
|
import * as ReviewPanel from '../types/review-panel-state'
|
||||||
import {
|
import {
|
||||||
|
@ -61,6 +60,12 @@ import {
|
||||||
ReviewPanelCommentThreadsApi,
|
ReviewPanelCommentThreadsApi,
|
||||||
} from '../../../../../../../types/review-panel/api'
|
} from '../../../../../../../types/review-panel/api'
|
||||||
import { DateString } from '../../../../../../../types/helpers/date'
|
import { DateString } from '../../../../../../../types/helpers/date'
|
||||||
|
import {
|
||||||
|
Change,
|
||||||
|
CommentOperation,
|
||||||
|
EditOperation,
|
||||||
|
} from '../../../../../../../types/change'
|
||||||
|
import { RangesTrackerWithResolvedThreadIds } from '@/features/ide-react/editor/document-container'
|
||||||
|
|
||||||
const dispatchReviewPanelEvent = (type: string, payload?: any) => {
|
const dispatchReviewPanelEvent = (type: string, payload?: any) => {
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
|
@ -251,7 +256,9 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||||
})
|
})
|
||||||
}, [loadThreadsController.signal, projectId, setLoadingThreads])
|
}, [loadThreadsController.signal, projectId, setLoadingThreads])
|
||||||
|
|
||||||
const rangesTrackers = useRef<Record<DocId, RangesTracker>>({})
|
const rangesTrackers = useRef<
|
||||||
|
Record<DocId, RangesTrackerWithResolvedThreadIds>
|
||||||
|
>({})
|
||||||
const refreshingRangeUsers = useRef(false)
|
const refreshingRangeUsers = useRef(false)
|
||||||
const refreshedForUserIds = useRef(new Set<UserId>())
|
const refreshedForUserIds = useRef(new Set<UserId>())
|
||||||
const refreshChangeUsers = useCallback(
|
const refreshChangeUsers = useCallback(
|
||||||
|
@ -299,12 +306,14 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||||
const getChangeTracker = useCallback(
|
const getChangeTracker = useCallback(
|
||||||
(docId: DocId) => {
|
(docId: DocId) => {
|
||||||
if (!rangesTrackers.current[docId]) {
|
if (!rangesTrackers.current[docId]) {
|
||||||
rangesTrackers.current[docId] = new RangesTracker()
|
const rangesTracker = new RangesTracker([], [])
|
||||||
rangesTrackers.current[docId].resolvedThreadIds = {
|
;(
|
||||||
...resolvedThreadIds,
|
rangesTracker as RangesTrackerWithResolvedThreadIds
|
||||||
}
|
).resolvedThreadIds = { ...resolvedThreadIds }
|
||||||
|
rangesTrackers.current[docId] =
|
||||||
|
rangesTracker as RangesTrackerWithResolvedThreadIds
|
||||||
}
|
}
|
||||||
return rangesTrackers.current[docId]
|
return rangesTrackers.current[docId]!
|
||||||
},
|
},
|
||||||
[resolvedThreadIds]
|
[resolvedThreadIds]
|
||||||
)
|
)
|
||||||
|
@ -435,17 +444,18 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||||
|
|
||||||
if (!loadingThreadsInProgressRef.current) {
|
if (!loadingThreadsInProgressRef.current) {
|
||||||
for (const comment of rangesTracker.comments) {
|
for (const comment of rangesTracker.comments) {
|
||||||
deleteChanges.delete(comment.id)
|
const commentId = comment.id as ThreadId
|
||||||
|
deleteChanges.delete(commentId)
|
||||||
|
|
||||||
let newComment: any
|
let newComment: any
|
||||||
if (localResolvedThreadIds[comment.op.t]) {
|
if (localResolvedThreadIds[comment.op.t]) {
|
||||||
docResolvedComments[comment.id] ??= {} as ReviewPanelCommentEntry
|
docResolvedComments[commentId] ??= {} as ReviewPanelCommentEntry
|
||||||
newComment = docResolvedComments[comment.id]
|
newComment = docResolvedComments[commentId]
|
||||||
delete docEntries[comment.id]
|
delete docEntries[commentId]
|
||||||
} else {
|
} else {
|
||||||
docEntries[comment.id] ??= {} as ReviewPanelEntry
|
docEntries[commentId] ??= {} as ReviewPanelEntry
|
||||||
newComment = docEntries[comment.id]
|
newComment = docEntries[commentId]
|
||||||
delete docResolvedComments[comment.id]
|
delete docResolvedComments[commentId]
|
||||||
}
|
}
|
||||||
|
|
||||||
newComment.type = 'comment'
|
newComment.type = 'comment'
|
||||||
|
@ -505,10 +515,12 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||||
}
|
}
|
||||||
// The open doc range tracker is kept up to date in real-time so
|
// The open doc range tracker is kept up to date in real-time so
|
||||||
// replace any outdated info with this
|
// replace any outdated info with this
|
||||||
|
const rangesTracker = currentDocument.ranges!
|
||||||
|
;(rangesTracker as RangesTrackerWithResolvedThreadIds).resolvedThreadIds = {
|
||||||
|
...resolvedThreadIds,
|
||||||
|
}
|
||||||
rangesTrackers.current[currentDocument.doc_id as DocId] =
|
rangesTrackers.current[currentDocument.doc_id as DocId] =
|
||||||
currentDocument.ranges
|
rangesTracker as RangesTrackerWithResolvedThreadIds
|
||||||
rangesTrackers.current[currentDocument.doc_id as DocId].resolvedThreadIds =
|
|
||||||
{ ...resolvedThreadIds }
|
|
||||||
currentDocument.on('flipped_pending_to_inflight', () =>
|
currentDocument.on('flipped_pending_to_inflight', () =>
|
||||||
regenerateTrackChangesId(currentDocument)
|
regenerateTrackChangesId(currentDocument)
|
||||||
)
|
)
|
||||||
|
@ -1034,8 +1046,8 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||||
type Doc = {
|
type Doc = {
|
||||||
id: DocId
|
id: DocId
|
||||||
ranges: {
|
ranges: {
|
||||||
comments?: unknown[]
|
comments?: Change<CommentOperation>[]
|
||||||
changes?: unknown[]
|
changes?: Change<EditOperation>[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1119,7 +1131,7 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { offset, length } = addCommentEntry
|
const { offset, length } = addCommentEntry
|
||||||
const threadId = RangesTracker.generateId()
|
const threadId = RangesTracker.generateId() as ThreadId
|
||||||
setCommentThreads(prevState => ({
|
setCommentThreads(prevState => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
[threadId]: { ...getThread(threadId), submitting: true },
|
[threadId]: { ...getThread(threadId), submitting: true },
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
/* eslint-disable
|
/* eslint-disable camelcase */
|
||||||
camelcase,
|
|
||||||
n/handle-callback-err,
|
|
||||||
max-len,
|
|
||||||
*/
|
|
||||||
// Migrated from services/web/frontend/js/ide/editor/Document.js
|
// Migrated from services/web/frontend/js/ide/editor/Document.js
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
import RangesTracker from '@overleaf/ranges-tracker'
|
import RangesTracker from '@overleaf/ranges-tracker'
|
||||||
import { ShareJsDoc } from './share-js-doc'
|
import { ShareJsDoc } from './share-js-doc'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
@ -19,6 +14,7 @@ import {
|
||||||
AnyOperation,
|
AnyOperation,
|
||||||
Change,
|
Change,
|
||||||
CommentOperation,
|
CommentOperation,
|
||||||
|
EditOperation,
|
||||||
} from '../../../../../types/change'
|
} from '../../../../../types/change'
|
||||||
import {
|
import {
|
||||||
isCommentOperation,
|
isCommentOperation,
|
||||||
|
@ -31,6 +27,7 @@ import {
|
||||||
TrackChangesIdSeeds,
|
TrackChangesIdSeeds,
|
||||||
Version,
|
Version,
|
||||||
} from '@/features/ide-react/editor/types/document'
|
} from '@/features/ide-react/editor/types/document'
|
||||||
|
import { ThreadId } from '../../../../../types/review-panel/review-panel'
|
||||||
|
|
||||||
const MAX_PENDING_OP_SIZE = 64
|
const MAX_PENDING_OP_SIZE = 64
|
||||||
|
|
||||||
|
@ -79,11 +76,22 @@ function getShareJsOpSize(shareJsOp: ShareJsOperation) {
|
||||||
return shareJsOp.reduce((total, op) => total + getOpSize(op), 0)
|
return shareJsOp.reduce((total, op) => total + getOpSize(op), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Document extends EventEmitter {
|
// TODO: define these in RangesTracker
|
||||||
|
type _RangesTracker = Omit<RangesTracker, 'changes' | 'comments'> & {
|
||||||
|
changes: Change<EditOperation>[]
|
||||||
|
comments: Change<CommentOperation>[]
|
||||||
|
track_changes?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RangesTrackerWithResolvedThreadIds = _RangesTracker & {
|
||||||
|
resolvedThreadIds: Record<ThreadId, boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DocumentContainer extends EventEmitter {
|
||||||
private connected: boolean
|
private connected: boolean
|
||||||
private wantToBeJoined = false
|
private wantToBeJoined = false
|
||||||
private chaosMonkeyTimer: number | null = null
|
private chaosMonkeyTimer: number | null = null
|
||||||
private track_changes_as: string | null = null
|
public track_changes_as: string | null = null
|
||||||
|
|
||||||
private joinCallbacks: JoinCallback[] = []
|
private joinCallbacks: JoinCallback[] = []
|
||||||
private leaveCallbacks: LeaveCallback[] = []
|
private leaveCallbacks: LeaveCallback[] = []
|
||||||
|
@ -91,7 +99,9 @@ export class Document extends EventEmitter {
|
||||||
doc?: ShareJsDoc
|
doc?: ShareJsDoc
|
||||||
cm6?: EditorFacade
|
cm6?: EditorFacade
|
||||||
oldInflightOp?: ShareJsOperation
|
oldInflightOp?: ShareJsOperation
|
||||||
ranges: RangesTracker
|
|
||||||
|
ranges?: _RangesTracker | RangesTrackerWithResolvedThreadIds
|
||||||
|
|
||||||
joined = false
|
joined = false
|
||||||
|
|
||||||
// This is set and read in useCodeMirrorScope
|
// This is set and read in useCodeMirrorScope
|
||||||
|
@ -103,7 +113,7 @@ export class Document extends EventEmitter {
|
||||||
private readonly globalEditorWatchdogManager: EditorWatchdogManager,
|
private readonly globalEditorWatchdogManager: EditorWatchdogManager,
|
||||||
private readonly ideEventEmitter: IdeEventEmitter,
|
private readonly ideEventEmitter: IdeEventEmitter,
|
||||||
private readonly eventLog: EventLog,
|
private readonly eventLog: EventLog,
|
||||||
private readonly detachDoc: (docId: string, doc: Document) => void
|
private readonly detachDoc: (docId: string, doc: DocumentContainer) => void
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.connected = this.socket.socket.connected
|
this.connected = this.socket.socket.connected
|
||||||
|
@ -675,18 +685,18 @@ export class Document extends EventEmitter {
|
||||||
let track_changes_as = null
|
let track_changes_as = null
|
||||||
const remote_op = msg != null
|
const remote_op = msg != null
|
||||||
if (remote_op && msg?.meta.tc) {
|
if (remote_op && msg?.meta.tc) {
|
||||||
old_id_seed = this.ranges.getIdSeed()
|
old_id_seed = this.ranges!.getIdSeed()
|
||||||
this.ranges.setIdSeed(msg.meta.tc)
|
this.ranges!.setIdSeed(msg.meta.tc)
|
||||||
track_changes_as = msg.meta.user_id
|
track_changes_as = msg.meta.user_id
|
||||||
} else if (!remote_op && this.track_changes_as != null) {
|
} else if (!remote_op && this.track_changes_as != null) {
|
||||||
track_changes_as = this.track_changes_as
|
track_changes_as = this.track_changes_as
|
||||||
}
|
}
|
||||||
this.ranges.track_changes = track_changes_as != null
|
this.ranges!.track_changes = track_changes_as != null
|
||||||
for (const op of this.filterOps(ops)) {
|
for (const op of this.filterOps(ops)) {
|
||||||
this.ranges.applyOp(op, { user_id: track_changes_as })
|
this.ranges!.applyOp(op, { user_id: track_changes_as })
|
||||||
}
|
}
|
||||||
if (old_id_seed != null) {
|
if (old_id_seed != null) {
|
||||||
this.ranges.setIdSeed(old_id_seed)
|
this.ranges!.setIdSeed(old_id_seed)
|
||||||
}
|
}
|
||||||
if (remote_op) {
|
if (remote_op) {
|
||||||
// With remote ops, the editor hasn't been updated when we receive this
|
// With remote ops, the editor hasn't been updated when we receive this
|
||||||
|
@ -697,7 +707,10 @@ export class Document extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private catchUpRanges(changes: Change[], comments: CommentOperation[]) {
|
private catchUpRanges(
|
||||||
|
changes: Change<EditOperation>[],
|
||||||
|
comments: Change<CommentOperation>[]
|
||||||
|
) {
|
||||||
// We've just been given the current server's ranges, but need to apply any local ops we have.
|
// We've just been given the current server's ranges, but need to apply any local ops we have.
|
||||||
// Reset to the server state then apply our local ops again.
|
// Reset to the server state then apply our local ops again.
|
||||||
if (changes == null) {
|
if (changes == null) {
|
||||||
|
@ -707,16 +720,16 @@ export class Document extends EventEmitter {
|
||||||
comments = []
|
comments = []
|
||||||
}
|
}
|
||||||
this.emit('ranges:clear')
|
this.emit('ranges:clear')
|
||||||
this.ranges.changes = changes
|
this.ranges!.changes = changes
|
||||||
this.ranges.comments = comments
|
this.ranges!.comments = comments
|
||||||
this.ranges.track_changes = this.doc?.track_changes
|
this.ranges!.track_changes = this.doc?.track_changes
|
||||||
for (const op of this.filterOps(this.doc?.getInflightOp() || [])) {
|
for (const op of this.filterOps(this.doc?.getInflightOp() || [])) {
|
||||||
this.ranges.setIdSeed(this.doc?.track_changes_id_seeds?.inflight)
|
this.ranges!.setIdSeed(this.doc?.track_changes_id_seeds?.inflight)
|
||||||
this.ranges.applyOp(op, { user_id: this.track_changes_as })
|
this.ranges!.applyOp(op, { user_id: this.track_changes_as })
|
||||||
}
|
}
|
||||||
for (const op of this.filterOps(this.doc?.getPendingOp() || [])) {
|
for (const op of this.filterOps(this.doc?.getPendingOp() || [])) {
|
||||||
this.ranges.setIdSeed(this.doc?.track_changes_id_seeds?.pending)
|
this.ranges!.setIdSeed(this.doc?.track_changes_id_seeds?.pending)
|
||||||
this.ranges.applyOp(op, { user_id: this.track_changes_as })
|
this.ranges!.applyOp(op, { user_id: this.track_changes_as })
|
||||||
}
|
}
|
||||||
return this.emit('ranges:redraw')
|
return this.emit('ranges:redraw')
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
// Migrated from static methods of Document in Document.js
|
// Migrated from static methods of Document in Document.js
|
||||||
|
|
||||||
import { Document } from '@/features/ide-react/editor/document'
|
import { DocumentContainer } from '@/features/ide-react/editor/document-container'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
import { Socket } from '@/features/ide-react/connection/types/socket'
|
import { Socket } from '@/features/ide-react/connection/types/socket'
|
||||||
import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter'
|
import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter'
|
||||||
|
@ -8,7 +8,7 @@ import { EventLog } from '@/features/ide-react/editor/event-log'
|
||||||
import EditorWatchdogManager from '@/features/ide-react/connection/editor-watchdog-manager'
|
import EditorWatchdogManager from '@/features/ide-react/connection/editor-watchdog-manager'
|
||||||
|
|
||||||
export class OpenDocuments {
|
export class OpenDocuments {
|
||||||
private openDocs = new Map<string, Document>()
|
private openDocs = new Map<string, DocumentContainer>()
|
||||||
|
|
||||||
// eslint-disable-next-line no-useless-constructor
|
// eslint-disable-next-line no-useless-constructor
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -44,7 +44,7 @@ export class OpenDocuments {
|
||||||
}
|
}
|
||||||
|
|
||||||
private createDoc(docId: string) {
|
private createDoc(docId: string) {
|
||||||
const doc = new Document(
|
const doc = new DocumentContainer(
|
||||||
docId,
|
docId,
|
||||||
this.socket,
|
this.socket,
|
||||||
this.globalEditorWatchdogManager,
|
this.globalEditorWatchdogManager,
|
||||||
|
@ -55,7 +55,7 @@ export class OpenDocuments {
|
||||||
this.openDocs.set(docId, doc)
|
this.openDocs.set(docId, doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
detachDoc(docId: string, doc: Document) {
|
detachDoc(docId: string, doc: DocumentContainer) {
|
||||||
if (this.openDocs.get(docId) === doc) {
|
if (this.openDocs.get(docId) === doc) {
|
||||||
debugConsole.log(
|
debugConsole.log(
|
||||||
`[detach] Removing document with ID (${docId}) from openDocs`
|
`[detach] Removing document with ID (${docId}) from openDocs`
|
||||||
|
|
|
@ -8,9 +8,12 @@ import {
|
||||||
} from '../vertical-overflow'
|
} from '../vertical-overflow'
|
||||||
import { EditorSelection, EditorState } from '@codemirror/state'
|
import { EditorSelection, EditorState } from '@codemirror/state'
|
||||||
import { EditorView, ViewUpdate } from '@codemirror/view'
|
import { EditorView, ViewUpdate } from '@codemirror/view'
|
||||||
import { CurrentDoc } from '../../../../../../types/current-doc'
|
|
||||||
import { fullHeightCoordsAtPos } from '../../utils/layer'
|
import { fullHeightCoordsAtPos } from '../../utils/layer'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
|
import { Change, EditOperation } from '../../../../../../types/change'
|
||||||
|
import { ThreadId } from '../../../../../../types/review-panel/review-panel'
|
||||||
|
import { isDeleteOperation, isInsertOperation } from '@/utils/operations'
|
||||||
|
import { DocumentContainer } from '@/features/ide-react/editor/document-container'
|
||||||
|
|
||||||
// With less than this number of entries, don't bother culling to avoid
|
// With less than this number of entries, don't bother culling to avoid
|
||||||
// little UI jumps when scrolling.
|
// little UI jumps when scrolling.
|
||||||
|
@ -75,7 +78,7 @@ export type UpdateType =
|
||||||
|
|
||||||
export const createChangeManager = (
|
export const createChangeManager = (
|
||||||
view: EditorView,
|
view: EditorView,
|
||||||
currentDoc: CurrentDoc
|
currentDoc: DocumentContainer
|
||||||
): ChangeManager => {
|
): ChangeManager => {
|
||||||
/**
|
/**
|
||||||
* Calculate the screen coordinates of each entry (change or comment),
|
* Calculate the screen coordinates of each entry (change or comment),
|
||||||
|
@ -152,7 +155,7 @@ export const createChangeManager = (
|
||||||
/**
|
/**
|
||||||
* Add a comment (thread) to the ShareJS doc when it's created
|
* Add a comment (thread) to the ShareJS doc when it's created
|
||||||
*/
|
*/
|
||||||
const addComment = (offset: number, length: number, threadId: string) => {
|
const addComment = (offset: number, length: number, threadId: ThreadId) => {
|
||||||
currentDoc.submitOp({
|
currentDoc.submitOp({
|
||||||
c: view.state.doc.sliceString(offset, offset + length),
|
c: view.state.doc.sliceString(offset, offset + length),
|
||||||
p: offset,
|
p: offset,
|
||||||
|
@ -164,14 +167,14 @@ export const createChangeManager = (
|
||||||
* Remove a comment (thread) from the range tracker when it's deleted
|
* Remove a comment (thread) from the range tracker when it's deleted
|
||||||
*/
|
*/
|
||||||
const removeComment = (commentId: string) => {
|
const removeComment = (commentId: string) => {
|
||||||
currentDoc.ranges.removeCommentId(commentId)
|
currentDoc.ranges!.removeCommentId(commentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove tracked changes from the range tracker when they're accepted
|
* Remove tracked changes from the range tracker when they're accepted
|
||||||
*/
|
*/
|
||||||
const acceptChanges = (changeIds: string[]) => {
|
const acceptChanges = (changeIds: string[]) => {
|
||||||
currentDoc.ranges.removeChangeIds(changeIds)
|
currentDoc.ranges!.removeChangeIds(changeIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -179,7 +182,9 @@ export const createChangeManager = (
|
||||||
* and restore the original content
|
* and restore the original content
|
||||||
*/
|
*/
|
||||||
const rejectChanges = (changeIds: string[]) => {
|
const rejectChanges = (changeIds: string[]) => {
|
||||||
const changes: any[] = currentDoc.ranges.getChanges(changeIds)
|
const changes = currentDoc.ranges!.getChanges(
|
||||||
|
changeIds
|
||||||
|
) as Change<EditOperation>[]
|
||||||
|
|
||||||
if (changes.length === 0) {
|
if (changes.length === 0) {
|
||||||
return {}
|
return {}
|
||||||
|
@ -242,38 +247,30 @@ export const createChangeManager = (
|
||||||
const changesToDispatch = changes.map(change => {
|
const changesToDispatch = changes.map(change => {
|
||||||
const { op } = change
|
const { op } = change
|
||||||
|
|
||||||
const opType = 'i' in op ? 'i' : 'c' in op ? 'c' : 'd'
|
if (isInsertOperation(op)) {
|
||||||
|
const from = op.p
|
||||||
|
const content = op.i
|
||||||
|
const to = from + content.length
|
||||||
|
|
||||||
switch (opType) {
|
const text = view.state.doc.sliceString(from, to)
|
||||||
case 'd': {
|
|
||||||
return {
|
if (text !== content) {
|
||||||
from: op.p,
|
throw new Error(
|
||||||
to: op.p,
|
`Op to be removed (${JSON.stringify(
|
||||||
insert: op.d,
|
change.op
|
||||||
}
|
)}) does not match editor text '${text}'`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'i': {
|
return { from, to, insert: '' }
|
||||||
const from = op.p
|
} else if (isDeleteOperation(op)) {
|
||||||
const content = op.i
|
return {
|
||||||
const to = from + content.length
|
from: op.p,
|
||||||
|
to: op.p,
|
||||||
const text = view.state.doc.sliceString(from, to)
|
insert: op.d,
|
||||||
|
|
||||||
if (text !== content) {
|
|
||||||
throw new Error(
|
|
||||||
`Op to be removed (${JSON.stringify(
|
|
||||||
change.op
|
|
||||||
)}) does not match editor text '${text}'`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { from, to, insert: '' }
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
throw new Error(`unknown change: ${JSON.stringify(change)}`)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`unknown change type: ${JSON.stringify(change)}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Range, RangeSet, RangeValue, Transaction } from '@codemirror/state'
|
import { Range, RangeSet, RangeValue, Transaction } from '@codemirror/state'
|
||||||
import { CurrentDoc } from '../../../../../../types/current-doc'
|
|
||||||
import {
|
import {
|
||||||
AnyOperation,
|
AnyOperation,
|
||||||
Change,
|
Change,
|
||||||
ChangeOperation,
|
|
||||||
CommentOperation,
|
CommentOperation,
|
||||||
} from '../../../../../../types/change'
|
} from '../../../../../../types/change'
|
||||||
|
import { ThreadId } from '../../../../../../types/review-panel/review-panel'
|
||||||
|
import { DocumentContainer } from '@/features/ide-react/editor/document-container'
|
||||||
|
|
||||||
export type StoredComment = {
|
export type StoredComment = {
|
||||||
text: string
|
text: string
|
||||||
|
@ -20,14 +20,14 @@ export type StoredComment = {
|
||||||
* Find tracked comments within the range of the current transaction's changes
|
* Find tracked comments within the range of the current transaction's changes
|
||||||
*/
|
*/
|
||||||
export const findCommentsInCut = (
|
export const findCommentsInCut = (
|
||||||
currentDoc: CurrentDoc,
|
currentDoc: DocumentContainer,
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
) => {
|
) => {
|
||||||
const items: StoredComment[] = []
|
const items: StoredComment[] = []
|
||||||
|
|
||||||
transaction.changes.iterChanges((fromA, toA) => {
|
transaction.changes.iterChanges((fromA, toA) => {
|
||||||
const comments = currentDoc.ranges.comments
|
const comments = currentDoc
|
||||||
.filter(
|
.ranges!.comments.filter(
|
||||||
comment =>
|
comment =>
|
||||||
fromA <= comment.op.p && comment.op.p + comment.op.c.length <= toA
|
fromA <= comment.op.p && comment.op.p + comment.op.c.length <= toA
|
||||||
)
|
)
|
||||||
|
@ -55,7 +55,7 @@ export const findCommentsInPaste = (
|
||||||
storedComments: StoredComment[],
|
storedComments: StoredComment[],
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
) => {
|
) => {
|
||||||
const ops: ChangeOperation[] = []
|
const ops: CommentOperation[] = []
|
||||||
|
|
||||||
transaction.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
|
transaction.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
|
||||||
const insertedText = inserted.toString()
|
const insertedText = inserted.toString()
|
||||||
|
@ -71,7 +71,7 @@ export const findCommentsInPaste = (
|
||||||
ops.push({
|
ops.push({
|
||||||
c: text,
|
c: text,
|
||||||
p: fromB + offset,
|
p: fromB + offset,
|
||||||
t: comment.id,
|
t: comment.id as ThreadId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,13 +93,13 @@ class CommentRangeValue extends RangeValue {
|
||||||
* Find tracked comments with no content with the ranges of a transaction's changes
|
* Find tracked comments with no content with the ranges of a transaction's changes
|
||||||
*/
|
*/
|
||||||
export const findDetachedCommentsInChanges = (
|
export const findDetachedCommentsInChanges = (
|
||||||
currentDoc: CurrentDoc,
|
currentDoc: DocumentContainer,
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
) => {
|
) => {
|
||||||
const items: Range<CommentRangeValue>[] = []
|
const items: Range<CommentRangeValue>[] = []
|
||||||
|
|
||||||
transaction.changes.iterChanges((fromA, toA) => {
|
transaction.changes.iterChanges((fromA, toA) => {
|
||||||
for (const comment of currentDoc.ranges.comments) {
|
for (const comment of currentDoc.ranges!.comments) {
|
||||||
const content = comment.op.c
|
const content = comment.op.c
|
||||||
|
|
||||||
// TODO: handle comments that were never attached
|
// TODO: handle comments that were never attached
|
||||||
|
@ -124,7 +124,7 @@ export const findDetachedCommentsInChanges = (
|
||||||
* (used when restoring comments on paste)
|
* (used when restoring comments on paste)
|
||||||
*/
|
*/
|
||||||
const submitOps = (
|
const submitOps = (
|
||||||
currentDoc: CurrentDoc,
|
currentDoc: DocumentContainer,
|
||||||
ops: AnyOperation[],
|
ops: AnyOperation[],
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
) => {
|
) => {
|
||||||
|
@ -133,14 +133,14 @@ const submitOps = (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that comments still match text. Will throw error if not.
|
// Check that comments still match text. Will throw error if not.
|
||||||
currentDoc.ranges.validate(transaction.state.doc.toString())
|
currentDoc.ranges!.validate(transaction.state.doc.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for the ShareJS doc to fire an event, then submit the operations.
|
* Wait for the ShareJS doc to fire an event, then submit the operations.
|
||||||
*/
|
*/
|
||||||
const submitOpsAfterEvent = (
|
const submitOpsAfterEvent = (
|
||||||
currentDoc: CurrentDoc,
|
currentDoc: DocumentContainer,
|
||||||
eventName: string,
|
eventName: string,
|
||||||
ops: AnyOperation[],
|
ops: AnyOperation[],
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
|
@ -161,7 +161,7 @@ const submitOpsAfterEvent = (
|
||||||
* Look through the comments stored on cut, and restore those in text that matches the pasted text.
|
* Look through the comments stored on cut, and restore those in text that matches the pasted text.
|
||||||
*/
|
*/
|
||||||
export const restoreCommentsOnPaste = (
|
export const restoreCommentsOnPaste = (
|
||||||
currentDoc: CurrentDoc,
|
currentDoc: DocumentContainer,
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
storedComments: StoredComment[]
|
storedComments: StoredComment[]
|
||||||
) => {
|
) => {
|
||||||
|
@ -183,18 +183,18 @@ export const restoreCommentsOnPaste = (
|
||||||
* When undoing a change, find comments from the original content and restore them.
|
* When undoing a change, find comments from the original content and restore them.
|
||||||
*/
|
*/
|
||||||
export const restoreDetachedComments = (
|
export const restoreDetachedComments = (
|
||||||
currentDoc: CurrentDoc,
|
currentDoc: DocumentContainer,
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
storedComments: RangeSet<any>
|
storedComments: RangeSet<any>
|
||||||
) => {
|
) => {
|
||||||
const ops: ChangeOperation[] = []
|
const ops: CommentOperation[] = []
|
||||||
|
|
||||||
const cursor = storedComments.iter()
|
const cursor = storedComments.iter()
|
||||||
|
|
||||||
while (cursor.value) {
|
while (cursor.value) {
|
||||||
const { id } = cursor.value.comment
|
const { id } = cursor.value.comment
|
||||||
|
|
||||||
const comment = currentDoc.ranges.comments.find(item => item.id === id)
|
const comment = currentDoc.ranges!.comments.find(item => item.id === id)
|
||||||
|
|
||||||
// check that the comment still exists and is detached
|
// check that the comment still exists and is detached
|
||||||
if (comment && comment.op.c === '') {
|
if (comment && comment.op.c === '') {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Prec, Transaction, Annotation, ChangeSpec } from '@codemirror/state'
|
import { Prec, Transaction, Annotation, ChangeSpec } from '@codemirror/state'
|
||||||
import { EditorView, ViewPlugin } from '@codemirror/view'
|
import { EditorView, ViewPlugin } from '@codemirror/view'
|
||||||
import { EventEmitter } from 'events'
|
import { EventEmitter } from 'events'
|
||||||
import { CurrentDoc } from '../../../../../types/current-doc'
|
|
||||||
import { ShareDoc } from '../../../../../types/share-doc'
|
import { ShareDoc } from '../../../../../types/share-doc'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import { DocumentContainer } from '@/features/ide-react/editor/document-container'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Integrate CodeMirror 6 with the real-time system, via ShareJS.
|
* Integrate CodeMirror 6 with the real-time system, via ShareJS.
|
||||||
|
@ -34,7 +34,7 @@ export type ChangeDescription = {
|
||||||
* A custom extension that connects the CodeMirror 6 editor to the currently open ShareJS document.
|
* A custom extension that connects the CodeMirror 6 editor to the currently open ShareJS document.
|
||||||
*/
|
*/
|
||||||
export const realtime = (
|
export const realtime = (
|
||||||
{ currentDoc }: { currentDoc: CurrentDoc },
|
{ currentDoc }: { currentDoc: DocumentContainer },
|
||||||
handleError: (error: Error) => void
|
handleError: (error: Error) => void
|
||||||
) => {
|
) => {
|
||||||
const realtimePlugin = ViewPlugin.define(view => {
|
const realtimePlugin = ViewPlugin.define(view => {
|
||||||
|
|
|
@ -21,15 +21,14 @@ import {
|
||||||
StoredComment,
|
StoredComment,
|
||||||
} from './changes/comments'
|
} from './changes/comments'
|
||||||
import { invertedEffects } from '@codemirror/commands'
|
import { invertedEffects } from '@codemirror/commands'
|
||||||
import { CurrentDoc } from '../../../../../types/current-doc'
|
|
||||||
import { Change, DeleteOperation } from '../../../../../types/change'
|
import { Change, DeleteOperation } from '../../../../../types/change'
|
||||||
import { ChangeManager } from './changes/change-manager'
|
import { ChangeManager } from './changes/change-manager'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import { isCommentOperation, isDeleteOperation } from '@/utils/operations'
|
||||||
import {
|
import {
|
||||||
isChangeOperation,
|
DocumentContainer,
|
||||||
isCommentOperation,
|
RangesTrackerWithResolvedThreadIds,
|
||||||
isDeleteOperation,
|
} from '@/features/ide-react/editor/document-container'
|
||||||
} from '@/utils/operations'
|
|
||||||
|
|
||||||
const clearChangesEffect = StateEffect.define()
|
const clearChangesEffect = StateEffect.define()
|
||||||
const buildChangesEffect = StateEffect.define()
|
const buildChangesEffect = StateEffect.define()
|
||||||
|
@ -46,7 +45,7 @@ const restoreDetachedCommentsEffect = StateEffect.define<RangeSet<any>>({
|
||||||
})
|
})
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
currentDoc: CurrentDoc
|
currentDoc: DocumentContainer
|
||||||
loadingThreads: boolean
|
loadingThreads: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,7 +181,11 @@ export const buildChangeMarkers = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildChangeDecorations = (currentDoc: CurrentDoc) => {
|
const buildChangeDecorations = (currentDoc: DocumentContainer) => {
|
||||||
|
if (!currentDoc.ranges) {
|
||||||
|
return Decoration.none
|
||||||
|
}
|
||||||
|
|
||||||
const changes = [...currentDoc.ranges.changes, ...currentDoc.ranges.comments]
|
const changes = [...currentDoc.ranges.changes, ...currentDoc.ranges.comments]
|
||||||
|
|
||||||
const decorations = []
|
const decorations = []
|
||||||
|
@ -245,7 +248,7 @@ class ChangeCalloutWidget extends WidgetType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createChangeRange = (change: Change, currentDoc: CurrentDoc) => {
|
const createChangeRange = (change: Change, currentDoc: DocumentContainer) => {
|
||||||
const { id, metadata, op } = change
|
const { id, metadata, op } = change
|
||||||
|
|
||||||
const from = op.p
|
const from = op.p
|
||||||
|
@ -273,14 +276,18 @@ const createChangeRange = (change: Change, currentDoc: CurrentDoc) => {
|
||||||
return [calloutWidget.range(from, from), changeWidget.range(from, from)]
|
return [calloutWidget.range(from, from), changeWidget.range(from, from)]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isChangeOperation(op) && currentDoc.ranges.resolvedThreadIds[op.t]) {
|
const _isCommentOperation = isCommentOperation(op)
|
||||||
|
|
||||||
|
if (
|
||||||
|
_isCommentOperation &&
|
||||||
|
(currentDoc.ranges as RangesTrackerWithResolvedThreadIds)
|
||||||
|
.resolvedThreadIds![op.t]
|
||||||
|
) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const isChangeOrCommentOperation =
|
const opType = _isCommentOperation ? 'c' : 'i'
|
||||||
isChangeOperation(op) || isCommentOperation(op)
|
const changedText = _isCommentOperation ? op.c : op.i
|
||||||
const opType = isChangeOrCommentOperation ? 'c' : 'i'
|
|
||||||
const changedText = isChangeOrCommentOperation ? op.c : op.i
|
|
||||||
const to = from + changedText.length
|
const to = from + changedText.length
|
||||||
|
|
||||||
// Mark decorations must not be empty
|
// Mark decorations must not be empty
|
||||||
|
|
|
@ -47,7 +47,6 @@ import {
|
||||||
import { setKeybindings } from '../extensions/keybindings'
|
import { setKeybindings } from '../extensions/keybindings'
|
||||||
import { Highlight } from '../../../../../types/highlight'
|
import { Highlight } from '../../../../../types/highlight'
|
||||||
import { EditorView } from '@codemirror/view'
|
import { EditorView } from '@codemirror/view'
|
||||||
import { CurrentDoc } from '../../../../../types/current-doc'
|
|
||||||
import { useErrorHandler } from 'react-error-boundary'
|
import { useErrorHandler } from 'react-error-boundary'
|
||||||
import { setVisual } from '../extensions/visual/visual'
|
import { setVisual } from '../extensions/visual/visual'
|
||||||
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||||
|
@ -56,6 +55,7 @@ import { setDocName } from '@/features/source-editor/extensions/doc-name'
|
||||||
import isValidTexFile from '@/main/is-valid-tex-file'
|
import isValidTexFile from '@/main/is-valid-tex-file'
|
||||||
import { captureException } from '@/infrastructure/error-reporter'
|
import { captureException } from '@/infrastructure/error-reporter'
|
||||||
import grammarlyExtensionPresent from '@/shared/utils/grammarly'
|
import grammarlyExtensionPresent from '@/shared/utils/grammarly'
|
||||||
|
import { DocumentContainer } from '@/features/ide-react/editor/document-container'
|
||||||
|
|
||||||
function useCodeMirrorScope(view: EditorView) {
|
function useCodeMirrorScope(view: EditorView) {
|
||||||
const ide = useIdeContext()
|
const ide = useIdeContext()
|
||||||
|
@ -71,7 +71,9 @@ function useCodeMirrorScope(view: EditorView) {
|
||||||
|
|
||||||
const [loadingThreads] = useScopeValue<boolean>('loadingThreads')
|
const [loadingThreads] = useScopeValue<boolean>('loadingThreads')
|
||||||
|
|
||||||
const [currentDoc] = useScopeValue<CurrentDoc | null>('editor.sharejs_doc')
|
const [currentDoc] = useScopeValue<DocumentContainer | null>(
|
||||||
|
'editor.sharejs_doc'
|
||||||
|
)
|
||||||
const [docName] = useScopeValue<string>('editor.open_doc_name')
|
const [docName] = useScopeValue<string>('editor.open_doc_name')
|
||||||
const [trackChanges] = useScopeValue<boolean>('editor.trackChanges')
|
const [trackChanges] = useScopeValue<boolean>('editor.trackChanges')
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {
|
import {
|
||||||
ChangeOperation,
|
|
||||||
CommentOperation,
|
CommentOperation,
|
||||||
DeleteOperation,
|
DeleteOperation,
|
||||||
InsertOperation,
|
InsertOperation,
|
||||||
|
@ -8,9 +7,7 @@ import {
|
||||||
|
|
||||||
export const isInsertOperation = (op: Operation): op is InsertOperation =>
|
export const isInsertOperation = (op: Operation): op is InsertOperation =>
|
||||||
'i' in op
|
'i' in op
|
||||||
export const isChangeOperation = (op: Operation): op is ChangeOperation =>
|
|
||||||
'c' in op && 't' in op
|
|
||||||
export const isCommentOperation = (op: Operation): op is CommentOperation =>
|
export const isCommentOperation = (op: Operation): op is CommentOperation =>
|
||||||
'c' in op && !('t' in op)
|
'c' in op
|
||||||
export const isDeleteOperation = (op: Operation): op is DeleteOperation =>
|
export const isDeleteOperation = (op: Operation): op is DeleteOperation =>
|
||||||
'd' in op
|
'd' in op
|
||||||
|
|
|
@ -1,36 +1,31 @@
|
||||||
|
import { ThreadId } from './review-panel/review-panel'
|
||||||
|
import { UserId } from './user'
|
||||||
|
|
||||||
export interface Operation {
|
export interface Operation {
|
||||||
p: number
|
p: number // position
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InsertOperation extends Operation {
|
export interface InsertOperation extends Operation {
|
||||||
i: string
|
i: string // inserted text
|
||||||
t: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChangeOperation extends Operation {
|
|
||||||
c: string
|
|
||||||
t: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeleteOperation extends Operation {
|
export interface DeleteOperation extends Operation {
|
||||||
d: string
|
d: string // deleted text
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommentOperation extends Operation {
|
export interface CommentOperation extends Operation {
|
||||||
c: string
|
c: string // comment text
|
||||||
|
t: ThreadId // thread/comment id
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NonCommentOperation =
|
export type EditOperation = InsertOperation | DeleteOperation
|
||||||
| InsertOperation
|
|
||||||
| ChangeOperation
|
|
||||||
| DeleteOperation
|
|
||||||
|
|
||||||
export type AnyOperation = NonCommentOperation | CommentOperation
|
export type AnyOperation = EditOperation | CommentOperation
|
||||||
|
|
||||||
export type Change<T extends AnyOperation = AnyOperation> = {
|
export type Change<T extends AnyOperation = AnyOperation> = {
|
||||||
id: string
|
id: string
|
||||||
metadata?: {
|
metadata?: {
|
||||||
user_id: string
|
user_id: UserId | null
|
||||||
ts: Date
|
ts: Date
|
||||||
}
|
}
|
||||||
op: T
|
op: T
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
import EventEmitter from '../frontend/js/utils/EventEmitter'
|
|
||||||
import { ShareDoc } from './share-doc'
|
|
||||||
import { EditorFacade } from '../frontend/js/features/source-editor/extensions/realtime'
|
|
||||||
import {
|
|
||||||
AnyOperation,
|
|
||||||
Change,
|
|
||||||
ChangeOperation,
|
|
||||||
CommentOperation,
|
|
||||||
DeleteOperation,
|
|
||||||
InsertOperation,
|
|
||||||
} from './change'
|
|
||||||
|
|
||||||
// type for the Document class in ide/editor/Document.js
|
|
||||||
// note: this is a custom EventEmitter class
|
|
||||||
|
|
||||||
// TODO: MIGRATION: This doesn't match the type for
|
|
||||||
// ide-react/editor/document.ts, which has a nullable `ranges` property and some
|
|
||||||
// other quirks. They should match.
|
|
||||||
export interface CurrentDoc extends EventEmitter {
|
|
||||||
doc_id: string
|
|
||||||
docName: string
|
|
||||||
doc: ShareDoc | null
|
|
||||||
track_changes_as: string | null
|
|
||||||
ranges: {
|
|
||||||
changes: Change<InsertOperation | ChangeOperation | DeleteOperation>[]
|
|
||||||
comments: Change<CommentOperation>[]
|
|
||||||
resolvedThreadIds: Record<string, any>
|
|
||||||
removeCommentId: (id: string) => void
|
|
||||||
removeChangeIds: (ids: string[]) => void
|
|
||||||
getChanges: (
|
|
||||||
ids: string[]
|
|
||||||
) => Change<InsertOperation | ChangeOperation | DeleteOperation>[]
|
|
||||||
validate: (text: string) => void
|
|
||||||
}
|
|
||||||
attachToCM6: (editor: EditorFacade) => void
|
|
||||||
detachFromCM6: () => void
|
|
||||||
submitOp: (op: AnyOperation) => void
|
|
||||||
getSnapshot: () => string
|
|
||||||
}
|
|
Loading…
Reference in a new issue