diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index 581880e4f..17320ff8f 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -94,6 +94,7 @@ export type Article = { contentReader: ContentReader; createdAt: Scalars['Date']; description?: Maybe; + directionality?: Maybe; feedContent?: Maybe; folder: Scalars['String']; hasContent?: Maybe; @@ -729,6 +730,11 @@ export type DeviceTokensSuccess = { deviceTokens: Array; }; +export enum DirectionalityType { + Ltr = 'LTR', + Rtl = 'RTL' +} + export type EmptyTrashError = { __typename?: 'EmptyTrashError'; errorCodes: Array; @@ -2404,6 +2410,7 @@ export enum SearchErrorCode { export type SearchItem = { __typename?: 'SearchItem'; + aiSummary?: Maybe; annotation?: Maybe; archivedAt?: Maybe; author?: Maybe; @@ -2412,6 +2419,7 @@ export type SearchItem = { contentReader: ContentReader; createdAt: Scalars['Date']; description?: Maybe; + directionality?: Maybe; feedContent?: Maybe; folder: Scalars['String']; highlights?: Maybe>; @@ -3378,6 +3386,7 @@ export enum UploadImportFileType { export type User = { __typename?: 'User'; email?: Maybe; + features?: Maybe>>; followersCount?: Maybe; friendsCount?: Maybe; id: Scalars['ID']; @@ -3707,6 +3716,7 @@ export type ResolversTypes = { DeviceTokensErrorCode: DeviceTokensErrorCode; DeviceTokensResult: ResolversTypes['DeviceTokensError'] | ResolversTypes['DeviceTokensSuccess']; DeviceTokensSuccess: ResolverTypeWrapper; + DirectionalityType: DirectionalityType; EmptyTrashError: ResolverTypeWrapper; EmptyTrashErrorCode: EmptyTrashErrorCode; EmptyTrashResult: ResolversTypes['EmptyTrashError'] | ResolversTypes['EmptyTrashSuccess']; @@ -4605,6 +4615,7 @@ export type ArticleResolvers; createdAt?: Resolver; description?: Resolver, ParentType, ContextType>; + directionality?: Resolver, ParentType, ContextType>; feedContent?: Resolver, ParentType, ContextType>; folder?: Resolver; hasContent?: Resolver, ParentType, ContextType>; @@ -5945,6 +5956,7 @@ export type SearchErrorResolvers = { + aiSummary?: Resolver, ParentType, ContextType>; annotation?: Resolver, ParentType, ContextType>; archivedAt?: Resolver, ParentType, ContextType>; author?: Resolver, ParentType, ContextType>; @@ -5953,6 +5965,7 @@ export type SearchItemResolvers; createdAt?: Resolver; description?: Resolver, ParentType, ContextType>; + directionality?: Resolver, ParentType, ContextType>; feedContent?: Resolver, ParentType, ContextType>; folder?: Resolver; highlights?: Resolver>, ParentType, ContextType>; @@ -6528,6 +6541,7 @@ export type UploadImportFileSuccessResolvers = { email?: Resolver, ParentType, ContextType>; + features?: Resolver>>, ParentType, ContextType>; followersCount?: Resolver, ParentType, ContextType>; friendsCount?: Resolver, ParentType, ContextType>; id?: Resolver; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index b344a250a..81959567a 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -69,6 +69,7 @@ type Article { contentReader: ContentReader! createdAt: Date! description: String + directionality: DirectionalityType feedContent: String folder: String! hasContent: Boolean @@ -646,6 +647,11 @@ type DeviceTokensSuccess { deviceTokens: [DeviceToken!]! } +enum DirectionalityType { + LTR + RTL +} + type EmptyTrashError { errorCodes: [EmptyTrashErrorCode!]! } @@ -1832,6 +1838,7 @@ enum SearchErrorCode { } type SearchItem { + aiSummary: String annotation: String archivedAt: Date author: String @@ -1840,6 +1847,7 @@ type SearchItem { contentReader: ContentReader! createdAt: Date! description: String + directionality: DirectionalityType feedContent: String folder: String! highlights: [Highlight!] @@ -2733,6 +2741,7 @@ enum UploadImportFileType { type User { email: String + features: [String] followersCount: Int friendsCount: Int id: ID! diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 86b1f46e2..ef17d9643 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -354,6 +354,11 @@ const schema = gql` note: String } + enum DirectionalityType { + LTR + RTL + } + type Article { id: ID! title: String! @@ -399,6 +404,7 @@ const schema = gql` wordsCount: Int folder: String! feedContent: String + directionality: DirectionalityType } # Query: article @@ -1649,6 +1655,7 @@ const schema = gql` links: JSON folder: String! aiSummary: String + directionality: DirectionalityType } type SearchItemEdge { diff --git a/packages/api/src/utils/helpers.ts b/packages/api/src/utils/helpers.ts index e76a9875a..9e3e18cd3 100644 --- a/packages/api/src/utils/helpers.ts +++ b/packages/api/src/utils/helpers.ts @@ -18,6 +18,7 @@ import { ContentReader, CreateArticleError, CreateArticleSuccess, + DirectionalityType, FeedArticle, Highlight, PageType, @@ -227,6 +228,7 @@ export const libraryItemToArticle = (item: LibraryItem): Article => ({ uploadFileId: item.uploadFile?.id, pageType: item.itemType as unknown as PageType, wordsCount: item.wordCount, + directionality: item.directionality as unknown as DirectionalityType, }) export const libraryItemToSearchItem = (item: LibraryItem): SearchItem => ({ @@ -245,6 +247,7 @@ export const libraryItemToSearchItem = (item: LibraryItem): SearchItem => ({ image: item.thumbnail, highlights: item.highlights?.map(highlightDataToHighlight), wordsCount: item.wordCount, + directionality: item.directionality as unknown as DirectionalityType, }) export const isParsingTimeout = (libraryItem: LibraryItem): boolean => { diff --git a/packages/api/test/resolvers/article.test.ts b/packages/api/test/resolvers/article.test.ts index 30c397d53..b7b638d22 100644 --- a/packages/api/test/resolvers/article.test.ts +++ b/packages/api/test/resolvers/article.test.ts @@ -7,7 +7,11 @@ import { DeepPartial } from 'typeorm' import { Group } from '../../src/entity/groups/group' import { Highlight } from '../../src/entity/highlight' import { Label } from '../../src/entity/label' -import { LibraryItem, LibraryItemState } from '../../src/entity/library_item' +import { + DirectionalityType, + LibraryItem, + LibraryItemState, +} from '../../src/entity/library_item' import { UploadFile } from '../../src/entity/upload_file' import { User } from '../../src/entity/user' import { @@ -118,6 +122,7 @@ const getArticleQuery = (slug: string) => { id slug content + directionality highlights { id shortId @@ -155,6 +160,7 @@ const searchQuery = (keyword = '') => { url createdAt updatedAt + directionality highlights { id } @@ -183,32 +189,9 @@ const searchQuery = (keyword = '') => { ` } -const savePageQuery = ( - url: string, - title: string, - originalContent: string, - state: ArticleSavingRequestStatus | null = null, - labels: string[] | null = null, - clientRequestId = generateFakeUuid(), - source = 'puppeteer-parse' -) => { - return ` - mutation { - savePage( - input: { - url: "${url}", - source: "${source}", - clientRequestId: "${clientRequestId}", - title: "${title}", - originalContent: "${originalContent}" - state: ${state} - labels: ${ - labels - ? '[' + labels.map((label) => `{ name: "${label}" }`) + ']' - : null - } - } - ) { +const savePageQuery = ` + mutation SavePage($input: SavePageInput!) { + savePage(input: $input) { ... on SaveSuccess { url } @@ -216,9 +199,7 @@ const savePageQuery = ( errorCodes } } - } - ` -} + }` const saveFileQuery = ( clientRequestId: string, @@ -452,6 +433,7 @@ describe('Article API', () => { readingProgressTopPercent: 100, user, originalUrl: 'https://blog.omnivore.app/test-with-omnivore', + directionality: DirectionalityType.RTL, } const item = await createOrUpdateLibraryItem(itemToCreate, user.id) itemId = item.id @@ -487,6 +469,9 @@ describe('Article API', () => { const res = await graphqlRequest(query, authToken).expect(200) expect(res.body.data.article.article.slug).to.eql(slug) + expect(res.body.data.article.article.directionality).to.eql( + DirectionalityType.RTL + ) }) it('should return highlights', async () => { @@ -543,14 +528,11 @@ describe('Article API', () => { }) describe('SavePage', () => { - let query = '' let title = 'Example Title' let url = 'https://blog.omnivore.app' - let originalContent = '
Example Content
' - - beforeEach(() => { - query = savePageQuery(url, title, originalContent) - }) + let originalContent = + '
Example Content
' + let source = 'puppeteer-parse' context('when we save a new item', () => { after(async () => { @@ -558,7 +540,15 @@ describe('Article API', () => { }) it('should return a slugged url', async () => { - const res = await graphqlRequest(query, authToken).expect(200) + const res = await graphqlRequest(savePageQuery, authToken, { + input: { + url, + originalContent, + title, + clientRequestId: generateFakeUuid(), + source, + }, + }).expect(200) expect(res.body.data.savePage.url).to.startsWith( 'http://localhost:3000/fakeUser/example-title-' ) @@ -575,10 +565,16 @@ describe('Article API', () => { }) it('it should return that item in the Search Query', async () => { - await graphqlRequest( - savePageQuery(url, title, originalContent), - authToken - ).expect(200) + const variables = { + input: { + url, + originalContent, + title, + clientRequestId: generateFakeUuid(), + source, + }, + } + await graphqlRequest(savePageQuery, authToken, variables).expect(200) // Save a link, then archive it let allLinks = await graphqlRequest( @@ -596,17 +592,7 @@ describe('Article API', () => { expect(allLinks.body.data.search.edges[0]?.node?.url).to.not.eq(url) // Now save the link again, and ensure it is returned - await graphqlRequest( - savePageQuery( - url, - title, - originalContent, - null, - null, - generateFakeUuid() - ), - authToken - ).expect(200) + await graphqlRequest(savePageQuery, authToken, variables).expect(200) allLinks = await graphqlRequest( searchQuery('in:inbox'), @@ -614,6 +600,9 @@ describe('Article API', () => { ).expect(200) expect(allLinks.body.data.search.edges[0].node.id).to.eq(justSavedId) expect(allLinks.body.data.search.edges[0].node.url).to.eq(url) + expect(allLinks.body.data.search.edges[0].node.directionality).to.eq( + 'RTL' + ) }) }) @@ -628,15 +617,23 @@ describe('Article API', () => { it('saves the labels and archives the item', async () => { const state = ArticleSavingRequestStatus.Archived - const labels = ['test name', 'test name 2'] - await graphqlRequest( - savePageQuery(url, title, originalContent, state, labels), - authToken - ).expect(200) + const labels = [{ name: 'test name' }, { name: 'test name 2' }] + await graphqlRequest(savePageQuery, authToken, { + input: { + url, + state, + labels, + originalContent, + clientRequestId: generateFakeUuid(), + source, + }, + }).expect(200) const savedItem = await findLibraryItemByUrl(url, user.id) expect(savedItem?.archivedAt).to.not.be.null - expect(savedItem?.labels?.map((l) => l.name)).to.include.members(labels) + expect(savedItem?.labels?.map((l) => l.name)).to.include.members( + labels.map((l) => l.name) + ) }) }) @@ -654,10 +651,14 @@ describe('Article API', () => { }) it('does not parse in the backend', async () => { - await graphqlRequest( - savePageQuery(url, title, originalContent, null, null, '', source), - authToken - ).expect(200) + await graphqlRequest(savePageQuery, authToken, { + input: { + url, + source, + originalContent, + clientRequestId: generateFakeUuid(), + }, + }).expect(200) expect(stub).not.to.have.been.called })