[document-updater] add track-changes support to setDoc (#35820)

GitOrigin-RevId: 5d39dd1945e710ec3f7068830fb2476e59f0f2a5
This commit is contained in:
Jakob Ackermann
2026-09-17 11:39:12 +00:00
committed by Copybot
parent a77a4782cc
commit 00a2765d01
14 changed files with 901 additions and 34 deletions
@@ -114,7 +114,14 @@ async function getDocumentWithHistoryRanges(projectId, docId) {
return doc
}
async function setDocument(projectId, docId, userId, docLines, source) {
async function setDocument(
projectId,
docId,
userId,
docLines,
source,
trackChanges = false
) {
const maybeJson = await fetchString(
`${BASE_URL}/project/${projectId}/doc/${docId}`,
{
@@ -123,6 +130,7 @@ async function setDocument(projectId, docId, userId, docLines, source) {
lines: docLines,
source,
user_id: userId,
trackChanges,
},
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
}
@@ -138,7 +146,14 @@ async function setDocument(projectId, docId, userId, docLines, source) {
}
}
async function appendToDocument(projectId, docId, userId, lines, source) {
async function appendToDocument(
projectId,
docId,
userId,
lines,
source,
trackChanges = false
) {
const maybeJson = await fetchString(
`${BASE_URL}/project/${projectId}/doc/${docId}/append`,
{
@@ -147,6 +162,7 @@ async function appendToDocument(projectId, docId, userId, lines, source) {
lines,
source,
user_id: userId,
trackChanges,
},
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
}
@@ -13,6 +13,7 @@ import LockManager from '../../infrastructure/LockManager.mjs'
import { Project } from '../../models/Project.mjs'
import ProjectEntityHandler from './ProjectEntityHandler.mjs'
import ProjectGetter from './ProjectGetter.mjs'
import ProjectHelper from './ProjectHelper.mjs'
import ProjectLocator from './ProjectLocator.mjs'
import ProjectOptionsHandler from './ProjectOptionsHandler.mjs'
import ProjectUpdateHandler from './ProjectUpdateHandler.mjs'
@@ -388,11 +389,19 @@ const upsertDoc = wrapWithLock(
if (!SafePath.isCleanFilename(docName)) {
throw new Errors.InvalidNameError('invalid element name')
}
const project = await ProjectGetter.promises.getProject(projectId, {
rootFolder: true,
rootDoc_id: true,
track_changes: true,
})
if (project == null) {
throw new Errors.NotFoundError('project not found')
}
let element, folderPath
try {
;({ element, path: folderPath } =
await ProjectLocator.promises.findElement({
project_id: projectId,
project,
element_id: folderId,
type: 'folder',
}))
@@ -421,7 +430,7 @@ const upsertDoc = wrapWithLock(
)
doc.rev = rev
const project =
const updatedProject =
await ProjectEntityMongoUpdateHandler.promises.replaceFileWithDoc(
projectId,
existingFile._id,
@@ -433,15 +442,15 @@ const upsertDoc = wrapWithLock(
projectId,
docId: doc._id,
path: filePath,
projectName: project.name,
projectName: updatedProject.name,
rev: existingFile.rev + 1,
folderId,
})
const projectHistoryId =
project.overleaf &&
project.overleaf.history &&
project.overleaf.history.id
updatedProject.overleaf &&
updatedProject.overleaf.history &&
updatedProject.overleaf.history.id
const newDocs = [
{
doc,
@@ -459,7 +468,7 @@ const upsertDoc = wrapWithLock(
projectId,
projectHistoryId,
userId,
{ oldFiles, newDocs, newProject: project },
{ oldFiles, newDocs, newProject: updatedProject },
source
)
@@ -471,12 +480,17 @@ const upsertDoc = wrapWithLock(
)
return { doc, isNew: true }
} else if (existingDoc) {
const trackChanges = ProjectHelper.isTrackChangesEnabledForUser(
project.track_changes,
userId
)
const result = await DocumentUpdaterHandler.promises.setDocument(
projectId,
existingDoc._id,
userId,
docLines,
source
source,
trackChanges
)
logger.debug(
{ projectId, docId: existingDoc._id },
@@ -509,18 +523,31 @@ const upsertDoc = wrapWithLock(
const appendToDoc = wrapWithLock(
async (projectId, docId, lines, source, userId) => {
const project = await ProjectGetter.promises.getProject(projectId, {
rootFolder: true,
rootDoc_id: true,
track_changes: true,
})
if (project == null) {
throw new Errors.NotFoundError('project not found')
}
const { element } = await ProjectLocator.promises.findElement({
project_id: projectId,
project,
element_id: docId,
type: 'doc',
})
const trackChanges = ProjectHelper.isTrackChangesEnabledForUser(
project.track_changes,
userId
)
return await DocumentUpdaterHandler.promises.appendToDocument(
projectId,
element._id,
userId,
lines,
source
source,
trackChanges
)
}
)
@@ -23,6 +23,7 @@ export default {
isArchived,
isTrashed,
isArchivedOrTrashed,
isTrackChangesEnabledForUser,
getAllowedImagesForUser,
ensureNameIsUnique,
}
@@ -62,6 +63,22 @@ function isArchivedOrTrashed(project, userId) {
return isArchived(project, userId) || isTrashed(project, userId)
}
/**
* Whether track changes is enabled for the given user, from the project's
* track_changes state: `true` means on for everyone (legacy format),
* otherwise it is an object keyed by user id.
*
* @param {boolean | Record<string, boolean> | undefined} trackChangesState
* @param {string | ObjectId | null} userId
* @returns {boolean}
*/
function isTrackChangesEnabledForUser(trackChangesState, userId) {
return (
trackChangesState === true ||
(userId != null && trackChangesState?.[userId.toString()] === true)
)
}
/**
* @param {string[]} nameList
* @param {string} name