|
@@ -7,7 +7,7 @@ import type {
|
|
|
IPage, IPageInfo, IPageInfoAll, IPageInfoForEntity, IPageWithMeta, IGrantedGroup, IRevisionHasId,
|
|
IPage, IPageInfo, IPageInfoAll, IPageInfoForEntity, IPageWithMeta, IGrantedGroup, IRevisionHasId,
|
|
|
} from '@growi/core';
|
|
} from '@growi/core';
|
|
|
import {
|
|
import {
|
|
|
- PageGrant, PageStatus, getIdForRef,
|
|
|
|
|
|
|
+ PageGrant, PageStatus, YDocStatus, getIdForRef,
|
|
|
} from '@growi/core';
|
|
} from '@growi/core';
|
|
|
import {
|
|
import {
|
|
|
pagePathUtils, pathUtils,
|
|
pagePathUtils, pathUtils,
|
|
@@ -41,7 +41,6 @@ import {
|
|
|
import type { PageTagRelationDocument } from '~/server/models/page-tag-relation';
|
|
import type { PageTagRelationDocument } from '~/server/models/page-tag-relation';
|
|
|
import PageTagRelation from '~/server/models/page-tag-relation';
|
|
import PageTagRelation from '~/server/models/page-tag-relation';
|
|
|
import type { UserGroupDocument } from '~/server/models/user-group';
|
|
import type { UserGroupDocument } from '~/server/models/user-group';
|
|
|
-import { getYjsConnectionManager } from '~/server/service/yjs-connection-manager';
|
|
|
|
|
import { createBatchStream } from '~/server/util/batch-stream';
|
|
import { createBatchStream } from '~/server/util/batch-stream';
|
|
|
import { collectAncestorPaths } from '~/server/util/collect-ancestor-paths';
|
|
import { collectAncestorPaths } from '~/server/util/collect-ancestor-paths';
|
|
|
import { generalXssFilter } from '~/services/general-xss-filter';
|
|
import { generalXssFilter } from '~/services/general-xss-filter';
|
|
@@ -49,11 +48,12 @@ import loggerFactory from '~/utils/logger';
|
|
|
import { prepareDeleteConfigValuesForCalc } from '~/utils/page-delete-config';
|
|
import { prepareDeleteConfigValuesForCalc } from '~/utils/page-delete-config';
|
|
|
|
|
|
|
|
import type { ObjectIdLike } from '../../interfaces/mongoose-utils';
|
|
import type { ObjectIdLike } from '../../interfaces/mongoose-utils';
|
|
|
-import { Attachment } from '../../models';
|
|
|
|
|
|
|
+import { Attachment } from '../../models/attachment';
|
|
|
import { PathAlreadyExistsError } from '../../models/errors';
|
|
import { PathAlreadyExistsError } from '../../models/errors';
|
|
|
import type { PageOperationDocument } from '../../models/page-operation';
|
|
import type { PageOperationDocument } from '../../models/page-operation';
|
|
|
import PageOperation from '../../models/page-operation';
|
|
import PageOperation from '../../models/page-operation';
|
|
|
import PageRedirect from '../../models/page-redirect';
|
|
import PageRedirect from '../../models/page-redirect';
|
|
|
|
|
+import { Revision } from '../../models/revision';
|
|
|
import { serializePageSecurely } from '../../models/serializers/page-serializer';
|
|
import { serializePageSecurely } from '../../models/serializers/page-serializer';
|
|
|
import ShareLink from '../../models/share-link';
|
|
import ShareLink from '../../models/share-link';
|
|
|
import Subscription from '../../models/subscription';
|
|
import Subscription from '../../models/subscription';
|
|
@@ -63,6 +63,7 @@ import { divideByType } from '../../util/granted-group';
|
|
|
import { configManager } from '../config-manager';
|
|
import { configManager } from '../config-manager';
|
|
|
import type { IPageGrantService } from '../page-grant';
|
|
import type { IPageGrantService } from '../page-grant';
|
|
|
import { preNotifyService } from '../pre-notify';
|
|
import { preNotifyService } from '../pre-notify';
|
|
|
|
|
+import { getYjsService } from '../yjs';
|
|
|
|
|
|
|
|
import { BULK_REINDEX_SIZE, LIMIT_FOR_MULTIPLE_PAGE_OP } from './consts';
|
|
import { BULK_REINDEX_SIZE, LIMIT_FOR_MULTIPLE_PAGE_OP } from './consts';
|
|
|
import type { IPageService } from './page-service';
|
|
import type { IPageService } from './page-service';
|
|
@@ -832,7 +833,6 @@ class PageService implements IPageService {
|
|
|
|
|
|
|
|
private async renamePageV4(page, newPagePath, user, options) {
|
|
private async renamePageV4(page, newPagePath, user, options) {
|
|
|
const Page = this.crowi.model('Page');
|
|
const Page = this.crowi.model('Page');
|
|
|
- const Revision = this.crowi.model('Revision');
|
|
|
|
|
const {
|
|
const {
|
|
|
isRecursively = false,
|
|
isRecursively = false,
|
|
|
createRedirectPage = false,
|
|
createRedirectPage = false,
|
|
@@ -1348,7 +1348,6 @@ class PageService implements IPageService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const Page = this.crowi.model('Page');
|
|
const Page = this.crowi.model('Page');
|
|
|
- const Revision = this.crowi.model('Revision');
|
|
|
|
|
|
|
|
|
|
const pageIds = pages.map(page => page._id);
|
|
const pageIds = pages.map(page => page._id);
|
|
|
const revisions = await Revision.find({ pageId: { $in: pageIds } });
|
|
const revisions = await Revision.find({ pageId: { $in: pageIds } });
|
|
@@ -1356,7 +1355,7 @@ class PageService implements IPageService {
|
|
|
// Mapping to set to the body of the new revision
|
|
// Mapping to set to the body of the new revision
|
|
|
const pageIdRevisionMapping = {};
|
|
const pageIdRevisionMapping = {};
|
|
|
revisions.forEach((revision) => {
|
|
revisions.forEach((revision) => {
|
|
|
- pageIdRevisionMapping[revision.pageId] = revision;
|
|
|
|
|
|
|
+ pageIdRevisionMapping[getIdForRef(revision.pageId)] = revision;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// key: oldPageId, value: newPageId
|
|
// key: oldPageId, value: newPageId
|
|
@@ -1404,7 +1403,6 @@ class PageService implements IPageService {
|
|
|
|
|
|
|
|
private async duplicateDescendantsV4(pages, user, oldPagePathPrefix, newPagePathPrefix) {
|
|
private async duplicateDescendantsV4(pages, user, oldPagePathPrefix, newPagePathPrefix) {
|
|
|
const Page = this.crowi.model('Page');
|
|
const Page = this.crowi.model('Page');
|
|
|
- const Revision = this.crowi.model('Revision');
|
|
|
|
|
|
|
|
|
|
const pageIds = pages.map(page => page._id);
|
|
const pageIds = pages.map(page => page._id);
|
|
|
const revisions = await Revision.find({ pageId: { $in: pageIds } });
|
|
const revisions = await Revision.find({ pageId: { $in: pageIds } });
|
|
@@ -1412,7 +1410,7 @@ class PageService implements IPageService {
|
|
|
// Mapping to set to the body of the new revision
|
|
// Mapping to set to the body of the new revision
|
|
|
const pageIdRevisionMapping = {};
|
|
const pageIdRevisionMapping = {};
|
|
|
revisions.forEach((revision) => {
|
|
revisions.forEach((revision) => {
|
|
|
- pageIdRevisionMapping[revision.pageId] = revision;
|
|
|
|
|
|
|
+ pageIdRevisionMapping[getIdForRef(revision.pageId)] = revision;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// key: oldPageId, value: newPageId
|
|
// key: oldPageId, value: newPageId
|
|
@@ -1709,7 +1707,6 @@ class PageService implements IPageService {
|
|
|
|
|
|
|
|
private async deletePageV4(page, user, options = {}, isRecursively = false) {
|
|
private async deletePageV4(page, user, options = {}, isRecursively = false) {
|
|
|
const Page = mongoose.model('Page') as PageModel;
|
|
const Page = mongoose.model('Page') as PageModel;
|
|
|
- const Revision = mongoose.model('Revision') as any; // TODO: Typescriptize model
|
|
|
|
|
|
|
|
|
|
const newPath = Page.getDeletedPageName(page.path);
|
|
const newPath = Page.getDeletedPageName(page.path);
|
|
|
const isTrashed = isTrashPage(page.path);
|
|
const isTrashed = isTrashPage(page.path);
|
|
@@ -1872,7 +1869,6 @@ class PageService implements IPageService {
|
|
|
async deleteCompletelyOperation(pageIds, pagePaths): Promise<void> {
|
|
async deleteCompletelyOperation(pageIds, pagePaths): Promise<void> {
|
|
|
// Delete Attachments, Revisions, Pages and emit delete
|
|
// Delete Attachments, Revisions, Pages and emit delete
|
|
|
const Page = this.crowi.model('Page');
|
|
const Page = this.crowi.model('Page');
|
|
|
- const Revision = this.crowi.model('Revision');
|
|
|
|
|
|
|
|
|
|
const { attachmentService } = this.crowi;
|
|
const { attachmentService } = this.crowi;
|
|
|
const attachments = await Attachment.find({ page: { $in: pageIds } });
|
|
const attachments = await Attachment.find({ page: { $in: pageIds } });
|
|
@@ -3839,7 +3835,6 @@ class PageService implements IPageService {
|
|
|
let savedPage = await page.save();
|
|
let savedPage = await page.save();
|
|
|
|
|
|
|
|
// Create revision
|
|
// Create revision
|
|
|
- const Revision = mongoose.model('Revision') as any; // TODO: Typescriptize model
|
|
|
|
|
const newRevision = Revision.prepareRevision(savedPage, body, null, user, options.origin);
|
|
const newRevision = Revision.prepareRevision(savedPage, body, null, user, options.origin);
|
|
|
savedPage = await pushRevision(savedPage, newRevision, user);
|
|
savedPage = await pushRevision(savedPage, newRevision, user);
|
|
|
await savedPage.populateDataToShowRevision();
|
|
await savedPage.populateDataToShowRevision();
|
|
@@ -3901,7 +3896,6 @@ class PageService implements IPageService {
|
|
|
*/
|
|
*/
|
|
|
private async createV4(path, body, user, options: any = {}) {
|
|
private async createV4(path, body, user, options: any = {}) {
|
|
|
const Page = mongoose.model('Page') as unknown as PageModel;
|
|
const Page = mongoose.model('Page') as unknown as PageModel;
|
|
|
- const Revision = mongoose.model('Revision') as any; // TODO: TypeScriptize model
|
|
|
|
|
|
|
|
|
|
const format = options.format || 'markdown';
|
|
const format = options.format || 'markdown';
|
|
|
const grantUserGroupIds = options.grantUserGroupIds || null;
|
|
const grantUserGroupIds = options.grantUserGroupIds || null;
|
|
@@ -4037,8 +4031,7 @@ class PageService implements IPageService {
|
|
|
let savedPage = await page.save();
|
|
let savedPage = await page.save();
|
|
|
|
|
|
|
|
// Create revision
|
|
// Create revision
|
|
|
- const Revision = mongoose.model('Revision') as any; // TODO: Typescriptize model
|
|
|
|
|
- const dummyUser = { _id: new mongoose.Types.ObjectId() };
|
|
|
|
|
|
|
+ const dummyUser: HasObjectId = { _id: new mongoose.Types.ObjectId().toString() };
|
|
|
const newRevision = Revision.prepareRevision(savedPage, body, null, dummyUser);
|
|
const newRevision = Revision.prepareRevision(savedPage, body, null, dummyUser);
|
|
|
savedPage = await pushRevision(savedPage, newRevision, dummyUser);
|
|
savedPage = await pushRevision(savedPage, newRevision, dummyUser);
|
|
|
|
|
|
|
@@ -4146,7 +4139,6 @@ class PageService implements IPageService {
|
|
|
options: IOptionsForUpdate = {},
|
|
options: IOptionsForUpdate = {},
|
|
|
): Promise<PageDocument> {
|
|
): Promise<PageDocument> {
|
|
|
const Page = mongoose.model('Page') as unknown as PageModel;
|
|
const Page = mongoose.model('Page') as unknown as PageModel;
|
|
|
- const Revision = mongoose.model('Revision') as any; // TODO: Typescriptize model
|
|
|
|
|
|
|
|
|
|
const wasOnTree = pageData.parent != null || isTopPage(pageData.path);
|
|
const wasOnTree = pageData.parent != null || isTopPage(pageData.path);
|
|
|
const isV5Compatible = this.crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
|
|
const isV5Compatible = this.crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
|
|
@@ -4290,7 +4282,6 @@ class PageService implements IPageService {
|
|
|
|
|
|
|
|
async updatePageV4(pageData: PageDocument, body, previousBody, user, options: IOptionsForUpdate = {}): Promise<PageDocument> {
|
|
async updatePageV4(pageData: PageDocument, body, previousBody, user, options: IOptionsForUpdate = {}): Promise<PageDocument> {
|
|
|
const Page = mongoose.model('Page') as unknown as PageModel;
|
|
const Page = mongoose.model('Page') as unknown as PageModel;
|
|
|
- const Revision = mongoose.model('Revision') as any; // TODO: TypeScriptize model
|
|
|
|
|
|
|
|
|
|
// use the previous data if absent
|
|
// use the previous data if absent
|
|
|
const grant = options.grant || pageData.grant;
|
|
const grant = options.grant || pageData.grant;
|
|
@@ -4444,35 +4435,18 @@ class PageService implements IPageService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async getYjsData(pageId: string): Promise<CurrentPageYjsData> {
|
|
async getYjsData(pageId: string): Promise<CurrentPageYjsData> {
|
|
|
- const yjsConnectionManager = getYjsConnectionManager();
|
|
|
|
|
|
|
+ const yjsService = getYjsService();
|
|
|
|
|
|
|
|
- const currentYdoc = yjsConnectionManager.getCurrentYdoc(pageId);
|
|
|
|
|
- const persistedYdoc = await yjsConnectionManager.getPersistedYdoc(pageId);
|
|
|
|
|
-
|
|
|
|
|
- const yjsDraft = (currentYdoc ?? persistedYdoc)?.getText('codemirror').toString();
|
|
|
|
|
- const hasRevisionBodyDiff = await this.hasRevisionBodyDiff(pageId, yjsDraft);
|
|
|
|
|
|
|
+ const currentYdoc = yjsService.getCurrentYdoc(pageId);
|
|
|
|
|
+ const ydocStatus = await yjsService.getYDocStatus(pageId);
|
|
|
|
|
+ const hasYdocsNewerThanLatestRevision = ydocStatus === YDocStatus.DRAFT;
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
- hasRevisionBodyDiff,
|
|
|
|
|
|
|
+ hasYdocsNewerThanLatestRevision,
|
|
|
awarenessStateSize: currentYdoc?.awareness.states.size,
|
|
awarenessStateSize: currentYdoc?.awareness.states.size,
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- async hasRevisionBodyDiff(pageId: string, comparisonTarget?: string): Promise<boolean> {
|
|
|
|
|
- if (comparisonTarget == null) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const Revision = mongoose.model<IRevisionHasId>('Revision');
|
|
|
|
|
- const revision = await Revision.findOne({ pageId }).sort({ createdAt: -1 });
|
|
|
|
|
-
|
|
|
|
|
- if (revision == null) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return revision.body !== comparisonTarget;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
async createTtlIndex(): Promise<void> {
|
|
async createTtlIndex(): Promise<void> {
|
|
|
const wipPageExpirationSeconds = configManager.getConfig('crowi', 'app:wipPageExpirationSeconds') ?? 172800;
|
|
const wipPageExpirationSeconds = configManager.getConfig('crowi', 'app:wipPageExpirationSeconds') ?? 172800;
|
|
|
const collection = mongoose.connection.collection('pages');
|
|
const collection = mongoose.connection.collection('pages');
|