From 999abf6d387b8195e661ec153a2703020a9205a8 Mon Sep 17 00:00:00 2001 From: MrwanBaghdad Date: Thu, 12 Feb 2026 03:39:10 +0100 Subject: [PATCH 1/2] Enable parsing of rich (smart) tags in google docs Currently docs.getText skip over smart chip elements within Google docs. Leaving out information. This commit adds support to render smart chip elements in google docs to simple text --- .../__tests__/services/DocsService.test.ts | 173 ++++++++++++++++++ workspace-server/src/services/DocsService.ts | 10 + 2 files changed, 183 insertions(+) diff --git a/workspace-server/src/__tests__/services/DocsService.test.ts b/workspace-server/src/__tests__/services/DocsService.test.ts index b33553a..bae6097 100644 --- a/workspace-server/src/__tests__/services/DocsService.test.ts +++ b/workspace-server/src/__tests__/services/DocsService.test.ts @@ -492,6 +492,179 @@ describe('DocsService', () => { index: 1, }); }); + + it('should extract text from smart chips (date, person, rich link)', async () => { + const mockDoc = { + data: { + tabs: [ + { + documentTab: { + body: { + content: [ + { + paragraph: { + elements: [ + { + textRun: { content: 'Meeting on ' }, + }, + { + dateElement: { + dateElementProperties: { + displayText: 'Jan 15, 2025', + timestamp: '1736899200', + }, + }, + }, + { + textRun: { content: ' with ' }, + }, + { + person: { + personProperties: { + name: 'John Doe', + email: 'john@example.com', + }, + }, + }, + { + textRun: { content: ' - see ' }, + }, + { + richLink: { + richLinkProperties: { + title: 'Project Plan', + uri: 'https://docs.google.com/document/d/abc123', + }, + }, + }, + { + textRun: { content: '\n' }, + }, + ], + }, + }, + ], + }, + }, + }, + ], + }, + }; + mockDocsAPI.documents.get.mockResolvedValue(mockDoc); + + const result = await docsService.getText({ documentId: 'test-doc-id' }); + + expect(result.content[0].text).toBe( + 'Meeting on Jan 15, 2025 with John Doe - see Project Plan\n', + ); + }); + + it('should fall back to email when person name is not available', async () => { + const mockDoc = { + data: { + tabs: [ + { + documentTab: { + body: { + content: [ + { + paragraph: { + elements: [ + { + person: { + personProperties: { + email: 'jane@example.com', + }, + }, + }, + ], + }, + }, + ], + }, + }, + }, + ], + }, + }; + mockDocsAPI.documents.get.mockResolvedValue(mockDoc); + + const result = await docsService.getText({ documentId: 'test-doc-id' }); + + expect(result.content[0].text).toBe('jane@example.com'); + }); + + it('should fall back to uri when rich link title is not available', async () => { + const mockDoc = { + data: { + tabs: [ + { + documentTab: { + body: { + content: [ + { + paragraph: { + elements: [ + { + richLink: { + richLinkProperties: { + uri: 'https://docs.google.com/spreadsheets/d/xyz', + }, + }, + }, + ], + }, + }, + ], + }, + }, + }, + ], + }, + }; + mockDocsAPI.documents.get.mockResolvedValue(mockDoc); + + const result = await docsService.getText({ documentId: 'test-doc-id' }); + + expect(result.content[0].text).toBe( + 'https://docs.google.com/spreadsheets/d/xyz', + ); + }); + + it('should fall back to timestamp when date displayText is not available', async () => { + const mockDoc = { + data: { + tabs: [ + { + documentTab: { + body: { + content: [ + { + paragraph: { + elements: [ + { + dateElement: { + dateElementProperties: { + timestamp: '1736899200', + }, + }, + }, + ], + }, + }, + ], + }, + }, + }, + ], + }, + }; + mockDocsAPI.documents.get.mockResolvedValue(mockDoc); + + const result = await docsService.getText({ documentId: 'test-doc-id' }); + + expect(result.content[0].text).toBe('1736899200'); + }); }); describe('appendText', () => { diff --git a/workspace-server/src/services/DocsService.ts b/workspace-server/src/services/DocsService.ts index b62b7df..80b8a5e 100644 --- a/workspace-server/src/services/DocsService.ts +++ b/workspace-server/src/services/DocsService.ts @@ -441,6 +441,16 @@ export class DocsService { element.paragraph.elements?.forEach((pElement) => { if (pElement.textRun && pElement.textRun.content) { text += pElement.textRun.content; + } else if (pElement.person?.personProperties) { + const { name, email } = pElement.person.personProperties; + text += name || email || ''; + } else if (pElement.richLink?.richLinkProperties) { + const { title, uri } = pElement.richLink.richLinkProperties; + text += title || uri || ''; + } else if (pElement.dateElement?.dateElementProperties) { + const { displayText, timestamp } = + pElement.dateElement.dateElementProperties; + text += displayText || timestamp || ''; } }); } else if (element.table) { From b6ca0eda9c658d03609c8774e2ade78be597f3af Mon Sep 17 00:00:00 2001 From: MrwanBaghdad Date: Thu, 12 Feb 2026 04:05:27 +0100 Subject: [PATCH 2/2] Update the text output --- .../src/__tests__/services/DocsService.test.ts | 9 +++++---- workspace-server/src/services/DocsService.ts | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/workspace-server/src/__tests__/services/DocsService.test.ts b/workspace-server/src/__tests__/services/DocsService.test.ts index 389fabe..818b748 100644 --- a/workspace-server/src/__tests__/services/DocsService.test.ts +++ b/workspace-server/src/__tests__/services/DocsService.test.ts @@ -569,7 +569,7 @@ describe('DocsService', () => { const result = await docsService.getText({ documentId: 'test-doc-id' }); expect(result.content[0].text).toBe( - 'Meeting on Jan 15, 2025 with John Doe - see Project Plan\n', + 'Meeting on Jan 15, 2025 with [John Doe](mailto:john@example.com) - see [Project Plan](https://docs.google.com/document/d/abc123)\n', ); }); @@ -605,10 +605,10 @@ describe('DocsService', () => { const result = await docsService.getText({ documentId: 'test-doc-id' }); - expect(result.content[0].text).toBe('jane@example.com'); + expect(result.content[0].text).toBe('[jane@example.com](mailto:jane@example.com)'); }); - it('should fall back to uri when rich link title is not available', async () => { + it('should render rich link as markdown link', async () => { const mockDoc = { data: { tabs: [ @@ -622,6 +622,7 @@ describe('DocsService', () => { { richLink: { richLinkProperties: { + title: 'Budget Spreadsheet', uri: 'https://docs.google.com/spreadsheets/d/xyz', }, }, @@ -641,7 +642,7 @@ describe('DocsService', () => { const result = await docsService.getText({ documentId: 'test-doc-id' }); expect(result.content[0].text).toBe( - 'https://docs.google.com/spreadsheets/d/xyz', + '[Budget Spreadsheet](https://docs.google.com/spreadsheets/d/xyz)', ); }); diff --git a/workspace-server/src/services/DocsService.ts b/workspace-server/src/services/DocsService.ts index 226d4e8..ed5cfd8 100644 --- a/workspace-server/src/services/DocsService.ts +++ b/workspace-server/src/services/DocsService.ts @@ -446,10 +446,10 @@ export class DocsService { text += pElement.textRun.content; } else if (pElement.person?.personProperties) { const { name, email } = pElement.person.personProperties; - text += name || email || ''; + text += `[${name || email}](mailto:${email})`; } else if (pElement.richLink?.richLinkProperties) { const { title, uri } = pElement.richLink.richLinkProperties; - text += title || uri || ''; + text += `[${title}](${uri})`; } else if (pElement.dateElement?.dateElementProperties) { const { displayText, timestamp } = pElement.dateElement.dateElementProperties;