diff --git a/external/@worldbrain/memex-common b/external/@worldbrain/memex-common index de1a65f2a7..fffeed5c5f 160000 --- a/external/@worldbrain/memex-common +++ b/external/@worldbrain/memex-common @@ -1 +1 @@ -Subproject commit de1a65f2a714600fed2103b1678efa12ae51b146 +Subproject commit fffeed5c5f2791726456ded719e2325a286509cb diff --git a/src/page-indexing/background/index.ts b/src/page-indexing/background/index.ts index e56a611a7a..fc56982fc6 100644 --- a/src/page-indexing/background/index.ts +++ b/src/page-indexing/background/index.ts @@ -32,7 +32,9 @@ import { PipelineRes, } from 'src/search/types' import { extractUrlParts } from '@worldbrain/memex-common/lib/url-utils/extract-parts' -import pagePipeline from '@worldbrain/memex-common/lib/page-indexing/pipeline' +import pagePipeline, { + extractTerms, +} from '@worldbrain/memex-common/lib/page-indexing/pipeline' import { initErrHandler } from 'src/search/storage' import { Page, PageCreationProps, PageCreationOpts } from 'src/search' import { DexieUtilsPlugin } from 'src/search/plugins' @@ -120,6 +122,9 @@ export class PageIndexingBackground { }) this.remoteFunctions = { + updatePageTitle: remoteFunctionWithExtraArgs((info, params) => + this.updatePageTitle(params), + ), initContentIdentifier: remoteFunctionWithExtraArgs((info, params) => this.initContentIdentifier({ ...params, @@ -376,6 +381,28 @@ export class PageIndexingBackground { await this.storage.createVisitsIfNeeded(pageData.url, visits) } + async updatePageTitle(params: { + normaliedPageUrl: string + title: string + }): Promise { + const title = params.title.trim() + if (!title.length) { + throw new Error(`Cannot set empty title`) + } + const existingPage = await this.storage.getPage(params.normaliedPageUrl) + if (!existingPage) { + throw new Error(`Cannot update title for non-existent page`) + } + await this.storage.updatePage( + { + ...existingPage, + fullTitle: title, + titleTerms: [...extractTerms(title)], + }, + existingPage, + ) + } + async addFavIconIfNeeded(url: string, favIcon: string) { const { hostname } = extractUrlParts(url) diff --git a/src/page-indexing/background/storage.ts b/src/page-indexing/background/storage.ts index 8eb408e0e9..8be1b40b4f 100644 --- a/src/page-indexing/background/storage.ts +++ b/src/page-indexing/background/storage.ts @@ -6,7 +6,7 @@ import { } from '@worldbrain/storex-pattern-modules' import { COLLECTION_DEFINITIONS as PAGE_COLLECTION_DEFINITIONS } from '@worldbrain/memex-common/lib/storage/modules/pages/constants' import { normalizeUrl } from '@worldbrain/memex-common/lib/url-utils/normalize' -import { PageCreationProps, PipelineRes, VisitInteraction } from 'src/search' +import type { PipelineRes, VisitInteraction } from 'src/search' import { initErrHandler } from 'src/search/storage' import { getTermsField } from '@worldbrain/memex-common/lib/storage/utils' import { diff --git a/src/page-indexing/background/types.ts b/src/page-indexing/background/types.ts index dc9861851c..a0e4afb25d 100644 --- a/src/page-indexing/background/types.ts +++ b/src/page-indexing/background/types.ts @@ -25,6 +25,10 @@ export interface PageIndexingInterface { { fullPageUrl: string }, string | null > + updatePageTitle: RemoteFunctionWithExtraArgs< + Role, + { normaliedPageUrl: string; title: string } + > } export interface InitContentIdentifierParams { diff --git a/src/personal-cloud/background/backend/translation-layer/index.test.ts b/src/personal-cloud/background/backend/translation-layer/index.test.ts index f670eaecae..ec176bf46f 100644 --- a/src/personal-cloud/background/backend/translation-layer/index.test.ts +++ b/src/personal-cloud/background/backend/translation-layer/index.test.ts @@ -53,6 +53,9 @@ import omit from 'lodash/omit' import { ROOT_NODE_PARENT_ID } from '@worldbrain/memex-common/lib/content-sharing/tree-utils' import type { ListTree } from 'src/custom-lists/background/types' import delay from 'src/util/delay' +import { mergeTermFields } from '@worldbrain/memex-common/lib/page-indexing/utils' +import type { PipelineRes } from 'src/search' +import { extractTerms } from '@worldbrain/memex-common/lib/page-indexing/pipeline' const isFBEmu = process.env.TEST_SERVER_STORAGE === 'firebase-emulator' @@ -588,6 +591,115 @@ describe('Personal cloud translation layer', () => { testSyncPushTrigger, } = await setup() + testSyncPushTrigger({ wasTriggered: false }) + await insertTestPages(setups[0].storageManager) + // Add a shared annotation to trigger creation of sharedPageInfo for our page + await setups[0].storageManager + .collection('annotations') + .createObject(LOCAL_TEST_DATA_V24.annotations.first) + await setups[0].storageManager + .collection('sharedAnnotationMetadata') + .createObject( + LOCAL_TEST_DATA_V24.sharedAnnotationMetadata.first, + ) + const updatedTitle = 'Updated title' + + const localPage: PipelineRes = await setups[0].storageManager + .collection('pages') + .findOneObject({ + url: LOCAL_TEST_DATA_V24.pages.first.url, + }) + expect(localPage).toEqual({ + ...LOCAL_TEST_DATA_V24.pages.first, + titleTerms: expect.any(Array), + urlTerms: expect.any(Array), + }) + + await setups[0].backgroundModules.pages.updatePageTitle({ + normaliedPageUrl: LOCAL_TEST_DATA_V24.pages.first.url, + title: updatedTitle, + }) + expect( + await setups[0].storageManager + .collection('pages') + .findOneObject({ + url: LOCAL_TEST_DATA_V24.pages.first.url, + }), + ).toEqual({ + ...LOCAL_TEST_DATA_V24.pages.first, + fullTitle: updatedTitle, + titleTerms: mergeTermFields( + 'titleTerms', + { titleTerms: extractTerms(updatedTitle) }, + { titleTerms: localPage.titleTerms }, + ), + urlTerms: expect.any(Array), + }) + + await setups[0].backgroundModules.personalCloud.waitForSync() + + const remoteData = serverIdCapturer.mergeIds(REMOTE_TEST_DATA_V24) + const testMetadata = remoteData.personalContentMetadata + const testLocators = remoteData.personalContentLocator + + // prettier-ignore + expect( + await getDatabaseContents([ + // 'dataUsageEntry', + 'personalDataChange', + 'personalBlockStats', + 'personalContentMetadata', + 'personalContentLocator', + 'sharedPageInfo', + ], { getWhere: getPersonalWhere }), + ).toEqual({ + ...personalDataChanges(remoteData, [ + [DataChangeType.Modify, 'personalContentMetadata', testMetadata.first.id], + ], { skipChanges: 7 }), + personalBlockStats: [personalBlockStats({ usedBlocks: 3 })], + personalContentMetadata: [ + { + ...testMetadata.first, + title: updatedTitle, + }, + testMetadata.second, + ], + personalContentLocator: [testLocators.first, testLocators.second], + sharedPageInfo: [{ + id: expect.anything(), + creator: TEST_USER.id, + fullTitle: updatedTitle, + normalizedUrl: localPage.url, + originalUrl: localPage.fullUrl, + updatedWhen: expect.anything(), + createdWhen: expect.anything(), + }], + }) + // prettier-ignore + await testDownload([ + { + type: PersonalCloudUpdateType.Overwrite, collection: 'pages', object: { + ...LOCAL_TEST_DATA_V24.pages.first, + fullTitle: updatedTitle + } + }, + ], { skip: 4 }) + testSyncPushTrigger({ wasTriggered: true }) + }) + + it('should update page titles', async () => { + const { + setups, + serverIdCapturer, + serverStorageManager, + getPersonalWhere, + personalDataChanges, + personalBlockStats, + getDatabaseContents, + testDownload, + testSyncPushTrigger, + } = await setup() + testSyncPushTrigger({ wasTriggered: false }) await insertTestPages(setups[0].storageManager) await setups[0].storageManager.collection('pages').updateObjects(